Przejdź do treści

Etap 4.6 — Mobile-parity API

The kiosk runs every flow through machine-API-key auth — the kiosk is the machine. For the future Android apps (courier app, employee app) we ship a parallel JWT-authenticated surface that drives the same backend state machine. This file lists every endpoint a mobile client would hit.

All endpoints are JWT-bearer auth (Keycloak smartbox realm). Per-shipment authorisation is enforced inside the handler — @Roles is too coarse to express "the user is the recipient of this specific shipment".

Lifecycle of a parcel from a courier's phone

``` courier POST /shipments → status=CREATED POST /shipments/:id/assign-locker → status=ASSIGNED, locker reserved POST /me/shipments/:id/open-locker → OPEN_LOCKER task fired on the machine (operator drops parcel, closes door) courier POST /me/shipments/:id/confirm-deposit → status=DEPOSITED, locker OCCUPIED

…notification to recipient (etap 7)…

recipient POST /me/shipments/:id/open-locker → OPEN_LOCKER task fired on the machine (recipient retrieves parcel, closes door) recipient POST /me/shipments/:id/confirm-pickup → status=PICKED_UP, locker FREE ```

All transitions are audited with actorType=USER, distinguishable from the kiosk machine flow (actorType=MACHINE) by their action prefix:

  • shipment.create (existing) — courier created
  • shipment.assign_locker (existing)
  • shipment.remote_openme/shipments/:id/open-locker
  • shipment.remote_confirm_deposit
  • shipment.remote_confirm_pickup

Endpoint reference

POST /shipments

Create a new shipment. Allowed for any authenticated user (employee, admin, courier). Generates trackingId + signed QR payload.

```json // request

// response 201 { "id": "", "trackingId": "SB-2026-XXXXXX", "status": "CREATED", "qrPayload": "...", "expiresAt": "2026-05-06T20:00:00Z" } ```

Audit: shipment.create.

POST /shipments/:id/assign-locker

Reserve the first FREE locker on a target machine for an existing CREATED shipment. Allowed for the creator or admin.

json // request { "machineId": "<uuid>" }

After this call the shipment is ASSIGNED and the locker is RESERVED. Audit: shipment.assign_locker.

POST /me/shipments/:id/open-locker

Issue an OPEN_LOCKER task on the assigned machine. The kiosk's agent (or future Modbus driver) picks the task up within 30 seconds and drives LockerHardware.open().

Authorisation rules:

Status Who can call
ASSIGNED (deposit) sender, courier, admin
DEPOSITED (pickup) recipient, admin
anything else rejected as 409 Conflict

json // response 201 { "ok": true, "taskId": "<uuid>", "lockerNumber": 7, "reason": "deposit" }

Audit: shipment.remote_open.

POST /me/shipments/:id/confirm-deposit

Sender / courier presses "I closed the door" in the mobile app. Flips ASSIGNED → DEPOSITED + locker OCCUPIED. Mobile counterpart of the kiosk's machine-key POST /shipments/:id/deposited.

json // response 200 — full Shipment row { "id": "...", "status": "DEPOSITED", "depositedAt": "...", ... }

Audit: shipment.remote_confirm_deposit.

POST /me/shipments/:id/confirm-pickup

Recipient presses "I took my parcel". Flips DEPOSITED → PICKED_UP + locker FREE.

Audit: shipment.remote_confirm_pickup.

POST /shipments/:id/cancel (existing)

Sender or admin cancels an in-flight shipment. Frees the locker if one was assigned.

GET /shipments?scope=mine&status=…&search=…&page=…&perPage=… (existing)

User's own shipments. Admin / courier with scope=all see every row, others see only theirs (created or received). Returns the paginated envelope {shipments, total, page, perPage}. Supported query params:

  • scopemine (default) or all (ADMIN / COURIER only).
  • status — repeatable; matches any of the listed statuses. Omitting it implies the active subset (CREATED, ASSIGNED, DEPOSITED).
  • search — ILIKE on tracking ID / recipient / sender (case-insensitive).
  • page — 0-indexed, default 0. perPage — one of 25, 50, 100, 250 (clamped server-side), default 25.

Notes for the Android client

  • Use server-issued OPEN_LOCKER tasks. Don't try to open the locker from the phone directly — the agent is the sole owner of LockerHardware. Mobile flow is: kick off open-locker → poll GET /shipments/:id until status flips (DEPOSITED for sender, PICKED_UP for recipient) → confirm via the matching endpoint when the user closes the door.
  • No offline mode on mobile. The kiosk's offline cache is a special case for the physical install on a wall. Mobile phones have networks; if there's no network the user just retries.
  • Audit / events fire identically. Notifications (real e-mail / SMS dispatch is queued for etap 8 — today NotificationWorker only audits "would-send" rows) will treat mobile-driven and kiosk-driven transitions the same way: the recipient's "your parcel is ready" message will go out on shipment.deposited regardless of who flipped it.