Complete the intake-event spec and align workout nullability with the DTO

- MedicationIntakeEvent declares the full row both intake responses
  actually return: autoMissed, attributionSource, injectionSite,
  doseTaken, inventoryConsumption, externalId, updatedAt, syncVersion,
  deletedAt were served but undeclared.
- GET /api/medications/{id}/intake is published (query schema, paged
  envelope); the spec only knew the POST while the client-facing
  history list has consumed the GET since v1.4.37.
- WorkoutListEntry's sportType/startedAt/endedAt/durationSec become
  nullable, matching the DTO — declaring them non-null was a
  strict-decoder bomb for the Swift client.
- OpenAPI regenerated.
This commit is contained in:
Marc-André Bombeck committed 2026-08-14 05:44:36 +02:00
1 parent 4b009c3a45
commit f67fcec1c9
4 files changed
+312 -17

No files matched your search

+210 -12
View File
@@ -3934,6 +3934,79 @@ paths:
$ref: "#/components/schemas/ErrorEnvelope"
"422": *a3
"429": *a4
get:
tags:
- Medications
summary: List a medication's intake events
description: "Paged intake history for one medication (tombstoned rows excluded). `status` filters by action state:
`all` (default, byte-stable pre-v1.4.37 contract), `taken`, `skipped`, or `completed` (taken OR skipped — hides
the ambiguous never-confirmed rows). Sorting by `takenAt` pins NULLs last so skipped/planned rows do not float
above real timestamps."
parameters:
- in: path
name: id
schema:
type: string
required: true
- in: query
name: limit
schema:
default: 25
type: integer
minimum: 1
maximum: 500
- in: query
name: offset
schema:
default: 0
type: integer
minimum: 0
maximum: 9007199254740991
- in: query
name: sortBy
schema:
default: scheduledFor
type: string
enum:
- scheduledFor
- takenAt
- source
- createdAt
- in: query
name: sortDir
schema:
default: desc
type: string
enum:
- asc
- desc
- in: query
name: status
schema:
default: all
type: string
enum:
- all
- taken
- skipped
- completed
responses:
"200":
description: Intake event page.
content:
application/json:
schema:
$ref: "#/components/schemas/ListMedicationIntakeEventsResponse"
"401": *a1
"403": *a6
"404":
description: Medication not found (or owned by another user).
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorEnvelope"
"422": *a3
"429": *a4
/api/medications/{id}/inventory:
get:
tags:
@@ -22856,19 +22929,27 @@ components:
id:
type: string
sportType:
type: string
anyOf:
- type: string
- type: "null"
startedAt:
type: string
format: date-time
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])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
anyOf:
- type: string
format: date-time
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])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
- type: "null"
endedAt:
type: string
format: date-time
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])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
anyOf:
- type: string
format: date-time
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])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
- type: "null"
durationSec:
type: integer
minimum: 0
maximum: 9007199254740991
anyOf:
- type: integer
minimum: 0
maximum: 9007199254740991
- type: "null"
distanceM:
anyOf:
- type: number
@@ -24098,6 +24179,17 @@ components:
- type: "null"
skipped:
type: boolean
autoMissed:
type: boolean
description: True when the nightly cron closed the slot as missed (no user action). Auto-missed rows count against
adherence but never consume inventory.
attributionSource:
type: string
enum:
- AUTO
- USER_PIN
description: "How the row bound to its schedule slot: AUTO = the write path's nearest-slot resolution; USER_PIN = the
user explicitly pinned the slot (the dedup converge keeps the pinned row)."
source:
type: string
enum:
@@ -24114,6 +24206,56 @@ components:
type: string
format: date-time
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])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
updatedAt:
type: string
format: date-time
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])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
injectionSite:
anyOf:
- type: string
enum:
- ABDOMEN_LEFT
- ABDOMEN_RIGHT
- ABDOMEN_UPPER_LEFT
- ABDOMEN_UPPER_RIGHT
- THIGH_LEFT
- THIGH_RIGHT
- UPPER_ARM_LEFT
- UPPER_ARM_RIGHT
- type: "null"
description: Recorded injection site for a site-tracked medication (GLP-1 rotation surface). NULL when the medication
does not track sites or none was recorded.
doseTaken:
anyOf:
- type: string
- type: "null"
description: Free-text dose actually taken when it differed from the scheduled dose (titration weeks, split doses). NULL
= the scheduled dose.
inventoryConsumption:
anyOf:
- {}
- type: "null"
description: JSON ledger of the container decrements this intake caused (`[{itemId, units}]`), written by the
consumption hook. NULL when nothing was consumed (skipped / no tracked inventory).
externalId:
anyOf:
- type: string
- type: "null"
description: v1.28 — client-supplied stable id for externally-mirrored intakes (Apple Health sync); the dedup key for
re-synced rows. NULL for native rows.
syncVersion:
type: integer
minimum: -9007199254740991
maximum: 9007199254740991
description: Monotonic per-row version for incremental sync readers; bumps on every mutation.
deletedAt:
anyOf:
- type: string
format: date-time
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])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
- type: "null"
description: Soft-delete tombstone. Non-null rows are excluded from every list/aggregate read; sync readers use it to
propagate deletions.
required:
- id
- userId
@@ -24121,12 +24263,22 @@ components:
- scheduledFor
- takenAt
- skipped
- autoMissed
- attributionSource
- source
- idempotencyKey
- createdAt
- updatedAt
- injectionSite
- doseTaken
- inventoryConsumption
- externalId
- syncVersion
- deletedAt
additionalProperties: false
description: Single dose log row. `takenAt` is non-null for confirmed intakes; `skipped:true` represents a
deliberately-missed dose (no inventory consumption).
description: Single dose log row — the FULL row shape both the intake POST (201/200) and the intake list GET return.
`takenAt` is non-null for confirmed intakes; `skipped:true` represents a deliberately-missed dose (no inventory
consumption).
CreateMedicationIntakeResponse:
type: object
properties:
@@ -24144,6 +24296,52 @@ components:
- data
- error
additionalProperties: false
ListMedicationIntakeEventsResponse:
type: object
properties:
data:
type: object
properties:
events:
type: array
items:
$ref: "#/components/schemas/MedicationIntakeEvent"
meta:
type: object
properties:
total:
type: integer
minimum: 0
maximum: 9007199254740991
limit:
type: integer
exclusiveMinimum: 0
maximum: 9007199254740991
offset:
type: integer
minimum: 0
maximum: 9007199254740991
required:
- total
- limit
- offset
additionalProperties: false
required:
- events
- meta
additionalProperties: false
error:
type: "null"
meta:
type: object
properties:
requestId:
type: string
additionalProperties: false
required:
- data
- error
additionalProperties: false
ListMedicationInventoryResponse:
type: object
properties: