openvidu/openvidu-dashboard/openspec/specs/egress-api/spec.md

12 KiB
Raw Permalink Blame History

egress-api Specification

Purpose

Read access to egresses: the list and its summary numbers, every query backed by an index.

Requirements

Requirement: Egresses list endpoint

Edition: CE. GET /api/v1/egresses SHALL list egresses with the common query contract (from, to, tz, limit 1-100 default 25, opaque cursor), the filters status, type, room and q, the sort keys startedAt (default, index-backed; _id tie-breaker) and the ranked endedAt and duration, order (desc default, asc), and the list envelope { items, nextCursor, prevCursor, total? } with total under the DASHBOARD_LIST_TOTAL_MAX_RANGE rule.

Scenario: Default list

  • GIVEN 40 egresses started in the last 24 hours and 5 older ones
  • WHEN the client requests GET /api/v1/egresses with a valid token
  • THEN the response is 200 with the 25 most recently started egresses of the last 24 hours ordered by startedAt desc, nextCursor set, prevCursor: null and total: 40

Scenario: Cursor walk

  • GIVEN 60 egresses in range and limit=25
  • WHEN the client follows nextCursor twice and then prevCursor once
  • THEN the pages hold egresses 1-25, 26-50, 51-60 and 26-50 with no gaps or duplicates

Scenario: Unsupported sort key

  • WHEN the client sends ?sort=durationMs
  • THEN the response is 400 with code VALIDATION_ERROR and details.issues[0].path equal to ["sort"]

Requirement: Egress list item

Edition: CE. Each item of the egresses list SHALL be the public projection of the egress document: identity, room reference, type and source, lifecycle, times, an outputs summary and an error flag; it SHALL never carry the request, the output locations or URLs, the status history, schemaVersion or expiresAt.

Scenario: Fields of an item

  • WHEN an item is read
  • THEN it has egressId, roomSid?, roomName?, type (room_composite | web | participant | track_composite | track), sourceType (web | sdk), status (starting | active | ending | complete | failed | aborted | limit_reached), closedBy? (event | reconciler), startedAt, endedAt?, durationMs?, outputs { kinds, count }, hasError and errorCode? (the protocol's error code), and no request, statusHistory, error message or output location

Scenario: Outputs summary

  • GIVEN an egress with two file outputs and one stream output
  • WHEN it is returned
  • THEN outputs is { kinds: ["file", "stream"], count: 3 } with kinds in order of first appearance

Scenario: Active egress

  • GIVEN an egress whose status is starting, active or ending
  • WHEN it is returned
  • THEN endedAt, durationMs and closedBy are absent and hasError is false

Scenario: Web egress without room

  • GIVEN a web egress started with an empty room id
  • WHEN it is returned
  • THEN roomSid and roomName are absent and type is web

Requirement: Status and type filters

Edition: CE. The egresses list SHALL accept status as all (default) or a comma-separated subset of the seven egress statuses, and type as a comma-separated subset of the five egress types (absent means all), and SHALL reject an unknown value in either with 400 VALIDATION_ERROR.

Scenario: In-flight egresses

  • WHEN the client sends ?status=starting,active,ending
  • THEN every item has one of those three statuses and the time range still applies to startedAt

Scenario: Failed recordings of one type

  • WHEN the client sends ?status=failed&type=room_composite,participant
  • THEN every item has status: "failed" and type equal to room_composite or participant

Scenario: Unknown status

  • WHEN the client sends ?status=running
  • THEN the response is 400 with code VALIDATION_ERROR and details.issues[0].path equal to ["status"]

Requirement: Room filter

Edition: CE. The egresses list SHALL accept room as an exact room SID matching ^RM_[A-Za-z0-9]+$, SHALL combine it with status, type and the range, and SHALL reject any other value with 400.

Scenario: Egresses of one room

  • GIVEN room RM_abc with one complete and one active egress in range and other rooms with egresses
  • WHEN the client sends ?room=RM_abc
  • THEN exactly the two egresses of RM_abc are returned, ordered by startedAt desc

Scenario: Malformed room

  • WHEN the client sends ?room=demo-room
  • THEN the response is 400 with code VALIDATION_ERROR and details.issues[0].path equal to ["room"]

Requirement: Room-name prefix search and egress id lookup

Edition: CE. The egresses list SHALL treat q as a case-insensitive prefix over the room name when it has at least two characters, matched on the egress's own lower-cased room name roomNameLower with the range on startedAt and combined with status, type and room, SHALL reject a one-character q with 400, and SHALL treat a value matching ^EG_[A-Za-z0-9]+$ as an exact egress id lookup that ignores from, to, status, type, room and sort and returns at most one item.

  • GIVEN egresses in rooms Demo-Room, demo-2 and my-demo started in range
  • WHEN the client sends ?q=DE
  • THEN items holds the egresses of Demo-Room and demo-2 ordered by startedAt desc, and none of my-demo

Scenario: One character

  • WHEN the client sends ?q=d
  • THEN the response is 400 with code VALIDATION_ERROR, details.issues[0].path equal to ["q"] and a message stating the two-character minimum

Scenario: Exact id outside the range

  • GIVEN a complete egress EG_ab12cd started 40 days ago
  • WHEN the client sends ?q=EG_ab12cd&from=now-24h&status=active
  • THEN the response is 200 with that single egress in items, total: 1 and both cursors null

Scenario: Prefix matching many rooms

  • GIVEN 150 rooms whose name starts with load-, each with one egress started in range
  • WHEN the client sends ?q=load- and walks the cursor
  • THEN the pages hold the 150 egresses ordered by startedAt desc with no gaps or duplicates, total is 150, and no room cap applies

Scenario: Room of the egress no longer stored

  • GIVEN an egress of room demo-room started in range whose room document has expired
  • WHEN the client sends ?q=demo
  • THEN the egress is returned, since the match reads the egress document's roomNameLower

Scenario: Document without the lower-cased room name

  • GIVEN an egress document written without roomNameLower
  • WHEN the client sends a prefix of its room name
  • THEN the egress is not returned, and ?q=<its id> still returns it

Requirement: Time range on start time

Edition: CE. The egresses list SHALL apply from/to to startedAt for every sort key, order and filter, so that an egress that started before from is not listed, even while it is active, unless it is looked up by exact id.

Scenario: Long-running egress outside the range

  • GIVEN an active egress started 3 days ago
  • WHEN the client sends ?status=active&from=now-24h&to=now
  • THEN the egress is not in items, and ?q=<its id> returns it

Scenario: Ranked sort keeps the start range

  • GIVEN the same active egress
  • WHEN the client sends ?sort=duration&from=now-24h&to=now
  • THEN the egress is not in items

Requirement: Egresses summary endpoint

Edition: CE. GET /api/v1/egresses/summary SHALL accept from, to and tz and answer 200 with { active, startedInRange, byType, byEndStatus, totalDurationMs, approximate }, where active counts the current starting, active and ending egresses and the range figures are exact within DASHBOARD_LIST_TOTAL_MAX_RANGE and hour-aligned sums of the hourly rollups with approximate: true beyond it.

Scenario: Short range

  • GIVEN 3 in-flight egresses now, and 120 egresses started in the last 24 hours of which 100 are room_composite, 20 track, 110 complete, 7 failed, 3 still active, totalling 36 000 000 ms of ended duration
  • WHEN the client requests GET /api/v1/egresses/summary?from=now-24h&to=now
  • THEN the response is { active: 3, startedInRange: 120, byType: { room_composite: 100, track: 20 }, byEndStatus: { complete: 110, failed: 7 }, totalDurationMs: 36000000, approximate: false }

Scenario: Long range

  • GIVEN the default threshold of 7 days
  • WHEN the client requests GET /api/v1/egresses/summary?from=now-30d&to=now
  • THEN startedInRange, byType, byEndStatus and totalDurationMs equal the sums of egress.started, egress.byType, egress.endedByStatus and egress.durationMs of the global hourly rollups whose bucket starts inside the range, active is still the exact current count, and approximate is true

Requirement: Index-backed egress queries

Edition: CE and PRO. Every query of this capability SHALL run on an index of the shared contract (dashboard-ce/typings/contract/indexes.ts), or on _id for the hourly rollups the long-range summary reads: no winning plan contains a COLLSCAN, and totalDocsExamined is bounded by the page size plus the documented residual filters for the index-ordered pages and by the egresses started in the range for the ranked sorts.

Scenario: Explain plans on the generated database

  • GIVEN a database generated by the data generator at scenario M
  • WHEN the list (every filter combination and sort) and summary queries are explained
  • THEN each winning plan uses an index of the shared contract and none contains COLLSCAN

Scenario: Room-name prefix

  • WHEN the list with q set to a room-name prefix and no room is explained
  • THEN the plan uses roomNameLower_startedAt with the range on startedAt, and no query reads rooms

Scenario: Ranked sorts over 7 days

  • GIVEN a database generated at scenario M for 7 days
  • WHEN the list is explained with sort=endedAt and sort=duration, with and without status, room and q
  • THEN each plan scans the range index of its filters (startedAt_id, status_startedAt, roomSid_startedAt or roomNameLower_startedAt), totalDocsExamined is at most the egresses started in the range, and the p95 of a page is within 200 ms

Requirement: Ranked egress sorts

Edition: CE. The egresses list SHALL accept the ranked sort keys (api-conventions) endedAt, ranking endedAt (asOf when absent), and duration, ranking durationMs (asOf − startedAt when absent), over the egresses started in the range and matching the filters, when the range is within DASHBOARD_LIST_TOTAL_MAX_RANGE, and SHALL answer 400 with code SORT_RANGE_TOO_WIDE beyond it.

Scenario: Longest egresses first

  • GIVEN egresses started in the last 24 hours: one complete after 2 hours, one complete after 10 minutes and one active that started 45 minutes before asOf
  • WHEN the client sends GET /api/v1/egresses?sort=duration
  • THEN items holds the 2-hour egress, the active egress and the 10-minute egress, in that order

Scenario: Latest to end first

  • GIVEN egresses started in range: one ended 3 hours ago, one ended 5 minutes ago and one active
  • WHEN the client sends ?sort=endedAt&order=desc&status=all
  • THEN items holds the active egress (ranked at asOf), the egress ended 5 minutes ago and the egress ended 3 hours ago, in that order

Scenario: Ranked sort beyond the limit

  • GIVEN the default DASHBOARD_LIST_TOTAL_MAX_RANGE of 7 days
  • WHEN the client sends ?sort=duration&from=now-30d&to=now
  • THEN the response is 400 with code SORT_RANGE_TOO_WIDE

Scenario: Ranked cursor walk

  • GIVEN 60 egresses started in range, two of them active, and sort=endedAt&limit=25
  • WHEN the client follows nextCursor twice
  • THEN the pages hold egresses 1-25, 26-50 and 51-60 of the ranking at the first page's asOf, with no gaps or duplicates