What to build
Add the slot creation endpoint with the central overlap-detection invariant, plus the "Add slot" form on the existing /doctor/slots page. Adjacent slots (new.endsAt == existing.startsAt, no gap) must be accepted; any time-overlap must reject with 409.
- Fill in
doctorContract with createSlot: body { startsAt, endsAt } ISO-8601 UTC, success response 201 with the created slot.
POST /doctors/me/slots runs in a single DB transaction using SELECT FOR UPDATE against the calling doctor's existing slots; overlap is new.startsAt < existing.endsAt AND new.endsAt > existing.startsAt; on overlap, transaction rolls back and the endpoint returns 409 { error: "SLOT_OVERLAP" }.
endsAt > startsAt is enforced both at the application validation layer (VR-001) and by the existing DB check constraint.
- Structured INFO log on every successful slot creation (acting doctor id, slot id).
- "Add slot" form on
/doctor/slots: Europe/Warsaw datetime-local pickers, converted to UTC ISO before sending; on success the list refreshes (TanStack Query invalidation); on 409 the form surfaces a "Slot overlaps with an existing slot" message; client validation also blocks endsAt <= startsAt before submit.
- Concurrency: integration test that fires two identical create requests via
Promise.all and asserts exactly one 201 and one 409.
Acceptance criteria
User stories covered
Blocked by
What to build
Add the slot creation endpoint with the central overlap-detection invariant, plus the "Add slot" form on the existing
/doctor/slotspage. Adjacent slots (new.endsAt == existing.startsAt, no gap) must be accepted; any time-overlap must reject with 409.doctorContractwithcreateSlot: body{ startsAt, endsAt }ISO-8601 UTC, success response 201 with the created slot.POST /doctors/me/slotsruns in a single DB transaction usingSELECT FOR UPDATEagainst the calling doctor's existing slots; overlap isnew.startsAt < existing.endsAt AND new.endsAt > existing.startsAt; on overlap, transaction rolls back and the endpoint returns 409{ error: "SLOT_OVERLAP" }.endsAt > startsAtis enforced both at the application validation layer (VR-001) and by the existing DB check constraint./doctor/slots:Europe/Warsawdatetime-local pickers, converted to UTC ISO before sending; on success the list refreshes (TanStack Query invalidation); on 409 the form surfaces a "Slot overlaps with an existing slot" message; client validation also blocksendsAt <= startsAtbefore submit.Promise.alland asserts exactly one 201 and one 409.Acceptance criteria
POST /doctors/me/slotswith a non-overlapping window returns 201 with the created slot infreestatus.POST /doctors/me/slotswithendsAt <= startsAtreturns 422 (validation rule VR-001).POST /doctors/me/slotsoverlapping an existing slot for the same doctor returns 409SLOT_OVERLAP.new.endsAt == existing.startsAt, ornew.startsAt == existing.endsAt) is accepted with 201.Promise.all) result in exactly one 201 and one 409./doctor/slotscreates a slot, shows it in the list, and surfaces an inline error on overlap.POST /doctors/me/slotsreturns 401 without JWT and 403 with a patient JWT.pnpm verifyexits 0.User stories covered
Blocked by