Meetings
Staff meeting rooms and the WebRTC call signaling events.
Meeting rooms are persistent staff voice/video channels, scoped to the active clinic. Any clinic member can list, create, join, and delete rooms — calls are staff-to-staff. The REST endpoints manage only the room list; the live call (participants + media) is handled over the realtime Socket.io connection and is never persisted.
| Method | Path | Purpose |
|---|---|---|
GET | /api/meetings | List the clinic's rooms |
POST | /api/meetings | Create a room |
DELETE | /api/meetings/{id} | Delete a room |
GET | /api/meetings/events | Scheduled meetings you're part of |
POST | /api/meetings/events | Schedule a meeting |
DELETE | /api/meetings/events/{id} | Delete a scheduled meeting (creator only) |
All endpoints require a signed-in member with an active clinic.
Endpoints
Create a room
| Field | Type | Notes |
|---|---|---|
name | string | Required. 1–80 chars |
curl -b cookies.txt -X POST http://localhost:4000/api/meetings \
-H "Content-Type: application/json" \
-d '{ "name": "Morning huddle" }'Returns 201 with { id, name, createdAt }.
Schedule a meeting
POST /api/meetings/events creates a calendar entry; GET /api/meetings/events returns the
meetings where you are the creator or an invited participant (with participant display names
resolved). The creator is always added as a participant.
| Field | Type | Notes |
|---|---|---|
title | string | Required. 1–120 chars |
date | string | Required. YYYY-MM-DD |
time | string | Required. HH:mm |
participants | string[] | Invited staff user ids (max 50) |
Realtime call signaling
Calls use a WebRTC mesh: every participant holds one peer connection to each other participant, and media flows peer-to-peer. The server only relays SDP/ICE over the authenticated Socket.io connection and tracks who is in a room. A room is capped at 4 participants (mesh degrades beyond that; 5+ would need an SFU).
| Event | Direction | Payload |
|---|---|---|
call:join | client → server | roomId (ack returns { ok, peers } or { ok: false, reason: "full" | "not_found" }) |
call:peer-joined | server → clients | { socketId, userId, userName } |
call:signal | both | { to, signal } relayed as { from, signal } — signal carries an SDP offer/answer or an ICE candidate |
call:leave | client → server | roomId |
call:peer-left | server → clients | { socketId } |
call:invite | client → server | { roomId, toUserId } — rings a member |
call:invite | server → client | { roomId, roomName, fromName } — relayed to the invitee (also a meeting notification) |
call:presence | server → clinic | { roomId, count } — live room occupancy |
On call:join the newcomer receives the peers already present and initiates an offer to
each; existing peers answer. Disconnects emit call:peer-left to the room automatically.
Invites ring a member into a room (a toast with a Join action, plus a bell notification);
presence broadcasts let the room list show how many people are in each call.
Cross-network calls (symmetric NAT) also need a TURN server — a deployment add-on. The bundled signaling uses public STUN, which covers same-network and simple-NAT setups.