Compare commits

...

266 Commits

Author SHA1 Message Date
CSantosM
94d9c7c50b backend: import disconnectFakeParticipants in analytics and webhook tests 2026-03-06 17:05:47 +01:00
CSantosM
c5bca6e133 backend: Enhances test reliability with active waiting
Replaces arbitrary `sleep()` calls in integration tests with explicit `wait-helpers`. These new helpers actively poll for specific conditions (e.g., room deletion, participant connection, recording status) directly from the database or LiveKit, rather than relying on fixed delays. This significantly reduces test flakiness and improves the accuracy of assertions.

Extracts LiveKit CLI interaction helpers (`joinFakeParticipant`, `disconnectFakeParticipants`, `updateParticipantMetadata`) into a dedicated `livekit-cli-helpers.ts` file for better organization and separation of concerns. Updates numerous integration tests to utilize the new waiting and LiveKit CLI helpers.
2026-03-06 16:38:12 +01:00
CSantosM
467ebf6d49 backend: add ASSISTANT_STATE_LOCK_TTL to internal config and update AiAssistantService to use it 2026-03-06 16:33:19 +01:00
juancarmore
8a8951c120 frontend: update participant moderation controls and badge handling in meeting component
Refactors participant moderation and badge display logic

Unifies participant badge handling to support multiple roles (owner, admin, moderator) and updates control visibility based on user permissions. Simplifies template context, centralizes moderation action checks, and refines role change logic for better maintainability and scalability of participant controls.
2026-03-06 11:44:39 +01:00
juancarmore
f70bd04497 Refactors participant role updates to use moderation actions
Unifies backend and frontend handling of participant role changes by shifting from direct role assignment to moderation actions and UI badges. Simplifies signal payloads, removes token/secret mechanics, and clarifies notifications for moderator promotions and removals. Improves maintainability and aligns with updated moderation model.
2026-03-06 11:41:15 +01:00
juancarmore
1223e3d53b feat: enhance room member token handling with participant metadata and moderation actions
Enhances token refresh with participant metadata support

Improves room member token generation to allow rebuilding token
metadata from current participant state in LiveKit, enabling
accurate permission and role handling after in-meeting upgrades
or downgrades. Adds support for in-meeting moderation actions
(promote/demote moderator) and updates token and context logic
to reflect dynamic role and permission changes for participants.
2026-03-06 11:39:28 +01:00
juancarmore
eca3acbcf0 backend: refactor assistant creation and cancellation to use participant identity from session service 2026-03-05 11:03:32 +01:00
juancarmore
9cf48fc49e backend: enhance room member token info handling to include participant identity in request session service 2026-03-05 11:03:03 +01:00
juancarmore
2453ce2760 frontend: refactor captions button logic to improve state management and prevent concurrent toggles 2026-03-04 18:44:30 +01:00
juancarmore
7607f134a0 Merge branch 'main' into feat/room-members-users 2026-03-04 18:17:05 +01:00
CSantosM
5045815a1c frontend: Redesigns Room Features wizard step UI
Overhauls the room configuration step, now titled "Room Features", to improve clarity and organization.

Transitions from a card-based layout to a categorized, list-style presentation, grouping features under sections like 'Security', 'Communication', and 'Experience'. Updates the HTML template and SCSS styling to reflect this new structure and removes the unused `MatCardModule`.

Adjusts the wizard state service to ensure the "Room Features" step is the initial active step when editing a room.
2026-03-03 14:36:40 +01:00
CSantosM
a81b6edf41 frontend: remove flex properties from step content in room access component 2026-03-03 14:22:01 +01:00
CSantosM
b775c37a86 frontend: add anonymous recording access toggle in recording configuration step 2026-03-03 14:05:32 +01:00
CSantosM
a829721ba5 frontend: Introduces Room Access step in room wizard
Replaces the 'Role Permissions' step with a comprehensive 'Room Access' step, centralizing all related configurations.

This new step allows users to define:
- General room access policies (anonymous moderator/speaker, registered users).
- Detailed role-based permissions for Moderator and Speaker.
- Specific room members to be added with their roles during room creation.

Pending members are now collected within the wizard and subsequently created using the RoomMemberService after the room is successfully established, including robust error handling for member creation.
2026-03-03 14:05:05 +01:00
juancarmore
e05dfdf001 backend: enhance room member token generation with support for recording access and update related tests 2026-03-03 10:02:39 +01:00
CSantosM
f49fd863b7 frontend: implement room deletion service with confirmation dialog and error handling 2026-03-02 18:44:24 +01:00
CSantosM
5b9fa3149c frontend: add room members list component with filtering and selection features
- Implemented RoomMembersListComponent for displaying room members in a Material Design table.
- Added SCSS styles for the room members list and its associated elements.
- Created AddRoomMemberComponent for adding new members to a room with role and permission configuration.
- Integrated user search functionality with autocomplete in the AddRoomMemberComponent.
- Updated RoomDetailComponent to utilize the new RoomMembersListComponent for displaying members.
- Defined routing for adding room members.
- Enhanced overall user experience with loading states and error handling.
2026-03-02 18:44:24 +01:00
CSantosM
fe71d07242 frontend: update recording detail layout and enhance video player integration 2026-03-02 18:44:24 +01:00
juancarmore
63d72c994b refactor: rename anonymous room access to access across all codebase
- Updated the MeetRoom interface to replace anonymous access configuration with a unified access configuration.
- Refactored RoomService to handle access configuration for both anonymous and registered users.
- Modified tests to reflect changes in access configuration structure.
- Updated frontend components to use the new access configuration for meeting URLs and permissions.
- Ensured backward compatibility by adjusting API endpoints and request/response types.
2026-03-02 17:37:25 +01:00
juancarmore
c563860758 Improve copilot instructions 2026-03-02 12:24:45 +01:00
juancarmore
43181bd068 backend: refactor global configuration methods to support partial updates and improve field retrieval 2026-02-27 10:34:46 +01:00
juancarmore
340d53066c backend: enhance user repository and service to support partial updates and selective field retrieval 2026-02-27 09:39:26 +01:00
juancarmore
e0d811237b backend: enhance room member repository and service to support partial updates and selective field retrieval 2026-02-27 09:16:02 +01:00
juancarmore
db279faee4 backend: update deleteRecording method to return void and improve hasRoomRecordings query efficiency 2026-02-26 22:07:37 +01:00
juancarmore
59c464387b backend: add partial update method for recordings and refactor update logic 2026-02-26 21:57:59 +01:00
juancarmore
54bb06adfd backend: implement atomic update paths for recording and room repositories 2026-02-26 19:51:42 +01:00
juancarmore
2572fd3960 backend: update room retrieval to use selective fields for efficiency 2026-02-26 18:37:48 +01:00
juancarmore
37023fe077 backend: refactor code to use partial updates for room properties 2026-02-26 17:50:46 +01:00
juancarmore
3a8e5e21be backend: update room repository methods for better handling of partial room data 2026-02-26 17:49:15 +01:00
juancarmore
eaf021f1dc test: add unit tests for MeetRecordingInfo, MeetRoom, and MeetRoomMember fields coverage 2026-02-26 14:11:16 +01:00
juancarmore
22a42e4c63 test: add unit tests for document-only fields in various schemas 2026-02-26 14:10:56 +01:00
juancarmore
8e5c07710b backend: add document-only fields and types for various schemas and repositories 2026-02-26 14:10:42 +01:00
juancarmore
09ac408f32 backend: enhance replaceOne method for improved addition of document only fields to replacement domain object 2026-02-26 14:10:07 +01:00
CSantosM
10a9982a57 frontend: update label and placeholder for name input in create user form 2026-02-25 18:18:36 +01:00
CSantosM
6592dac7fc frontend: update navigation logic for room cancellation and updates in RoomWizardComponent 2026-02-25 15:28:15 +01:00
juancarmore
b2380ec12b fix: replace update method with replace for room data consistency in setupTestUsersForRoom 2026-02-24 20:42:20 +01:00
juancarmore
78edf51842 test: replace update method with replace for room status consistency in GC tests 2026-02-24 20:39:19 +01:00
juancarmore
cae5a58568 backend: integrate schema versioning into create methods across repositories 2026-02-24 20:17:14 +01:00
juancarmore
cdbfd8c968 backend: update mongoose to version 9.2.2 and adjust query filter types in repository and migration service 2026-02-24 20:16:40 +01:00
juancarmore
8ac2631e25 backend: replace update methods with replaceOne for consistency across repositories 2026-02-24 19:50:39 +01:00
juancarmore
161f42f83c backend: enhance document update and replacement methods in BaseRepository with partial updates and safety checks 2026-02-24 19:49:52 +01:00
CSantosM
d51c5ae0c3 frontend: enhance API key management section with documentation link and styling improvements 2026-02-24 16:46:25 +01:00
CSantosM
4794b30ff9 frontend: add role permissions step in room wizard 2026-02-24 14:29:15 +01:00
Carlos Santos
177134d2a6
Updates to Express v5 (#7)
Updates the Express dependency to version 5.2.1 and its corresponding types.

This change also adapts the request validator middleware to extract validated query parameters and store them in `res.locals` instead of modifying `req.query` to align with Express v5's intended usage and prevent potential conflicts.

Includes a fix for a server startup error, logging the error and exiting the process, and adds a type definition file for Express locals.
2026-02-24 11:54:43 +01:00
CSantosM
78060c0cdf Merge branch 'feat/node_v24' into feat/room-members-users 2026-02-24 11:21:15 +01:00
CSantosM
fcf21468c2 Updated to Node.js v24.13.1 2026-02-24 11:02:06 +01:00
juancarmore
02a9774d72 backend: ensure collection exists before index synchronization in MigrationService 2026-02-23 16:18:12 +01:00
CSantosM
210e5fe3f5 backend: Updates backend dependencies
Updates several backend dependencies to their latest versions.
This includes updates to AWS S3 client, Azure storage blob,
Google Cloud Storage, and body-parser, potentially bringing
bug fixes, performance improvements, and new features.
2026-02-23 15:25:00 +01:00
CSantosM
e5ebcbaab0 frontend: Adds users management components
Implements users list, reset password dialog and create user page.
This enhancement provides the necessary UI components and services
to manage users within the application, including listing,
creating, and resetting passwords.

The user list component supports filtering, sorting, and bulk
actions. The reset password dialog allows administrators to reset
user passwords. The create user page enables the creation of new
user accounts with specific roles.
2026-02-23 15:11:03 +01:00
CSantosM
1f603a683d backend: update profile layout and styles for improved user details display 2026-02-23 15:11:03 +01:00
juancarmore
f1b9f1c6e3 backend: add testing guidelines to migrations README 2026-02-23 13:49:32 +01:00
juancarmore
a25569b4f7 test: add migration tests for global config, recordings, rooms, and users 2026-02-23 13:49:06 +01:00
juancarmore
d476b9c715 backend: change log level from warn to error for migration failures 2026-02-23 13:48:33 +01:00
juancarmore
5cbd05be11 backend: refactor test cleanup to sequentially delete rooms and recordings 2026-02-23 10:14:51 +01:00
juancarmore
248015fc06 backend: add index migration functionality to MigrationService 2026-02-23 10:14:22 +01:00
juancarmore
803e4b6704 backend: enhance migration registries and improve type handling in migration service 2026-02-22 13:42:37 +01:00
juancarmore
696df4a7a2 backend: update convertToDTO method to explicitly ignore sensitive user fields 2026-02-22 13:41:51 +01:00
juancarmore
27d4249c57 backend: simplify repository classes by removing generic types and enhancing type handling 2026-02-22 13:41:40 +01:00
juancarmore
83583259ba backend: enhance BaseRepository to improve type handling 2026-02-22 13:40:08 +01:00
juancarmore
a9818f0cdc backend: streamline migration models by removing Document extension and integrating schemaVersion for migration tracking 2026-02-22 13:39:35 +01:00
juancarmore
0777f299d9 backend: update schema versions and implement user, room, and global config migrations 2026-02-20 11:23:12 +01:00
juancarmore
a69c8d0239 backend: add room member migrations and update migration registry 2026-02-20 10:15:13 +01:00
juancarmore
7f784af12b backend: update migration README with detailed steps for adding new migrations and schema changes 2026-02-20 09:42:03 +01:00
juancarmore
dcf6b5a84e frontend: fetch room name from room service if no recordings are available 2026-02-20 09:41:31 +01:00
juancarmore
99b9f56959 frontend: update room link tooltip and router link in recording detail component 2026-02-20 09:41:16 +01:00
juancarmore
41438e20fa frontend: update recording and room components to use typed sort fields 2026-02-20 09:41:02 +01:00
CSantosM
b695b5f117 frontend: simplify recording detail layout by removing player card wrapper 2026-02-19 17:20:57 +01:00
CSantosM
66e7a8b038 frontend: update sorting implementation to use SortOrder enum across recording and room components 2026-02-19 17:19:00 +01:00
CSantosM
7976c99654 frontend: add user profile page with password management and admin actions
- Implemented profile component with loading state, user details, and password change functionality.
- Added form validation for password fields and error handling.
- Included admin actions for role management, password reset, and user deletion.
- Updated console navigation to include a link to the profile page.
- Refactored user component to remove admin password management UI.
2026-02-19 17:08:15 +01:00
juancarmore
491c3392ce backend: update field selection to use arrays instead of comma-separated strings across repositories and services 2026-02-19 14:05:25 +01:00
juancarmore
66978509b1 backend: add fields validation schema for RoomMember and update filters to use typed array 2026-02-19 14:05:25 +01:00
juancarmore
c6742fd5eb openapi: rename extraFields and X-ExtraFields files and update references in room API 2026-02-19 14:05:25 +01:00
juancarmore
761b205ed0 openapi: add sort field parameters for users, recordings, room members, and rooms 2026-02-19 14:05:25 +01:00
juancarmore
a4d368d856 backend: update sorting fields and order in recording, room, room member, and user schema validators 2026-02-19 14:05:25 +01:00
juancarmore
9e85174b69 typings: enhance sorting capabilities in response typings 2026-02-19 14:05:25 +01:00
juancarmore
2c216f53c3 Update WebComponentDocGenerator to reference new typings for events, commands, and properties 2026-02-19 14:05:25 +01:00
juancarmore
bc70759837 refactor(types): reorganize typings by category
Restructure TypeScript typings to improve organization and maintainability.
Group definitions by type: database entities, request types, and response types, making the structure clearer and easier to navigate.
2026-02-19 14:05:25 +01:00
CSantosM
87645efb3c frontend: Update room navigation to remove detail from route path 2026-02-19 13:49:46 +01:00
CSantosM
b9550aced9 frontend: Implement recording detail page with loading and error states 2026-02-18 17:48:23 +01:00
CSantosM
333bd0e92f frontend: Updates FeatureCalculator documentation for clarity 2026-02-18 17:37:12 +01:00
juancarmore
92b96f9a34 frontend: reorder RoomFeature properties 2026-02-18 16:49:29 +01:00
juancarmore
c00c255533 frontend: update activities panel button logic to include view recordings condition 2026-02-18 16:48:30 +01:00
juancarmore
c27cc49985 frontend: improve recording action buttons with consistent attributes and structure 2026-02-18 16:47:54 +01:00
CSantosM
dec8307cfb frontend: Refactors room feature service location
Moves the RoomFeatureService from the shared directory to the rooms domain directory.
This change improves the modularity and organization of the codebase by grouping
domain-specific services within their respective domains. It also updates
imports to reflect the new location of the service.
2026-02-18 13:06:31 +01:00
CSantosM
2df238f0cc frontend: Decouples UI features from moderation role
Moves UI feature flags from `canModerateRoom` to granular `meetingUI` properties.
This change provides more precise control over UI elements,
allowing for customization based on specific feature flags rather than
solely relying on the user's moderation status.

Refactors components to utilize the new `meetingUI` computed signal
for determining the visibility of UI elements such as share link, layout
selector, captions controls, and leave menu. Also, the logic to enable theme
selector, start/stop recording, view recordings, join meeting and kick
participants has been included.

Updates features calculation to properly include room config
and permissions to show or hide features

This improves flexibility in managing the user interface based on a
combination of room configuration and user permissions.
2026-02-18 13:06:31 +01:00
juancarmore
583fdbe608 backend: revert schema versions to initial state 2026-02-17 18:06:00 +01:00
juancarmore
71c08dee8c Merge branch 'main' into feat/room-members-users 2026-02-17 17:56:43 +01:00
CSantosM
bfe97395d0 frontend: Decouples room feature service from global config
Removes direct dependency of the room feature service on the global config service and room member contexts.

The global configs and room member data are now observed through signals, ensuring reactive updates and decoupling of concerns.

This change allows for a more streamlined and testable architecture.
2026-02-17 16:56:39 +01:00
CSantosM
9c187be2b8 webcomponent: remove unnecessary MeetRecordingAccess references in room config tests 2026-02-17 15:19:30 +01:00
CSantosM
f780fac60d backend: add sorting parameters to getRooms API calls in tests 2026-02-17 14:47:49 +01:00
CSantosM
fd13af8e2b backend: simplify jest command line by removing unnecessary node options 2026-02-17 14:47:34 +01:00
CSantosM
6c38d615ea backend: add enum values for sortField parameter in API 2026-02-17 14:47:22 +01:00
CSantosM
1856201b87 backend: refactor GET Recording fields filtering tests to remove redundant setup 2026-02-17 12:53:55 +01:00
CSantosM
65674e164d backend: separated room and recording deletion for avoiding race condition deleting recordings 2026-02-17 11:54:37 +01:00
CSantosM
f42d91ec74 backend: remove allowUserCreation from security config tests for simplification 2026-02-17 11:28:30 +01:00
CSantosM
4708181628 backend: update expectObjectFields to use expect.arrayContaining for better key validation 2026-02-17 11:08:30 +01:00
CSantosM
874538a8b7 backend: Adds fields filtering tests to recording API
Enables filtering of recording API responses (start, get, list, stop)
using both the `fields` query parameter and the `X-Fields` header.
The feature allows clients to request only specific fields in the
response, improving performance and reducing payload size.
When both are provided, values are merged (union of unique fields).
2026-02-16 19:02:00 +01:00
CSantosM
9dc4834edd backend: implement X-Fields header for recording responses, enhancing data retrieval flexibility 2026-02-16 14:09:14 +01:00
CSantosM
6ca1ace61e backend: add encoding parameter to recording response examples 2026-02-16 12:24:40 +01:00
CSantosM
61b10befec backend: update API documentation for room parameters and responses, enhancing clarity and consistency 2026-02-16 11:20:50 +01:00
juancarmore
14bf154390 frontend: remove unused page URL parameter from room access validation guards 2026-02-16 11:10:03 +01:00
juancarmore
d55cf45fa5 frontend: include redirect URL removal from session storage in clearRoomSessionGuard 2026-02-16 10:50:16 +01:00
juancarmore
4f96192cc6 frontend: streamline comments for computed signals in MeetingContextService 2026-02-16 10:46:34 +01:00
juancarmore
2566219dd9 openapi: update success response messages for room deletion scenarios 2026-02-16 10:46:19 +01:00
CSantosM
c7e7674161 backend: update parameter references in rooms.yaml 2026-02-16 09:53:23 +01:00
CSantosM
9c963ccf81 refactor: remove unused import from room.helper.ts 2026-02-16 09:43:39 +01:00
CSantosM
5cf2e74eab backend: implement partial update for room repository and adjust service calls accordingly 2026-02-16 09:36:48 +01:00
CSantosM
6d3d19ccd0 frontend: refactor computed signals in MeetingContextService for clarity and add captions status 2026-02-13 16:59:33 +01:00
CSantosM
d155846708 backend: enhance room deletion API to support field filtering
- Added support for `fields` and `extraFields` parameters in `deleteMeetRoom` and `bulkDeleteMeetRooms` methods to allow clients to specify which fields to include in the response.
- Updated the `RoomService` to handle field filtering logic when deleting rooms, ensuring only requested fields are returned in the response.
- Enhanced integration tests to verify that the API correctly filters responses based on `fields` and `extraFields` query parameters and headers during room deletion operations.
- Created new test cases to validate the behavior when some rooms fail to delete due to active meetings, ensuring the response includes the correct fields.
- Refactored existing tests to accommodate the new field filtering functionality and ensure comprehensive coverage.
2026-02-13 16:45:38 +01:00
CSantosM
5c80387244 backend: enhance room API tests to validate extra fields in responses 2026-02-13 16:45:38 +01:00
CSantosM
f6dd80e8eb backend: update success response schema for room deletion to improve clarity and structure 2026-02-13 16:45:38 +01:00
CSantosM
28f4bf6b38 backend: add success responses for room deletion and scheduling 2026-02-13 16:45:38 +01:00
CSantosM
b8e7baf705 Renames expand to extraFields in room API
Updates the room API to use `extraFields` instead of `expand` for including additional data in responses.

This change improves clarity and consistency in the API design.
It also simplifies the filtering logic by explicitly differentiating between
base fields (controlled by `fields`) and extra fields (controlled by `extraFields`).

The changes include:
- Renaming the query parameter and header
- Updating the validation schemas
- Adjusting the filtering logic in the controller and service layers
- Updating the frontend components and services
2026-02-13 16:45:38 +01:00
CSantosM
c0b77314b5 Refactors feature flag structure for clarity
Organizes feature flags into sub-interfaces for media, UI, permissions, and appearance, enhancing code readability and maintainability.

This change improves the structure of the `RoomFeatures` interface by grouping related flags, making it easier to manage and understand the different categories of features within the application.
2026-02-13 16:44:33 +01:00
juancarmore
b23445d063 frontend: enhance clear room session guard to include meeting context cleanup 2026-02-13 14:00:30 +01:00
juancarmore
cf64838c9b frontend: simplify navigation back to room by removing room secret requirement 2026-02-13 13:59:53 +01:00
juancarmore
d115760e03 frontend: prevent event propagation on more actions button in rooms list 2026-02-13 13:59:31 +01:00
juancarmore
fedb9c2b44 frontend: update room link copying methods to use anonymous access URLs 2026-02-13 13:59:20 +01:00
juancarmore
ea5ef99773 frontend: reorder app initializers for room member error handling precedence 2026-02-13 13:58:47 +01:00
juancarmore
01c5b695d9 backend: update base path handling in room repository and helper 2026-02-13 12:30:39 +01:00
juancarmore
5ca46e59d8 Merge branch 'main' into feat/room-members-users 2026-02-13 12:10:00 +01:00
juancarmore
90edd756a2 frontend: update navigation paths to include leading slashes for consistency 2026-02-13 12:03:10 +01:00
juancarmore
4ebbaa47ef frontend: include 'roomId' and 'anonymous' fields in room data retrieval for lobby service 2026-02-12 19:05:50 +01:00
juancarmore
3f108cd161 frontend: optimize participant name handling to use getRawValue for disabled form controls 2026-02-12 19:05:13 +01:00
juancarmore
8a7989478a frontend: enhance participant name handling and disable input based on context 2026-02-12 18:37:42 +01:00
juancarmore
ab907bb0e8 frontend: refactor room member context and related services for improved state management 2026-02-12 18:37:27 +01:00
juancarmore
599a744302 frontend: refactor meeting components and services for improved readability and performance 2026-02-12 13:53:20 +01:00
juancarmore
4c864b193f frontend: set meeting context to ended by self when ending meeting 2026-02-12 11:01:59 +01:00
juancarmore
beb5571983 frontend: remove unsued MeetingShareLinkOverlay component and its associated files 2026-02-12 11:01:36 +01:00
juancarmore
04563a009f frontend: update recordings URL handling and navigation logic 2026-02-12 11:01:17 +01:00
juancarmore
0e77aba428 frontend: refactor MeetingLobby and MeetingContext services to improve state management and reactivity 2026-02-12 10:32:05 +01:00
juancarmore
28bfa609d8 frontend: update back button text logic to use user authentication instead of role 2026-02-12 08:10:59 +01:00
juancarmore
9db0e8b29e frontend: add clearRoomSessionGuard to remove room session data on console route entry 2026-02-12 08:08:48 +01:00
juancarmore
9a2597a997 frontend: enhance E2EE key handling and storage management 2026-02-12 08:07:21 +01:00
juancarmore
fb4bdbfcfb frontend: refactor error handlers to utilize dedicated header provider services for token management 2026-02-12 08:04:05 +01:00
juancarmore
8e8e2670c4 frontend: move leave redirect URL handling to NavigationService and clean up extractParams utility 2026-02-12 08:03:40 +01:00
juancarmore
01f21a724f frontend: implement HTTP header providers for authentication and room member tokens 2026-02-10 15:11:18 +01:00
juancarmore
e45aa91d90 frontend: rename RoomMemberContextService references and implement RoomMemberContextAdapter for improved context management 2026-02-10 12:47:51 +01:00
CSantosM
7cdfdf20f9 frontend: Updated service names for better maintainability and comprehensibility 2026-02-10 12:03:41 +01:00
juancarmore
dbca91f0c3 backend: update base URL construction in MeetRoomHelper to use getBasePath utility 2026-02-10 11:17:46 +01:00
juancarmore
38b3db6171 backend: update permission error handling in RoomMemberService to throw errorInsufficientPermissions 2026-02-10 11:17:27 +01:00
juancarmore
fd675573fc frontend: update meeting URL handling to use access URL and improve error handling in lobby service 2026-02-10 11:17:13 +01:00
juancarmore
ee55f02aaa frontend: update query parameter handling to support array values in recording, room member, room, and user services 2026-02-10 11:16:40 +01:00
juancarmore
7099012317 frontend: update error messages and navigation error reasons 2026-02-10 11:11:19 +01:00
juancarmore
9f46d03646 frontend: move room and recording parameter extraction guards to its corresponding domain folder and refactor code 2026-02-10 11:10:35 +01:00
CSantosM
5bb9a2f3e1 frontend: add room members and recordings tabs with loading states and actions 2026-02-09 18:09:23 +01:00
CSantosM
6c4adfeaaa frontend: refactor room status handling with RoomUiUtils and improve room detail display 2026-02-09 17:22:45 +01:00
CSantosM
35e727cbd0 frontend: add room detail page with loading state and actions 2026-02-09 16:46:05 +01:00
CSantosM
0e37d8cc09 frontend: remove recording access selection logic from recording config component 2026-02-09 15:16:21 +01:00
CSantosM
1aa6c8a383 frontend: Show profile button and update icon division styles in console navigation 2026-02-09 15:00:06 +01:00
CSantosM
19e7c48fd0 test: Update field filtering assertions in room creation tests 2026-02-09 14:36:25 +01:00
CSantosM
9441343f45 typings: Update import paths to include file extension 2026-02-09 13:25:09 +01:00
CSantosM
eacd629006 Adds room response options
Introduces response options for room-related API calls, allowing clients to specify which fields to include and which properties to expand.

This change provides more control over the data returned by the API,
reducing payload size and improving performance.
It also fixes an issue where the frontend was not able to request only the fields needed for specific components.
2026-02-09 13:16:58 +01:00
CSantosM
1c85eaa364 Refactors types location for backend models
Moves shared type definitions from backend models to the `typings` package.

This change centralizes type definitions, improving code maintainability
and consistency across the project. It removes duplicated type
definitions in backend and uses shared types from `typings` package
instead.
2026-02-09 13:16:58 +01:00
CSantosM
af6b5cab28 backend: Applies permission filtering to room data
Implements permission-based filtering to restrict access to sensitive room information.

This ensures that only authorized users can view specific fields based on their assigned permissions.
2026-02-09 13:16:58 +01:00
CSantosM
733665b49b backend: rename permission options for clarity and consistency in room API 2026-02-09 13:16:58 +01:00
CSantosM
80ce1a3efd backend: Improves data fetching efficiency
Refactors repository methods to accept an array of fields
instead of a comma-separated string, optimizing data retrieval
and reducing unnecessary string manipulation. Also, modifies
services and validators to use array of fields instead of strings.
2026-02-09 13:16:58 +01:00
CSantosM
d4a87f8a45 Enables response control via headers
Adds functionality to control the room creation and retrieval responses
using the `X-Fields` and `X-Expand` headers.

- `X-Fields` allows clients to specify which fields to include in the
  response, optimizing bandwidth usage.
- `X-Expand` allows clients to request the full data of expandable
  properties, such as `config`, avoiding subsequent GET requests.

This change introduces new request validators, service methods, and
helper functions to handle the header logic and process the room objects accordingly.
2026-02-09 13:16:58 +01:00
CSantosM
ecef2844a0 Adds filtering and expansion to room/recording APIs
Introduces filtering capabilities for room and recording list endpoints.

This allows users to query rooms and recordings based on various criteria such as name, status, and other relevant properties.
It also implements expansion support for room properties, enabling retrieval of full object details instead of stubs.

The changes include:
- Defines data models for room and recording filter requests.
- Updates Zod schemas for robust input validation.
- Implements logic for collapsing and expanding room properties, improving API response structure and efficiency.
2026-02-09 13:16:58 +01:00
juancarmore
336a6751c2 backend: remove allowUserCreation field from authentication config Mongoose schema 2026-02-09 10:04:00 +01:00
juancarmore
70ca7a0fa9 backend: remove currentParticipantIdentity from room member schema and refactor RoomMemberService to use meberId as participant identity when joining a meeting. Update related tests 2026-02-09 10:03:06 +01:00
juancarmore
f61fa6183c frontend: move room member related services to dedicated model directory. Implement RoomMemberService for managing room members API 2026-02-07 00:00:45 +01:00
juancarmore
ded3c37ab2 frontend: add users management component, service and routes 2026-02-06 13:57:20 +01:00
juancarmore
14c64fdd75 frontend: move room recordings component to recordings model 2026-02-06 13:56:27 +01:00
juancarmore
eb5144291a frontend: remove unused About component from console pages 2026-02-06 13:54:48 +01:00
juancarmore
6c3d87c8bf fix(frontend): update app to match backend API changes and restore build
Refactored frontend code to align with recent backend API updates.
Adjusted endpoints, data models, and related logic to ensure successful compilation
2026-02-06 13:53:06 +01:00
juancarmore
ae4217a4d4 backend: remove allowUserCreation from AuthenticationConfig and update related logic 2026-02-06 13:42:48 +01:00
CSantosM
85e4a5b8a6 Adds expandable properties to room responses
Implements expandable properties for room responses to reduce payload size.

Introduces an `expand` query parameter to control which complex properties, like `config`, are included in the response. By default, these properties are replaced with a stub containing a HATEOAS link to fetch the full data.

This change optimizes network bandwidth and improves API performance by preventing unnecessary data transfer, especially when clients only need a subset of room details.
2026-02-05 13:52:24 +01:00
juancarmore
b78744b8b6 ci: update Room Management API tests to include Room Members 2026-02-05 12:27:31 +01:00
juancarmore
335ab30a48 test: add integration tests for Room Members API 2026-02-05 12:22:10 +01:00
juancarmore
f01ec31c1b test: update room member token response validation and permissions handling 2026-02-05 12:21:37 +01:00
juancarmore
18426e2111 test: update webhook config references and improve request helpers
- Renamed `getWebbhookConfig` and `updateWebbhookConfig` to `getWebhookConfig` and `updateWebhookConfig` respectively for consistency.
- Updated integration tests to reflect the new function names.
- Refactored request helper methods to use `getFullPath` for API endpoint construction.
- Removed unnecessary parameters in `stopRecording` calls across various tests.
- Cleaned up test scenarios by removing redundant room deletion logic.
- Ensured proper handling of recording states in tests to avoid race conditions.
2026-02-05 12:19:46 +01:00
juancarmore
27a6064b61 backend: refactor room member token creation to use MeetRoomMemberTokenOptions interface 2026-02-05 12:19:46 +01:00
juancarmore
b3ab245dff Centralize Prettier configuration to enforce consistent formatting across all subprojects 2026-02-05 12:19:46 +01:00
CSantosM
8af5759e4d typings: Add comments in recording.model 2026-02-04 17:41:53 +01:00
juancarmore
07ac5b91c9 Merge remote-tracking branch 'origin/main' into feat/room-members-users 2026-02-03 13:16:40 +01:00
juancarmore
6e225fe265 test: add integration tests for room member creation and validation 2026-02-03 11:36:07 +01:00
juancarmore
d252784a39 test: add tests for room member token invalidation scenarios 2026-02-02 17:18:57 +01:00
juancarmore
3df0c54004 backend: add 'iat' timestamp to token metadata and update related validation logic 2026-02-02 17:18:37 +01:00
juancarmore
73e7a1ece7 backend: enhance room member token validation to check for updated permissions 2026-02-02 14:14:40 +01:00
juancarmore
70d51e21a6 backend: add timestamps for permissions updates in room member and room schemas 2026-02-02 14:14:29 +01:00
juancarmore
268a6f9709 backend: add error handling for disabled anonymous access in room member service 2026-02-02 13:14:19 +01:00
juancarmore
21f4563202 test: add integration tests for user management API 2026-02-02 12:41:30 +01:00
juancarmore
c561cf9bcd test: enhance change password and user profile tests with new scenarios and validations 2026-01-30 17:27:21 +01:00
juancarmore
54c2c79ccb test: streamline authentication request helpers and update related tests 2026-01-30 17:26:44 +01:00
juancarmore
cdbb30fc2a test: add comprehensive token validation tests for access and room member tokens 2026-01-30 13:04:24 +01:00
juancarmore
993681395c test: enhance authentication API tests with user role validations and token management 2026-01-30 10:21:32 +01:00
juancarmore
ad3e0b81e5 test: update authentication methods to return access and refresh tokens 2026-01-30 10:21:08 +01:00
juancarmore
68477d8ad3 backend: enhance access and refresh token management with new metadata structure and validation 2026-01-30 10:19:53 +01:00
juancarmore
1e1d66ae11 test: add security tests for room members API 2026-01-29 13:26:10 +01:00
juancarmore
6a350b07a5 test: enhance analytics, API key, global config, meeting, and user API security tests with user role and permissions validations 2026-01-28 18:04:11 +01:00
juancarmore
0a5852d89a test: enhance recording API security tests with user role and permissions validations 2026-01-28 16:12:04 +01:00
juancarmore
7ff040864f test: enhance test scenarios and include more cases in room API security tests 2026-01-28 16:10:45 +01:00
juancarmore
11f7ac1401 backend: streamline recording access authorization and improve room filtering logic 2026-01-28 16:06:46 +01:00
juancarmore
1188255210 backend: enhance room member token handling for external member IDs and validate secrets 2026-01-28 16:04:59 +01:00
juancarmore
2e7cbeb96a backend: persist effective permissions in room member documents instead of compute them on retrieval. Update permissions of all room members when updating room roles permissions 2026-01-28 16:03:31 +01:00
juancarmore
89e7d5db88 test: enhance room API security tests with user role validations 2026-01-26 18:25:15 +01:00
juancarmore
c1a43af260 test: enhance test scenarios with user and room member management interfaces 2026-01-26 18:24:33 +01:00
juancarmore
987085a466 test: add user and room member management helpers in request-helpers 2026-01-26 18:23:35 +01:00
juancarmore
e0e2fc2a44 backend: update token and role validator to allow access to change-password and me endpoints when password change is required 2026-01-26 18:23:02 +01:00
juancarmore
fb4e7a022c backend: update login schema and controller to use userId instead of username 2026-01-26 18:22:23 +01:00
juancarmore
503db6c2cb test: update test descriptions to reflect room member token usage for recording access 2026-01-23 19:15:03 +01:00
juancarmore
e580843b3a backend: reorder middlewares in room and meeting routes 2026-01-23 19:14:34 +01:00
juancarmore
84a0b2ac6e backend: streamline room and recording middlewares, enhance permission checks and error handling 2026-01-23 19:13:51 +01:00
juancarmore
23a25c1c3d openapi: add error response for empty ZIP download and update recordings and rooms notes 2026-01-23 19:12:48 +01:00
juancarmore
7c4b5c6724 backend: streamline recording access validation and error handling for ZIP downloads and bulk delete 2026-01-23 19:10:56 +01:00
juancarmore
456e890ffe openapi: add user role update endpoint and request/response schemas 2026-01-22 11:39:26 +01:00
juancarmore
de0674d82c backend: implement user role update functionality 2026-01-22 11:38:40 +01:00
juancarmore
7d7f66edf3 backend: prevent admins to delete their own account or root admin user account 2026-01-22 10:55:03 +01:00
juancarmore
8ab8007c1d test: update security tests to use admin login for authentication 2026-01-21 19:57:09 +01:00
juancarmore
b2488544e3 tests: refactor API tests to reflect backend code changes
- Updated the `generateRoomMemberToken` function to use `joinMeeting` instead of `grantJoinMeetingPermission` for clarity.
- Changed test descriptions to reflect the new parameter names and improved readability.
- Removed unnecessary imports and cleaned up tests related to recording access configurations.
- Updated validation error messages for better clarity in the API responses.
- Refactored security configuration tests to align with the new authentication structure.
- Removed deprecated tests for room member roles.
- Adjusted user profile tests to reflect changes in the response structure.
2026-01-21 19:57:09 +01:00
juancarmore
2bc1d02620 backend: enhance error handling and validation in room and recording access middleware 2026-01-21 19:57:09 +01:00
juancarmore
35498a5854 backend: enhance permission checks and error handling in room and recording services 2026-01-21 19:57:09 +01:00
juancarmore
086e325551 backend: enhance getMeetRoom method to include permission checks for sensitive properties 2026-01-21 19:57:09 +01:00
juancarmore
136a422fb6 backend: refactor downloadRecordingsZip to simplify recording ID handling 2026-01-21 19:57:09 +01:00
juancarmore
086f60d60a backend: enhance deleteMany method to allow non-failure on empty results in RoomMemberRepository 2026-01-21 19:57:09 +01:00
juancarmore
8d255bd051 backend: enforce required oauthProviders in AuthenticationConfig and update related schemas 2026-01-21 19:57:09 +01:00
juancarmore
c0ecc826cb backend: update response schemas to remove 'allowAccessTo' and add layout options for recordings 2026-01-21 19:57:09 +01:00
juancarmore
1855755cd6 backend: simplify update room methods to use merge for partial updates 2026-01-21 19:57:09 +01:00
juancarmore
4b183fe02c backend: add room-member controller, middleware and model exports 2026-01-21 19:57:09 +01:00
CSantosM
6e99b21965 frontend: Refactor meeting and room components to use accessUrl instead of moderatorUrl 2026-01-21 19:19:38 +01:00
CSantosM
1414566f7f typings: Enhance permissions and configuration interfaces with detailed JSDoc comments 2026-01-21 19:13:40 +01:00
CSantosM
9293568ccd frontend: Update ApplicationFeatures interface to include new UI controls and adjust permissions handling 2026-01-21 18:58:29 +01:00
CSantosM
78cfc3035e frontend: Remove recording access control from recording configuration 2026-01-21 18:49:03 +01:00
CSantosM
337ba91725 Merge branch 'main' into feat/room-members-users 2026-01-21 18:39:19 +01:00
juancarmore
c573cf802e Merge branch 'main' into feat/room-members-users 2026-01-19 09:43:56 +01:00
juancarmore
c9eee9bf9d backend: implement permission check and participant kick mechanism during member updates 2026-01-19 08:58:17 +01:00
juancarmore
4689c866d6 openapi: add currentParticipantIdentity to room member schema and response 2026-01-14 17:08:25 +01:00
juancarmore
7d462a8f08 backend: add priority mechanism to authentication validators for improved request handling 2026-01-14 17:02:21 +01:00
juancarmore
8d47a7444b backend: reorder authentication validators for consistency in recording and room routes 2026-01-14 17:02:09 +01:00
juancarmore
94fbd55ed8 backend: add todo to notify participant of role/permission changes when updating room member 2026-01-14 13:41:38 +01:00
juancarmore
7ee20f31c1 backend: implement participant kick mechanism during room member deletion 2026-01-14 13:19:27 +01:00
juancarmore
f0a6d63f4d backend: add currentParticipantIdentity to room member schema and update it based on participant webhooks 2026-01-14 13:19:03 +01:00
juancarmore
c60cb244a7 openapi: add reset user password endpoint specification 2026-01-12 17:26:15 +01:00
juancarmore
23154bd380 backend: add password reset functionality for admin users 2026-01-12 17:03:40 +01:00
juancarmore
1498332d28 backend: implement mechanism that requires users to change their password after their first login to improve account security. 2026-01-12 16:49:06 +01:00
juancarmore
765f8c08d1 backend: reorder authorization logic for room member access 2026-01-12 13:59:59 +01:00
juancarmore
770a330e7a backend: implement bulk cleanup of user resources, deleting all memberships for a user and transfering ownership to the global admin 2026-01-12 12:54:21 +01:00
juancarmore
14d10838d6 openapi: add room member conflict error response for existing members and role restrictions 2026-01-12 11:55:52 +01:00
juancarmore
33fd9eabfc openapi: add missing responses and remove unused ones 2026-01-12 11:55:25 +01:00
juancarmore
dccc4bc2f9 backend: add room member error handling for existing members and role restrictions 2026-01-12 11:52:32 +01:00
juancarmore
1d2ebd8be3 backend: enhance room member access authorization to allow users to access their own member info 2026-01-12 10:23:39 +01:00
juancarmore
4390573021 backend: optimize recording and room services by lazy loading dependencies and enhancing permission handling 2026-01-12 10:23:26 +01:00
juancarmore
f2a84717e5 backend: enhance bulk room and recording deletion logic to check for permissions on each item to delete 2026-01-09 13:36:10 +01:00
juancarmore
5fe71482f7 backend: add method to find rooms by owner and update recording service to use it 2026-01-09 13:32:25 +01:00
juancarmore
b8507b824b backend: enhace get all rooms and recordings methods to filter results based on user's role and permissions 2026-01-09 11:26:14 +01:00
juancarmore
0deae8ad29 openapi: enhance user and room member schemas with registration and membership dates; update API documentation for clarity 2026-01-07 19:01:43 +01:00
juancarmore
e7f61aa2c2 backend: add accessUrl field to MeetRoomMember schema and update related services 2026-01-07 18:47:14 +01:00
juancarmore
42c38f82af backend: update default authentication configuration in global config service 2026-01-07 13:46:37 +01:00
juancarmore
df2d58c3bb backend: add membershipDate and registrationDate fields to room member and user schemas 2026-01-07 13:46:11 +01:00
juancarmore
b017334976 backend: update schema versions and add schemaVersion field to MeetRoomMember schema 2026-01-07 12:30:42 +01:00
juancarmore
92b99764b9 openapi: update users and room members get enpoints to include sorting options 2026-01-07 12:30:09 +01:00
juancarmore
577c48fe1b backend: enhance room members and users filtering 2026-01-07 12:28:55 +01:00
juancarmore
a069ebafaf backend: enhance room deletion process to include member removal 2026-01-07 10:34:30 +01:00
juancarmore
93046c8db0 backend: implement room member management features including CRUD operations 2026-01-07 10:34:26 +01:00
juancarmore
a26f2a754b backend: add room roles and anonymous access management features 2026-01-07 10:33:25 +01:00
juancarmore
6b781aac8e backend: enhance room and members management with new authorization middlewares 2026-01-07 10:27:42 +01:00
juancarmore
459799a716 backend: implement all user endpoints 2026-01-07 10:27:42 +01:00
juancarmore
e72566dd8c backend: implement room member management with repository and database schema definition 2026-01-07 10:27:40 +01:00
juancarmore
79cef519b8 backend: refactor room member and user authentication middleware, adding new permissions and request handling 2026-01-07 10:23:09 +01:00
juancarmore
b709ad320a backend: add room member and user endpoints 2026-01-07 10:23:09 +01:00
juancarmore
5d874f57f5 backend: refactor auth middlewares and data stored in request context 2026-01-07 10:23:09 +01:00
juancarmore
53ac60d37a backend: add request validation middleware for room members and users 2026-01-07 10:23:09 +01:00
juancarmore
e0736677ca backend: enhance zod schemas for room members and users, adding new validation and permissions structures 2026-01-07 10:22:41 +01:00
juancarmore
e3de4256a8 typings: complete room, room member, user and permissions interfaces 2026-01-07 10:19:09 +01:00
juancarmore
5abd106641 openapi: add bulk delete endpoints to room members and users API, and add more query params to get all endpoints 2026-01-07 10:14:59 +01:00
513 changed files with 33839 additions and 10017 deletions

46
.github/copilot-instructions.md vendored Normal file
View File

@ -0,0 +1,46 @@
# Project Guidelines
## Code Style
- TypeScript + ESM across backend packages (`"type": "module"`): keep explicit `.js` suffix for local imports in TS source (example: `meet-ce/backend/src/middlewares/auth.middleware.ts`).
- Respect strict TS settings and path aliases from package `tsconfig.json` (example: `meet-ce/frontend/tsconfig.json` with `@app/*`, `@environment/*`, and shared-components source mapping).
- In Angular packages, follow existing standalone/signal patterns already documented in `meet-ce/frontend/.github/copilot-instructions.md` for frontend-only edits.
- Keep changes small and package-scoped; avoid cross-package refactors unless required by the task.
## Architecture
- Monorepo is pnpm-workspace based (`pnpm-workspace.yaml`) with CE and PRO packages plus `testapp`.
- CE is the base product: `meet-ce/backend` (Express + Inversify), `meet-ce/frontend` (Angular), `meet-ce/typings` (shared contracts), `meet-ce/frontend/webcomponent`.
- PRO extends CE behavior, especially via route/provider customization (see `meet-ce/frontend/src/app/app.routes.ts` vs `meet-pro/frontend/src/app/app.routes.ts`).
- Shared Angular domain logic is in `meet-ce/frontend/projects/shared-meet-components/src/lib/domains/*`.
## Build and Test
- Runtime baseline: Node.js >= 22 and pnpm workspace usage from repo root.
- Install all workspaces: `pnpm install`
- Preferred orchestrator: `./meet.sh dev`, `./meet.sh build`, `./meet.sh test-unit-backend`, `./meet.sh test-unit-webcomponent`, `./meet.sh test-e2e-webcomponent`
- Root filtered commands: `pnpm --filter @openvidu-meet/backend run start:dev|build|test:unit`
- Frontend CE: `pnpm --filter @openvidu-meet/frontend run dev|build|test:unit|lib:build`
- Typings: `pnpm --filter @openvidu-meet/typings run dev|build`
- Webcomponent: `pnpm --filter openvidu-meet-webcomponent run build|test:unit|test:e2e`
- Backend integration suites are exposed at root (`test:integration-backend-*`) and backend package (`test:integration-*`).
## Project Conventions
- Use `meet.sh` or `pnpm --filter` commands instead of ad-hoc `cd` workflows for routine tasks.
- Backend DI registration order is intentional; keep dependency bind order coherent when adding services (`meet-ce/backend/src/config/dependency-injector.config.ts`).
- Treat `meet-ce/typings` as the source of shared API/domain contracts; update typings first when frontend/backend payloads change.
- For CI/Docker dependency switching of `openvidu-components-angular`, use only:
- `./meet.sh prepare-ci-build --components-angular-version <version>` or
- `./meet.sh prepare-ci-build --components-angular-tarball <path>`
- `./meet.sh restore-dev-config` after build/test.
- Do not manually rewrite workspace dependencies (`workspace:*`) or `pnpm-workspace.yaml`; use scripts above.
## Integration Points
- External sibling repo dependency is `../openvidu/openvidu-components-angular/projects/openvidu-components-angular` (declared in `pnpm-workspace.yaml`).
- Development workspace uses `pnpm-workspace.yaml`; CI/Docker mode is switched via `pnpm-workspace.docker.yaml` through `prepare-ci-build`.
- Frontend integration relies on `@openvidu-meet/shared-components` and `openvidu-components-angular` via workspace links.
- Backend integrations include LiveKit and optional blob providers (S3/ABS/GCS) configured through storage factory and environment (`meet-ce/backend/src/config/dependency-injector.config.ts`).
- Local backend development env is `meet-ce/backend/.env.development` (LiveKit URL/API key/secret expected).
## Security
- Auth is a validator chain with priority (`apiKey > roomMemberToken > accessToken > anonymous`) in `meet-ce/backend/src/middlewares/auth.middleware.ts`.
- Header contract is centralized in `meet-ce/backend/src/config/internal-config.ts` (`authorization`, `x-refresh-token`, `x-room-member-token`, `x-api-key`).
- Request context relies on request-scoped session service (AsyncLocalStorage-backed singleton); avoid bypassing it when adding auth-aware logic.
- Frontend token persistence currently uses localStorage (`meet-ce/frontend/projects/shared-meet-components/src/lib/shared/services/token-storage.service.ts`); preserve existing flows when modifying auth.

View File

@ -1,397 +0,0 @@
# OpenVidu Meet - Copilot Instructions
## Project Overview
**OpenVidu Meet** is a production-ready videoconferencing application built on top of OpenVidu and LiveKit. It provides a complete, customizable solution for video conferencing with both Community Edition (CE) and Pro versions.
### Key Technologies
- **Frontend**: Angular 20.x, TypeScript, Angular Material
- **Backend**: Node.js, Express framework
- **Package Manager**: pnpm (v10.18.3+) with workspaces
- **Build System**: Nx-like monorepo structure
- **Testing**: Playwright (E2E), Jest (Unit)
- **Containerization**: Docker multi-stage builds
---
## Project Structure
```
openvidu-meet/
├── meet-ce/ # Community Edition
│ ├── frontend/ # Angular application
│ │ ├── src/ # Main app source
│ │ ├── webcomponent/ # Web Component build
│ │ └── projects/
│ │ └── shared-meet-components/ # Shared Angular library
│ ├── backend/ # Express backend
│ └── docker/ # Docker configurations
├── meet-pro/ # Pro Edition (extends CE)
├── testapp/ # Testing application for E2E tests
├── meet-demo/ # Demo application for showcasing features
├── scripts/ # Build and automation scripts
└── meet.sh # Main CLI tool
```
---
## Architecture Principles
### 1. **Monorepo with External Dependencies**
- Uses pnpm workspaces for internal packages
- **External dependency**: `openvidu-components-angular` (located outside this repo)
- **Development**: Uses `workspace:*` protocol for local linking
- **CI/Docker**: Uses npm registry or tarball installations
### 2. **Dual Workspace Configuration**
```yaml
# Development (pnpm-workspace.yaml)
packages:
- 'meet-ce/**'
- '../openvidu/openvidu-components-angular/projects/openvidu-components-angular'
# CI/Docker (pnpm-workspace.docker.yaml)
packages:
- 'meet-ce/**' # No external packages
```
### 3. **Library Structure**
- `@openvidu-meet/frontend`: Main Angular application
- `@openvidu-meet/shared-components`: Reusable Angular library
- Has `openvidu-components-angular` as **peerDependency**
- Built as Angular library (ng-packagr)
- Does NOT bundle dependencies
---
## Coding Standards
### TypeScript/Angular
```typescript
// ✅ Good: Use interfaces for data models
export interface Conference {
id: string;
name: string;
participants: Participant[];
}
// ✅ Good: Use Angular dependency injection
@Injectable({ providedIn: 'root' })
export class ConferenceService {
constructor(private http: HttpClient) {}
}
// ✅ Good: Use RxJS operators properly
this.participants$.pipe(
map(participants => participants.filter(p => p.isActive)),
takeUntil(this.destroy$)
).subscribe();
// ❌ Bad: Don't use 'any' type
// ❌ Bad: Don't forget to unsubscribe from observables
```
### Express Backend with TypeScript and InversifyJS
```typescript
// ✅ Good: Use decorators and dependency injection
@injectable()
export class LoggerService {
log(message: string): void {
console.log(message);
}
}
```
---
## Build & Development
### Main CLI Tool: `meet.sh`
```bash
# Development
./meet.sh dev # Start development servers
./meet.sh dev --skip-install # Skip dependency installation
# Building
./meet.sh build # Build all packages
./meet.sh build --skip-install # Build without installing deps
./meet.sh build --base-href=/custom/ # Custom base href
# Docker
./meet.sh build-docker ov-meet # Build Docker image (CE)
./meet.sh build-docker ov-meet --components-angular-version 3.5.0-beta1
# CI Preparation (Important!)
./meet.sh prepare-ci-build --components-angular-version 3.5.0-beta1
./meet.sh prepare-ci-build --components-angular-tarball ./components.tgz
./meet.sh restore-dev-config # Restore development config
```
### CI/Docker Dependency Strategy
**Problem**: External package `openvidu-components-angular` is not in this repo.
**Solution**: Dual workspace configuration
1. **Development**: Uses `workspace:*` (local linking)
2. **CI/Docker**: Replaces `workspace:*` with registry/tarball versions
**Scripts**:
- `scripts/prepare-ci-build.sh`: Prepares workspace for CI/Docker builds
- `scripts/restore-dev-config.sh`: Restores development configuration
**Workflow**:
```bash
# Before CI/Docker build
prepare-ci-build.sh --components-angular-version 3.5.0-beta1
# Changes: pnpm-workspace.yaml → pnpm-workspace.docker.yaml
# .npmrc → .npmrc.docker
# package.json: workspace:* → ^3.5.0-beta1
# After build
restore-dev-config.sh
# Restores all original files
```
---
## Important Conventions
### 1. **Never Manually Edit package.json in CI**
```bash
# ❌ Bad: Manual sed in CI
sed -i 's/workspace:\*/3.5.0/g' package.json
# ✅ Good: Use prepare-ci-build script
./scripts/prepare-ci-build.sh --components-angular-version 3.5.0-beta1
```
### 2. **peerDependencies Guidelines**
- `shared-meet-components` declares `openvidu-components-angular` as **peerDependency**
- ✅ Use semver ranges: `"^3.0.0"`
- ✅ Use workspace protocol in dev: `"workspace:*"`
- ❌ NEVER use `file:` in peerDependencies (invalid)
### 3. **Angular Version Compatibility**
- Current: Angular 20.x
- `openvidu-components-angular` must support same Angular version
- Check peer dependency warnings carefully
### 4. **Docker Best Practices**
```dockerfile
# ✅ Good: Multi-stage build
FROM node:22.19.0 AS builder
# ... build stage ...
FROM node:22.19.0-alpine AS runner
# ... runtime stage ...
# ✅ Good: Use build args
ARG COMPONENTS_VERSION=3.5.0-beta1
ARG BASE_HREF=/
# ✅ Good: Use .dockerignore
# Avoid copying unnecessary files
```
---
## Common Tasks
### Adding a New Feature
1. Create feature in `meet-ce/frontend/src/app/features/`
2. If reusable, consider moving to `shared-meet-components`
3. Add tests in `*.spec.ts` files
4. Update documentation
### Updating openvidu-components-angular
```bash
# Development (local changes)
cd ../openvidu/openvidu-components-angular
npm run lib:build
cd -
pnpm install
# CI/Docker (version update)
./meet.sh prepare-ci-build --components-angular-version 3.5.0-beta1
./meet.sh build
./meet.sh restore-dev-config
```
### Working with Shared Components
```bash
# Build shared library
cd meet-ce/frontend/projects/shared-meet-components
ng build
# Use in main app
import { SomeComponent } from '@openvidu-meet/shared-components';
```
---
## Testing
### E2E Tests (Playwright)
```bash
npm run test:e2e # Run all E2E tests
npm run test:e2e:ui # Run with UI
npm run test:e2e:debug # Debug mode
```
### Unit Tests (Jest)
```bash
npm run test # Run all unit tests
npm run test:watch # Watch mode
npm run test:coverage # With coverage
```
---
## Troubleshooting
### "workspace package not found" Error
**Cause**: Using `workspace:*` in CI/Docker mode
**Solution**:
```bash
./meet.sh prepare-ci-build --components-angular-version 3.5.0-beta1
```
### "Invalid peer dependency" Error
**Cause**: Using `file:` in peerDependencies
**Solution**: Use semver range (`^3.0.0`) in peerDependencies, not `file:` paths
### Angular Version Mismatch Warnings
**Cause**: `openvidu-components-angular` version doesn't support your Angular version
**Solution**: Update to compatible version
```bash
./meet.sh prepare-ci-build --components-angular-version 3.5.0-beta1 # Supports Angular 20
```
### pnpm-lock.yaml Conflicts
**Solution**: Restore development config first
```bash
./meet.sh restore-dev-config
pnpm install
```
---
## File Naming Conventions
```
✅ Good:
- conference.service.ts (Services)
- conference.component.ts (Components)
- conference.component.spec.ts (Tests)
- conference.model.ts (Models/Interfaces)
- conference.module.ts (Modules)
❌ Bad:
- ConferenceService.ts (PascalCase in filename)
- conference_service.ts (snake_case)
- conferenceService.ts (camelCase)
```
---
## Environment Variables
### Development
```bash
# Frontend
OPENVIDU_SERVER_URL=ws://localhost:4443
OPENVIDU_SECRET=MY_SECRET
# Backend
PORT=3000
OPENVIDU_URL=https://localhost:4443
OPENVIDU_SECRET=MY_SECRET
```
### Docker
Set via `docker-compose.yml` or Dockerfile ARG/ENV
---
## Security Considerations
1. **Never commit secrets** to version control
2. Use environment variables for sensitive data
3. Validate all user inputs (DTOs in backend)
4. Sanitize HTML content in frontend
5. Use Angular's built-in XSS protection
---
## Performance Guidelines
1. **Lazy load modules** when possible
2. **Use OnPush change detection** for performance-critical components
3. **Unsubscribe from observables** (use `takeUntil` or async pipe)
4. **Optimize Docker images** (multi-stage builds, alpine images)
5. **Use pnpm** for faster installs and efficient disk usage
---
## Documentation References
- Full CI/Docker strategy: `docs/ci-docker-dependencies-strategy.md`
- Dependency diagrams: `docs/ci-docker-dependencies-diagrams.md`
- Implementation summary: `docs/IMPLEMENTATION_SUMMARY.md`
- Validation checklist: `docs/VALIDATION_CHECKLIST.md`
---
## When Suggesting Code Changes
1. **Understand the context**: Is this for development or CI/Docker?
2. **Check package.json**: Are dependencies using `workspace:*` or versions?
3. **Consider peerDependencies**: Never use `file:` paths in peerDependencies
4. **Follow conventions**: Use existing patterns in the codebase
5. **Test implications**: Consider impact on both local dev and CI/Docker builds
6. **Use prepare-ci-build**: For any CI/Docker related changes
---
## Quick Reference Commands
```bash
# Development
./meet.sh dev # Start dev servers
./meet.sh build # Build all
# CI/Docker
./meet.sh prepare-ci-build --components-angular-version 3.5.0-beta1
./meet.sh build-docker ov-meet
./meet.sh restore-dev-config
# Testing
npm run test # Unit tests
npm run test:e2e # E2E tests
# Package management
pnpm install # Install deps
pnpm add <package> --filter @openvidu-meet/frontend # Add to specific package
```
---
## Remember
- **Always use scripts** for CI/Docker preparation, never manual edits
- **peerDependencies** are for library compatibility declarations, not installation
- **workspace:*** works only when package is in workspace
- **Test locally** before pushing CI/Docker changes
- **Document breaking changes** and update this file accordingly
---
**Last Updated**: 2025-10-27
**Project Version**: OpenVidu Meet CE/Pro
**Maintained By**: OpenVidu Team

View File

@ -38,7 +38,7 @@ jobs:
fail-fast: false
matrix:
include:
- test-name: 'Room Management API Tests (Rooms, Meetings)'
- test-name: 'Room Management API Tests (Rooms, Room Members, Meetings)'
test-script: 'test:integration-backend-room-management'
- test-name: 'Webhook Tests'
test-script: 'test:integration-backend-webhooks'
@ -53,7 +53,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v5
with:
node-version: '22.13'
node-version: '24.13.1'
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
@ -191,7 +191,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v5
with:
node-version: '22.13'
node-version: '24.13.1'
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:

View File

@ -12,7 +12,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v5
with:
node-version: '22.13'
node-version: '24.13.1'
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:

View File

@ -12,7 +12,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v5
with:
node-version: '22.13'
node-version: '24.13.1'
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:

View File

@ -12,7 +12,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v5
with:
node-version: '22.13'
node-version: '24.13.1'
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:

View File

@ -1,5 +1,5 @@
{
"jest.jestCommandLine": "node --experimental-vm-modules ../../node_modules/.bin/jest --config jest.integration.config.mjs",
"jest.jestCommandLine": "../../node_modules/.bin/jest --config jest.integration.config.mjs",
"jest.rootPath": "backend",
"jest.nodeEnv": {
"NODE_OPTIONS": "--experimental-vm-modules"

View File

@ -5,8 +5,8 @@ const integrationConfig = {
runInBand: true,
forceExit: true,
detectOpenHandles: true,
testMatch: ['**/tests/integration/**/*.(spec|test).ts'],
detectOpenHandles: true,
testMatch: ['**/tests/integration/**/*.(spec|test).ts']
};
export default integrationConfig;

View File

@ -0,0 +1,9 @@
in: query
name: userId
required: false
description: >
Filter users by userId. The search matches users that contain the specified text in their userId.
For example, 'alice' will match 'alice_smith', 'bob_alice', and 'alice123'.
schema:
type: string
example: 'alice_smith'

View File

@ -0,0 +1,9 @@
name: userIds
in: query
required: true
description: >
Comma-separated list of user IDs to delete.
Each ID should correspond to an existing user.
schema:
type: string
example: 'alice_smith,john_doe'

View File

@ -0,0 +1,9 @@
name: name
in: query
required: false
description: >
Filter users by name. The search is case-insensitive and matches users that contain the specified text in their name.
For example, 'alice' will match 'Alice Smith' and 'Bob Alice'.
schema:
type: string
example: 'Alice'

View File

@ -0,0 +1,8 @@
name: role
in: query
required: false
description: Filter users by their role.
schema:
type: string
enum: [admin, user, room_member]
example: 'admin'

View File

@ -0,0 +1,9 @@
name: sortField
in: query
required: false
description: The user field by which to sort the results.
schema:
type: string
enum:
- name
- registrationDate

View File

@ -2,9 +2,9 @@ name: withMeeting
in: query
description: |
Policy for room deletion when it has an active meeting. Options are:
- force: The meeting will be ended, and the room will be deleted without waiting for participants to leave.
- when_meeting_ends: The room will be deleted when the meeting ends.
- fail: The deletion will fail if there is an active meeting.
- `force`: The meeting will be ended, and the room will be deleted without waiting for participants to leave.
- `when_meeting_ends`: The room will be deleted when the meeting ends.
- `fail`: The deletion will fail if there is an active meeting.
required: false
schema:
type: string

View File

@ -0,0 +1,11 @@
name: sortField
in: query
required: false
description: The recording field by which to sort the results.
schema:
type: string
enum:
- startDate
- roomName
- duration
- size

View File

@ -0,0 +1,15 @@
name: X-Fields
in: header
description: >
Comma-separated list of **Recording** fields to include in the response.
Use this header to request only the data you need, reducing payload size and improving performance.
When combined with the `fields` query parameter, values are merged (union of unique fields).
required: false
schema:
type: string
examples:
basic:
value: 'recordingId,roomId,status'
summary: Only return basic recording information

View File

@ -2,9 +2,9 @@ name: withRecordings
in: query
description: |
Policy for room deletion when it has recordings. Options are:
- force: The room and its recordings will be deleted.
- close: The room will be closed instead of deleted, maintaining its recordings.
- fail: The deletion will fail if the room has recordings.
- `force`: The room and its recordings will be deleted.
- `close`: The room will be closed instead of deleted, maintaining its recordings.
- `fail`: The deletion will fail if the room has recordings.
required: false
schema:
type: string

View File

@ -0,0 +1,10 @@
name: extraFields
in: query
description: >
Comma-separated list of additional fields to include in the response that are excluded by default to reduce payload size.
<br/><br/>
These fields are included even if not listed in `fields` query parameter or `X-Fields` header.
required: false
schema:
type: string
example: 'extrafields=config'

View File

@ -6,4 +6,4 @@ description: >
required: false
schema:
type: string
example: 'roomId,moderatorUrl'
example: 'fields=roomId,roomName'

View File

@ -0,0 +1,9 @@
name: memberIds
in: query
required: true
description: >
Comma-separated list of room member IDs to delete.
Each ID should correspond to an existing member in the room.
schema:
type: string
example: 'alice_smith,ext:abc123'

View File

@ -0,0 +1,9 @@
name: name
in: query
required: false
description: >
Filter members by name. The search is case-insensitive and matches members whose names contain the specified text.
For example, 'ali' will match 'Alice' and 'Malik'.
schema:
type: string
example: 'Alice'

View File

@ -0,0 +1,9 @@
name: sortField
in: query
required: false
description: The room member field by which to sort the results.
schema:
type: string
enum:
- name
- membershipDate

View File

@ -0,0 +1,10 @@
name: sortField
in: query
required: false
description: The room field by which to sort the results.
schema:
type: string
enum:
- creationDate
- roomName
- autoDeletionDate

View File

@ -0,0 +1,13 @@
name: X-ExtraFields
in: header
description: >
Comma-separated list of extra **Room** fields to include fully in the response.
By default, large or nested properties (e.g., `config`) are excluded to reduce payload size.
These fields are included even if not listed in the `X-Fields` header.
required: false
schema:
type: string
example: config

View File

@ -0,0 +1,13 @@
name: X-Fields
in: header
description: >
Comma-separated list of **Room** fields to include in the response.
Use this header to request only the data you need, reducing payload size and improving performance.
required: false
schema:
type: string
examples:
basic:
value: 'roomId,roomName,status'
summary: Only return basic room information

View File

@ -1,6 +0,0 @@
name: sortField
in: query
required: false
description: The field by which to sort the results.
schema:
type: string

View File

@ -9,16 +9,16 @@ content:
type: string
example: 'alice_smith'
description: |
The unique identifier for an internal OpenVidu Meet user. This field should be provided when adding an internal Meet user as a member.
The unique identifier for a registered OpenVidu Meet user. This field should be provided when adding a registered Meet user as a member.
If provided:
- The member will be associated with the Meet user account identified by this userId.
- The 'name' field should be left blank or an error will be fired. It will be automatically set based on the Meet user's profile name.
- The 'name' field must be left blank or an error will be fired. It will be automatically set based on the Meet user's profile name.
- The memberId will be set to this userId value.
If omitted, the member will be treated as an external user and 'name' must be provided.
Important: You must provide either 'userId' (for internal users) or 'name' (for external users), but NOT both.
Important: You must provide either 'userId' (for registered users) or 'name' (for external users), but NOT both.
If both are provided, a validation error will be returned.
name:
type: string
@ -27,9 +27,9 @@ content:
description: |
The display name for the participant when joining the meeting with this member access. It is recommended to be unique for the members of the room to easily identify them in the meeting.
This field is required only when adding an external user. The 'userId' field should be left blank or an error will be fired.
This field is required only when adding an external user. The 'userId' field must be left blank or an error will be fired.
Important: You must provide either 'userId' (for internal users) or 'name' (for external users), but NOT both.
Important: You must provide either 'userId' (for registered users) or 'name' (for external users), but NOT both.
If both are provided, a validation error will be returned.
baseRole:
type: string
@ -62,7 +62,7 @@ content:
description: |
Request body to add a new member to a room.
Important: You must provide either 'userId' (for internal Meet users) or 'name' (for external users), but NOT both.
- If 'userId' is provided, the member will be linked to an internal user account and 'name' will be set from that account.
Important: You must provide either 'userId' (for registered Meet users) or 'name' (for external users), but NOT both.
- If 'userId' is provided, the member will be linked to a registered user account and 'name' will be set from that account.
- If 'name' is provided, the member will be treated as an external user without a linked account.
- If both 'userId' and 'name' are provided or neither is provided, the request will fail with a validation error.

View File

@ -0,0 +1,12 @@
description: Reset user password request
required: true
content:
application/json:
schema:
type: object
properties:
newPassword:
type: string
minLength: 5
description: The new temporary password for the user.
example: 'newSecurePassword123'

View File

@ -0,0 +1,12 @@
description: Update user role request
required: true
content:
application/json:
schema:
type: object
properties:
role:
type: string
enum: [admin, user, room_member]
description: The new role to assign to the user.
example: 'user'

View File

@ -0,0 +1,9 @@
description: Room access configuration update options
required: true
content:
application/json:
schema:
type: object
properties:
access:
$ref: '../schemas/meet-room-access-config.yaml'

View File

@ -1,6 +0,0 @@
description: Room anonymous access configuration update options
required: true
content:
application/json:
schema:
$ref: '../schemas/meet-room-anonymous-config.yaml'

View File

@ -3,4 +3,7 @@ required: true
content:
application/json:
schema:
$ref: '../schemas/meet-room-roles-config.yaml'
type: object
properties:
config:
$ref: '../schemas/meet-room-roles-config.yaml'

View File

@ -0,0 +1,53 @@
description: >
**Mixed results**. Some room members were deleted successfully while others
could not be deleted (e.g., if they do not exist).
content:
application/json:
schema:
type: object
properties:
message:
type: string
deleted:
type: array
items:
type: string
description: List of room members that were deleted successfully.
failed:
type: array
description: List of room members that could not be deleted along with the corresponding error messages.
items:
type: object
properties:
memberId:
type: string
description: The unique identifier of the room member that was not deleted.
example: 'alice_smith'
error:
type: string
description: A message explaining why the deletion failed.
example: 'Room member not found'
examples:
partial_deletion_with_errors:
summary: Some room members were deleted successfully, others failed
value:
message: '2 room member(s) could not be deleted'
deleted:
- 'alice_smith'
- 'ext:abc123'
failed:
- memberId: 'bob_jones'
error: 'Room member not found'
- memberId: 'ext:def456'
error: 'Room member not found'
no_deletion_performed:
summary: No room members were deleted
value:
message: '2 room member(s) could not be deleted'
deleted: []
failed:
- memberId: 'bob_jones'
error: 'Room member not found'
- memberId: 'room-123--EG_ZYX--XX448'
error: 'Room member not found'

View File

@ -1,8 +1,8 @@
description: Recordings not from the same room
description: Recordings not available for ZIP download
content:
application/json:
schema:
$ref: ../schemas/error.yaml
example:
error: 'Recording Error'
message: 'None of the provided recording IDs belong to room "room123"'
message: 'None of the provided recordings are available for ZIP download'

View File

@ -1,4 +1,4 @@
description: Room not found
description: Room cannot be updated because it has an active meeting.
content:
application/json:
schema:

View File

@ -0,0 +1,16 @@
description: Room member conflict error
content:
application/json:
schema:
$ref: '../schemas/error.yaml'
examples:
member_already_exists:
summary: Member already exists
value:
error: 'Room Member Error'
message: 'User "alice_smith" is already a member of room "room_123"'
member_cannot_be_owner_or_admin:
summary: Member cannot be owner or admin
value:
error: 'Room Member Error'
message: 'User "alice_smith" cannot be added as a member of room "room_123" because they are the room owner or an admin'

View File

@ -7,7 +7,7 @@ content:
member_not_found:
summary: Room member does not exist in the specified room
value:
error: 'Room Error'
error: 'Room Member Error'
message: 'Room member "abc123" does not exist in room "room_123"'
room_not_found:
summary: Room does not exist

View File

@ -1,8 +0,0 @@
description: Rooms appearance configuration not defined
content:
application/json:
schema:
$ref: '../../schemas/error.yaml'
example:
error: 'Global Config Error'
message: Rooms appearance configuration not defined

View File

@ -0,0 +1,53 @@
description: >
**Mixed results**. Some users were deleted successfully while others
could not be deleted (e.g., if they do not exist).
content:
application/json:
schema:
type: object
properties:
message:
type: string
deleted:
type: array
items:
type: string
description: List of users that were deleted successfully.
failed:
type: array
description: List of users that could not be deleted along with the corresponding error messages.
items:
type: object
properties:
userId:
type: string
description: The unique identifier of the user that was not deleted.
example: 'john_doe'
error:
type: string
description: A message explaining why the deletion failed.
example: 'User not found'
examples:
partial_deletion_with_errors:
summary: Some users were deleted successfully, others failed
value:
message: '2 user(s) could not be deleted'
deleted:
- 'john_doe'
- 'jane_doe'
failed:
- userId: 'alice_smith'
error: 'User not found'
- userId: 'bob_jones'
error: 'User not found'
no_deletion_performed:
summary: No users were deleted
value:
message: '2 user(s) could not be deleted'
deleted: []
failed:
- userId: 'alice_smith'
error: 'User not found'
- userId: 'bob_jones'
error: 'User not found'

View File

@ -0,0 +1,18 @@
description: All specified users were deleted successfully.
content:
application/json:
schema:
type: object
properties:
message:
type: string
deleted:
type: array
items:
type: string
description: List of users that were deleted successfully.
example:
message: "All users deleted successfully"
deleted:
- 'alice_smith'
- 'john_doe'

View File

@ -7,3 +7,11 @@ content:
message:
type: string
example: Password for user 'admin' changed successfully
accessToken:
type: string
description: >
The access token to authenticate the user in subsequent requests. Only present when password had to be changed.
refreshToken:
type: string
description: >
The refresh token to obtain a new access token when the current one expires. Only present when password had to be changed.

View File

@ -17,12 +17,15 @@ content:
users:
- userId: 'admin'
name: 'Admin'
registrationDate: 1620000000000
role: 'admin'
- userId: 'alice_smith'
name: 'Alice Smith'
registrationDate: 1620050000000
role: 'user'
- userId: 'bob_jones'
name: 'Bob Jones'
registrationDate: 1620100000000
role: 'room_member'
pagination:
nextPageToken: 'eyJvZmZzZXQiOjEwfQ=='
@ -34,6 +37,7 @@ content:
users:
- userId: 'admin'
name: 'Admin'
registrationDate: 1620000000000
role: 'admin'
pagination:
isTruncated: false

View File

@ -0,0 +1,9 @@
description: Successfully changed user password
content:
application/json:
schema:
type: object
properties:
message:
type: string
example: Password for user 'alice_smith' has been reset successfully. User must change password on next login.

View File

@ -0,0 +1,11 @@
description: Successfully updated user role
content:
application/json:
schema:
type: object
properties:
message:
type: string
example: Role for user 'alice_smith' updated successfully to 'admin'
user:
$ref: '../../schemas/internal/meet-user.yaml'

View File

@ -15,3 +15,8 @@ content:
type: string
description: >
The refresh token to obtain a new access token when the current one expires.
mustChangePassword:
type: boolean
example: true
description: >
Indicates whether the user must change their password after login. Only present if password change is required.

View File

@ -0,0 +1,18 @@
description: All specified room members were deleted successfully.
content:
application/json:
schema:
type: object
properties:
message:
type: string
deleted:
type: array
items:
type: string
description: List of room members that were deleted successfully.
example:
message: "All room members deleted successfully"
deleted:
- 'alice_smith'
- 'ext:abc123'

View File

@ -2,40 +2,238 @@ description: All specified rooms were successfully processed for deletion.
content:
application/json:
schema:
type: object
properties:
message:
type: string
successful:
type: array
items:
type: object
properties:
successCode:
type: string
enum:
- room_deleted
- room_with_active_meeting_deleted
- room_with_active_meeting_scheduled_to_be_deleted
- room_and_recordings_deleted
- room_closed
- room_with_active_meeting_and_recordings_deleted
- room_with_active_meeting_closed
- room_with_active_meeting_and_recordings_scheduled_to_be_deleted
- room_with_active_meeting_scheduled_to_be_closed
description: A code representing the success scenario
message:
type: string
description: A message providing additional context about the success
room:
$ref: '../schemas/meet-room.yaml'
description: List of rooms that were successfully processed for deletion
example:
message: 'All rooms successfully processed for deletion'
successful:
- roomId: room-123
successCode: room_deleted
message: Room 'room-123' deleted successfully
- roomId: room-456
successCode: room_with_active_meeting_deleted
message: Room 'room-456' with active meeting deleted successfully
oneOf:
- type: object
properties:
message:
type: string
successful:
type: array
items:
type: object
properties:
successCode:
type: string
enum:
- room_closed
- room_with_active_meeting_scheduled_to_be_deleted
- room_with_active_meeting_closed
- room_with_active_meeting_and_recordings_scheduled_to_be_deleted
- room_with_active_meeting_scheduled_to_be_closed
description: A code representing the success scenario
message:
type: string
description: A message providing additional context about the success
room:
allOf:
- $ref: '../schemas/meet-room.yaml'
- type: object
properties:
_extraFields:
type: array
description: >
List of extra fields that can be included in the response based on the `X-ExtraFields` header or `extraFields` query parameter.
items:
type: string
example: config
description: List of rooms that were successfully processed for deletion
- type: object
properties:
message:
type: string
successful:
type: array
items:
type: object
properties:
successCode:
type: string
enum:
- room_deleted
- room_with_active_meeting_deleted
- room_and_recordings_deleted
- room_with_active_meeting_and_recordings_deleted
description: A code representing the success scenario
message:
type: string
description: A message providing additional context about the success
examples:
room_closed:
summary: Room closed successfully
value:
message: 'Bulk deletion request processed successfully'
successful:
- successCode: room_closed
message: Room 'room-123' has been closed instead of deleted because it has recordings
room:
roomId: room-123
roomName: room
owner: 'admin'
creationDate: 1620000000000
access:
anonymous:
moderator:
enabled: true
url: 'https://example.com/room/room-123?secret=123456'
speaker:
enabled: true
url: 'https://example.com/room/room-123?secret=654321'
recording:
enabled: true
url: 'https://example.com/room/room-123/recordings?secret=987654'
registered:
enabled: false
url: 'https://example.com/room/room-123'
status: closed
meetingEndAction: none
_extraFields:
- config
room_with_active_meeting_scheduled_to_be_deleted:
summary: Room with active meeting scheduled to be deleted
value:
message: 'Bulk deletion request processed successfully'
successful:
- successCode: room_with_active_meeting_scheduled_to_be_deleted
message: Room 'room-123' with active meeting scheduled to be deleted when the meeting ends
room:
roomId: room-123
roomName: room
owner: 'admin'
creationDate: 1620000000000
access:
anonymous:
moderator:
enabled: true
url: 'https://example.com/room/room-123?secret=123456'
speaker:
enabled: true
url: 'https://example.com/room/room-123?secret=654321'
recording:
enabled: true
url: 'https://example.com/room/room-123/recordings?secret=987654'
registered:
enabled: false
url: 'https://example.com/room/room-123'
status: active_meeting
meetingEndAction: delete
_extraFields:
- config
room_with_active_meeting_closed:
summary: Room with active meeting closed
value:
message: 'Bulk deletion request processed successfully'
successful:
- successCode: room_with_active_meeting_closed
message: Room 'room-123' with active meeting has been closed instead of deleted because it has recordings
room:
roomId: room-123
roomName: room
owner: 'admin'
creationDate: 1620000000000
access:
anonymous:
moderator:
enabled: true
url: 'https://example.com/room/room-123?secret=123456'
speaker:
enabled: true
url: 'https://example.com/room/room-123?secret=654321'
recording:
enabled: true
url: 'https://example.com/room/room-123/recordings?secret=987654'
registered:
enabled: false
url: 'https://example.com/room/room-123'
status: active_meeting
meetingEndAction: close
_extraFields:
- config
room_with_active_meeting_and_recordings_scheduled_to_be_deleted:
summary: Room with active meeting and recordings scheduled to be deleted
value:
message: 'Bulk deletion request processed successfully'
successful:
- successCode: room_with_active_meeting_and_recordings_scheduled_to_be_deleted
message: Room 'room-123' with active meeting and its recordings scheduled to be deleted when the meeting ends
room:
roomId: room-123
roomName: room
owner: 'admin'
creationDate: 1620000000000
access:
anonymous:
moderator:
enabled: true
url: 'https://example.com/room/room-123?secret=123456'
speaker:
enabled: true
url: 'https://example.com/room/room-123?secret=654321'
recording:
enabled: true
url: 'https://example.com/room/room-123/recordings?secret=987654'
registered:
enabled: false
url: 'https://example.com/room/room-123'
status: active_meeting
meetingEndAction: delete
_extraFields:
- config
room_with_active_meeting_scheduled_to_be_closed:
summary: Room with active meeting scheduled to be closed
value:
message: 'Bulk deletion request processed successfully'
successful:
- successCode: room_with_active_meeting_scheduled_to_be_closed
message: Room 'room-123' with active meeting scheduled to be closed when the meeting ends because it has recordings
room:
roomId: room-123
roomName: room
owner: 'admin'
creationDate: 1620000000000
access:
anonymous:
moderator:
enabled: true
url: 'https://example.com/room/room-123?secret=123456'
speaker:
enabled: true
url: 'https://example.com/room/room-123?secret=654321'
recording:
enabled: true
url: 'https://example.com/room/room-123/recordings?secret=987654'
registered:
enabled: false
url: 'https://example.com/room/room-123'
status: active_meeting
meetingEndAction: close
_extraFields:
- config
room_deleted:
summary: Room deleted successfully
value:
message: 'Bulk deletion request processed successfully'
successful:
- successCode: room_deleted
message: Room 'room-123' deleted successfully
room_with_active_meeting_deleted:
summary: Room with active meeting deleted
value:
message: 'Bulk deletion request processed successfully'
successful:
- successCode: room_with_active_meeting_deleted
message: Room 'room-123' with active meeting deleted successfully
room_and_recordings_deleted:
summary: Room and recordings deleted
value:
message: 'Bulk deletion request processed successfully'
successful:
- successCode: room_and_recordings_deleted
message: Room 'room-123' and its recordings deleted successfully
room_with_active_meeting_and_recordings_deleted:
summary: Room with active meeting and recordings deleted
value:
message: 'Bulk deletion request processed successfully'
successful:
- successCode: room_with_active_meeting_and_recordings_deleted
message: Room 'room-123' with active meeting and its recordings deleted successfully

View File

@ -2,7 +2,79 @@ description: Room created successfully
content:
application/json:
schema:
$ref: '../schemas/meet-room.yaml'
allOf:
- $ref: '../schemas/meet-room.yaml'
- type: object
properties:
_extraFields:
type: array
description: >
List of extra fields that can be included in the response based on the `X-ExtraFields` header.
items:
type: string
example: config
examples:
default_room_created:
summary: Room created successfully without config
value:
roomId: 'room-123'
roomName: 'room'
owner: 'admin'
creationDate: 1620000000000
autoDeletionDate: 1900000000000
autoDeletionPolicy:
withMeeting: when_meeting_ends
withRecordings: close
roles:
moderator:
permissions:
canRecord: true
canRetrieveRecordings: true
canDeleteRecordings: true
canJoinMeeting: true
canShareAccessLinks: true
canMakeModerator: true
canKickParticipants: true
canEndMeeting: true
canPublishVideo: true
canPublishAudio: true
canShareScreen: true
canReadChat: true
canWriteChat: true
canChangeVirtualBackground: true
speaker:
permissions:
canRecord: false
canRetrieveRecordings: true
canDeleteRecordings: false
canJoinMeeting: true
canShareAccessLinks: false
canMakeModerator: false
canKickParticipants: false
canEndMeeting: false
canPublishVideo: true
canPublishAudio: true
canShareScreen: true
canReadChat: true
canWriteChat: true
canChangeVirtualBackground: true
access:
anonymous:
moderator:
enabled: true
url: 'https://example.com/room/room-123?secret=123456'
speaker:
enabled: true
url: 'https://example.com/room/room-123?secret=654321'
recording:
enabled: true
url: 'https://example.com/room/room-123/recordings?secret=987654'
registered:
enabled: false
url: 'https://example.com/room/room-123'
status: open
meetingEndAction: none
_extraFields: ['config']
headers:
Location:
description: URL of the newly created room

View File

@ -0,0 +1,112 @@
description: Room was successfully processed for deletion
content:
application/json:
schema:
oneOf:
- type: object
required: [successCode, message, room]
properties:
successCode:
type: string
enum:
- room_closed
- room_with_active_meeting_closed
message:
type: string
room:
allOf:
- $ref: '../schemas/meet-room.yaml'
- type: object
properties:
_extraFields:
type: array
description: >
List of extra fields that can be included in the response based on the `X-ExtraFields` header or `extraFields` query parameter.
items:
type: string
example: config
- type: object
required: [successCode, message]
properties:
successCode:
type: string
enum:
- room_deleted
- room_with_active_meeting_deleted
- room_and_recordings_deleted
- room_with_active_meeting_and_recordings_deleted
message:
type: string
examples:
room_deleted:
value:
successCode: room_deleted
message: Room 'room-123' deleted successfully
room_with_active_meeting_deleted:
value:
successCode: room_with_active_meeting_deleted
message: Room 'room-123' with active meeting deleted successfully
room_and_recordings_deleted:
value:
successCode: room_and_recordings_deleted
message: Room 'room-123' and its recordings deleted successfully
room_closed:
value:
successCode: room_closed
message: Room 'room-123' has been closed instead of deleted because it has recordings
room:
roomId: room-123
roomName: room
owner: 'admin'
creationDate: 1620000000000
access:
anonymous:
moderator:
enabled: true
url: 'https://example.com/room/room-123?secret=123456'
speaker:
enabled: true
url: 'https://example.com/room/room-123?secret=654321'
recording:
enabled: true
url: 'https://example.com/room/room-123/recordings?secret=987654'
registered:
enabled: false
url: 'https://example.com/room/room-123'
status: closed
meetingEndAction: none
_extraFields:
- config
room_with_active_meeting_and_recordings_deleted:
value:
successCode: room_with_active_meeting_and_recordings_deleted
message: Room 'room-123' with active meeting and its recordings deleted successfully
room_with_active_meeting_closed:
value:
successCode: room_with_active_meeting_closed
message: Room 'room-123' with active meeting has been closed instead of deleted because it has recordings
room:
roomId: room-123
roomName: room
owner: 'admin'
creationDate: 1620000000000
access:
anonymous:
moderator:
enabled: true
url: 'https://example.com/room/room-123?secret=123456'
speaker:
enabled: true
url: 'https://example.com/room/room-123?secret=654321'
recording:
enabled: true
url: 'https://example.com/room/room-123/recordings?secret=987654'
registered:
enabled: false
url: 'https://example.com/room/room-123'
status: active_meeting
meetingEndAction: close
_extraFields:
- config

View File

@ -0,0 +1,110 @@
description: Room was successfully scheduled to be deleted or closed
content:
application/json:
schema:
type: object
properties:
successCode:
type: string
enum:
- room_with_active_meeting_scheduled_to_be_deleted
- room_with_active_meeting_and_recordings_scheduled_to_be_deleted
- room_with_active_meeting_scheduled_to_be_closed
description: A code representing the success scenario
message:
type: string
description: A message providing additional context about the success
room:
allOf:
- $ref: '../schemas/meet-room.yaml'
- type: object
properties:
_extraFields:
type: array
description: >
List of extra fields that can be included in the response based on the `X-ExtraFields` header or `extraFields` query parameter.
items:
type: string
example: config
examples:
room_with_active_meeting_scheduled_to_be_deleted:
value:
successCode: room_with_active_meeting_scheduled_to_be_deleted
message: Room 'room-123' with active meeting scheduled to be deleted when the meeting ends
room:
roomId: room-123
roomName: room
owner: 'admin'
creationDate: 1620000000000
access:
anonymous:
moderator:
enabled: true
url: 'https://example.com/room/room-123?secret=123456'
speaker:
enabled: true
url: 'https://example.com/room/room-123?secret=654321'
recording:
enabled: true
url: 'https://example.com/room/room-123/recordings?secret=987654'
registered:
enabled: false
url: 'https://example.com/room/room-123'
status: active_meeting
meetingEndAction: delete
_extraFields:
- config
room_with_active_meeting_and_recordings_scheduled_to_be_deleted:
value:
successCode: room_with_active_meeting_and_recordings_scheduled_to_be_deleted
message: Room 'room-123' with active meeting and its recordings scheduled to be deleted when the meeting ends
room:
roomId: room-123
roomName: room
owner: 'admin'
creationDate: 1620000000000
access:
anonymous:
moderator:
enabled: true
url: 'https://example.com/room/room-123?secret=123456'
speaker:
enabled: true
url: 'https://example.com/room/room-123?secret=654321'
recording:
enabled: true
url: 'https://example.com/room/room-123/recordings?secret=987654'
registered:
enabled: false
url: 'https://example.com/room/room-123'
status: active_meeting
meetingEndAction: delete
_extraFields:
- config
room_with_active_meeting_scheduled_to_be_closed:
value:
successCode: room_with_active_meeting_scheduled_to_be_closed
message: Room 'room-123' with active meeting scheduled to be closed when the meeting ends because it has recordings
room:
roomId: room-123
roomName: room
owner: 'admin'
creationDate: 1620000000000
access:
anonymous:
moderator:
enabled: true
url: 'https://example.com/room/room-123?secret=123456'
speaker:
enabled: true
url: 'https://example.com/room/room-123?secret=654321'
recording:
enabled: true
url: 'https://example.com/room/room-123/recordings?secret=987654'
registered:
enabled: false
url: 'https://example.com/room/room-123'
status: active_meeting
meetingEndAction: close
_extraFields:
- config

View File

@ -12,6 +12,7 @@ content:
roomName: 'room'
status: 'complete'
layout: 'grid'
encoding: 'H264_720P_30'
filename: 'room-123--XX445.mp4'
startDate: 1600000000000
endDate: 1600000003600
@ -27,5 +28,6 @@ content:
roomName: 'room'
status: 'active'
layout: 'grid'
encoding: 'H264_720P_30'
filename: 'room-456--QR789.mp4'
startDate: 1682500000000

View File

@ -20,6 +20,7 @@ content:
roomName: 'room'
status: 'active'
layout: 'grid'
encoding: 'H264_720P_30'
filename: 'room-123--XX445.mp4'
startDate: 1620000000000
endDate: 1620000003600
@ -31,6 +32,7 @@ content:
roomName: 'room'
status: 'complete'
layout: 'grid'
encoding: 'H264_720P_30'
filename: 'room-456--XX678.mp4'
startDate: 1625000000000
endDate: 1625000007200

View File

@ -18,7 +18,8 @@ content:
members:
- memberId: 'alice_smith'
name: 'Alice Smith'
accessUrl: 'http://localhost:6080/room/room-123'
membershipDate: 1620000000000
accessUrl: 'https://example.com/room/room-123'
baseRole: 'moderator'
customPermissions:
canEndMeeting: false
@ -39,7 +40,8 @@ content:
canChangeVirtualBackground: true
- memberId: 'ext-abc123'
name: 'Bob'
accessUrl: 'http://localhost:6080/room/room-123?secret=ext-abc123'
membershipDate: 1620003600000
accessUrl: 'https://example.com/room/room-123?secret=ext-abc123'
baseRole: 'speaker'
customPermissions:
canShareScreen: false
@ -59,6 +61,7 @@ content:
canReadChat: true
canWriteChat: true
canChangeVirtualBackground: true
currentParticipantIdentity: 'bob-5678'
pagination:
isTruncated: false
maxItems: 10
@ -66,9 +69,9 @@ content:
summary: Response with only accessUrl and baseRole for each member
value:
members:
- accessUrl: 'http://localhost:6080/room/room-123'
- accessUrl: 'https://example.com/room/room-123'
baseRole: 'moderator'
- accessUrl: 'http://localhost:6080/room/room-123?secret=ext-abc123'
- accessUrl: 'https://example.com/room/room-123?secret=ext-abc123'
baseRole: 'speaker'
pagination:
isTruncated: false

View File

@ -2,10 +2,106 @@ description: Success response for retrieving a room
content:
application/json:
schema:
$ref: '../schemas/meet-room.yaml'
allOf:
- $ref: '../schemas/meet-room.yaml'
- type: object
properties:
_extraFields:
type: array
description: >
List of extra fields that can be included in the response based on the `X-ExtraFields` header or `extraFields` query parameter.
items:
type: string
example: config
examples:
complete_room_details:
summary: Full room details response
default_room_details:
summary: Full room details response without extra fields
value:
roomId: 'room-123'
roomName: 'room'
owner: 'admin'
creationDate: 1620000000000
autoDeletionDate: 1900000000000
autoDeletionPolicy:
withMeeting: when_meeting_ends
withRecordings: close
roles:
moderator:
permissions:
canRecord: true
canRetrieveRecordings: true
canDeleteRecordings: true
canJoinMeeting: true
canShareAccessLinks: true
canMakeModerator: true
canKickParticipants: true
canEndMeeting: true
canPublishVideo: true
canPublishAudio: true
canShareScreen: true
canReadChat: true
canWriteChat: true
canChangeVirtualBackground: true
speaker:
permissions:
canRecord: false
canRetrieveRecordings: true
canDeleteRecordings: false
canJoinMeeting: true
canShareAccessLinks: false
canMakeModerator: false
canKickParticipants: false
canEndMeeting: false
canPublishVideo: true
canPublishAudio: true
canShareScreen: true
canReadChat: true
canWriteChat: true
canChangeVirtualBackground: true
access:
anonymous:
moderator:
enabled: true
url: 'https://example.com/room/room-123?secret=123456'
speaker:
enabled: true
url: 'https://example.com/room/room-123?secret=654321'
recording:
enabled: true
url: 'https://example.com/room/room-123/recordings?secret=987654'
registered:
enabled: false
url: 'https://example.com/room/room-123'
status: open
meetingEndAction: none
_extraFields:
- config
fields=roomId,roomName,creationDate,autoDeletionDate,config:
summary: Room details with roomId, roomName, creationDate, autoDeletionDate, and config
value:
roomId: 'room-123'
roomName: 'room'
creationDate: 1620000000000
autoDeletionDate: 1900000000000
config:
chat:
enabled: true
recording:
enabled: true
layout: grid
encoding: H264_720P_30
virtualBackground:
enabled: true
e2ee:
enabled: false
captions:
enabled: true
_extraFields:
- config
extraFields=config:
summary: Room details with expanded config
value:
roomId: 'room-123'
roomName: 'room'
@ -16,13 +112,12 @@ content:
withMeeting: when_meeting_ends
withRecordings: close
config:
recording:
enabled: false
layout: grid
encoding: H264_720P_30
allowAccessTo: admin_moderator_speaker
chat:
enabled: true
recording:
enabled: true
layout: grid
encoding: H264_720P_30
virtualBackground:
enabled: true
e2ee:
@ -62,52 +157,40 @@ content:
canReadChat: true
canWriteChat: true
canChangeVirtualBackground: true
anonymous:
moderator:
enabled: true
accessUrl: 'http://localhost:6080/room/room-123?secret=123456'
speaker:
enabled: true
accessUrl: 'http://localhost:6080/room/room-123?secret=654321'
accessUrl: 'http://localhost:6080/room/room-123'
access:
anonymous:
moderator:
enabled: true
url: 'https://example.com/room/room-123?secret=123456'
speaker:
enabled: true
url: 'https://example.com/room/room-123?secret=654321'
recording:
enabled: true
url: 'https://example.com/room/room-123/recordings?secret=987654'
registered:
enabled: false
url: 'https://example.com/room/room-123'
status: open
meetingEndAction: none
fields=roomId,roomName,creationDate,autoDeletionDate,config:
summary: Room details with roomId, roomName, creationDate, autoDeletionDate, and config
_extraFields:
- config
fields=access:
summary: Response containing only access configuration
value:
roomId: 'room-123'
roomName: 'room'
creationDate: 1620000000000
autoDeletionDate: 1900000000000
config:
recording:
access:
anonymous:
moderator:
enabled: true
url: 'https://example.com/room/room-123?secret=123456'
speaker:
enabled: true
url: 'https://example.com/room/room-123?secret=654321'
recording:
enabled: true
url: 'https://example.com/room/room-123/recordings?secret=987654'
registered:
enabled: false
layout: grid
encoding:
video:
width: 1920
height: 1080
framerate: 30
codec: H264_MAIN
audio:
codec: OPUS
bitrate: 128
allowAccessTo: admin_moderator_speaker
chat:
enabled: true
virtualBackground:
enabled: true
e2ee:
enabled: false
fields=anonymous:
summary: Response containing only anonymous access configuration
value:
anonymous:
moderator:
enabled: true
accessUrl: 'http://localhost:6080/room/room-123?secret=123456'
speaker:
enabled: true
accessUrl: 'http://localhost:6080/room/room-123?secret=654321'
url: 'https://example.com/room/room-123'
_extraFields:
- config

View File

@ -8,12 +8,153 @@ content:
type: array
items:
$ref: '../schemas/meet-room.yaml'
_extraFields:
type: array
description: >
List of extra fields that can be included in the response based on the `X-ExtraFields` header or `extraFields` query parameter.
items:
type: string
example: config
pagination:
$ref: '../schemas/meet-pagination.yaml'
examples:
complete_room_details:
summary: Full room details response with multiple rooms
default_room_details:
summary: Full room details without extra fields for multiple rooms
value:
rooms:
- roomId: 'room-123'
roomName: 'room'
owner: 'admin'
creationDate: 1620000000000
autoDeletionDate: 1900000000000
autoDeletionPolicy:
withMeeting: when_meeting_ends
withRecordings: close
roles:
moderator:
permissions:
canRecord: true
canRetrieveRecordings: true
canDeleteRecordings: true
canJoinMeeting: true
canShareAccessLinks: true
canMakeModerator: true
canKickParticipants: true
canEndMeeting: true
canPublishVideo: true
canPublishAudio: true
canShareScreen: true
canReadChat: true
canWriteChat: true
canChangeVirtualBackground: true
speaker:
permissions:
canRecord: false
canRetrieveRecordings: true
canDeleteRecordings: false
canJoinMeeting: true
canShareAccessLinks: false
canMakeModerator: false
canKickParticipants: false
canEndMeeting: false
canPublishVideo: true
canPublishAudio: true
canShareScreen: true
canReadChat: true
canWriteChat: true
canChangeVirtualBackground: true
access:
anonymous:
moderator:
enabled: true
url: 'https://example.com/room/room-123?secret=123456'
speaker:
enabled: true
url: 'https://example.com/room/room-123?secret=654321'
recording:
enabled: true
url: 'https://example.com/room/room-123/recordings?secret=987654'
registered:
enabled: false
url: 'https://example.com/room/room-123'
status: open
meetingEndAction: none
- roomId: 'room-456'
roomName: 'room'
owner: 'alice_smith'
creationDate: 1620001000000
autoDeletionDate: 1900000000000
autoDeletionPolicy:
withMeeting: when_meeting_ends
withRecordings: close
roles:
moderator:
permissions:
canRecord: true
canRetrieveRecordings: true
canDeleteRecordings: false
canJoinMeeting: true
canShareAccessLinks: true
canMakeModerator: false
canKickParticipants: true
canEndMeeting: true
canPublishVideo: true
canPublishAudio: true
canShareScreen: true
canReadChat: true
canWriteChat: true
canChangeVirtualBackground: true
speaker:
permissions:
canRecord: true
canRetrieveRecordings: true
canDeleteRecordings: false
canJoinMeeting: true
canShareAccessLinks: false
canMakeModerator: false
canKickParticipants: false
canEndMeeting: false
canPublishVideo: true
canPublishAudio: true
canShareScreen: false
canReadChat: true
canWriteChat: true
canChangeVirtualBackground: true
access:
anonymous:
moderator:
enabled: false
url: 'https://example.com/room/room-456?secret=789012'
speaker:
enabled: true
url: 'https://example.com/room/room-456?secret=210987'
recording:
enabled: true
url: 'https://example.com/room/room-456/recordings?secret=345678'
registered:
enabled: true
url: 'https://example.com/room/room-456'
status: open
meetingEndAction: none
_extraFields:
- config
pagination:
isTruncated: false
maxItems: 10
fields=roomId:
summary: Response with only roomId for each room
value:
rooms:
- roomId: 'room-123'
- roomId: 'room-456'
_extraFields:
- config
pagination:
isTruncated: false
maxItems: 10
extraFields=config:
summary: Room details with expanded config
value:
rooms:
- roomId: 'room-123'
@ -29,7 +170,6 @@ content:
enabled: false
layout: grid
encoding: H264_720P_30
allowAccessTo: admin_moderator_speaker
chat:
enabled: true
virtualBackground:
@ -71,14 +211,20 @@ content:
canReadChat: true
canWriteChat: true
canChangeVirtualBackground: true
anonymous:
moderator:
enabled: true
accessUrl: 'http://localhost:6080/room/room-123?secret=123456'
speaker:
enabled: true
accessUrl: 'http://localhost:6080/room/room-123?secret=654321'
accessUrl: 'http://localhost:6080/room/room-123'
access:
anonymous:
moderator:
enabled: true
url: 'https://example.com/room/room-123?secret=123456'
speaker:
enabled: true
url: 'https://example.com/room/room-123?secret=654321'
recording:
enabled: true
url: 'https://example.com/room/room-123/recordings?secret=987654'
registered:
enabled: false
url: 'https://example.com/room/room-123'
status: open
meetingEndAction: none
- roomId: 'room-456'
@ -99,10 +245,13 @@ content:
height: 720
framerate: 60
codec: H264_HIGH
bitrate: 2500
keyFrameInterval: 2
depth: 2
audio:
codec: AAC
bitrate: 192
allowAccessTo: admin_moderator_speaker
frequency: 48000
chat:
enabled: false
virtualBackground:
@ -142,59 +291,59 @@ content:
canReadChat: true
canWriteChat: true
canChangeVirtualBackground: true
anonymous:
moderator:
enabled: false
accessUrl: 'http://localhost:6080/room/room-456?secret=789012'
speaker:
access:
anonymous:
moderator:
enabled: false
url: 'https://example.com/room/room-456?secret=789012'
speaker:
enabled: true
url: 'https://example.com/room/room-456?secret=210987'
recording:
enabled: true
url: 'https://example.com/room/room-456/recordings?secret=345678'
registered:
enabled: true
accessUrl: 'http://localhost:6080/room/room-456?secret=210987'
accessUrl: 'http://localhost:6080/room/room-456'
url: 'https://example.com/room/room-456'
status: open
meetingEndAction: none
_extraFields:
- config
pagination:
isTruncated: false
maxItems: 10
fields=roomId:
summary: Response with only roomId for each room
fields=roomId;extraFields=config:
summary: Room details including config
value:
rooms:
- roomId: 'room-123'
- roomId: 'room-456'
pagination:
isTruncated: false
maxItems: 10
fields=roomId,roomName,creationDate,autoDeletionDate,config:
summary: Room details including config but no URLs
value:
rooms:
- roomId: 'room-123'
roomName: 'room'
creationDate: 1620000000000
autoDeletionDate: 1900000000000
config:
recording:
enabled: false
layout: grid
encoding: H264_720P_30
chat:
enabled: true
virtualBackground:
enabled: true
e2ee:
enabled: false
captions:
enabled: true
- roomId: 'room-456'
roomName: 'room'
creationDate: 1620001000000
autoDeletionDate: 1900000000000
config:
recording:
enabled: true
layout: grid
encoding: H264_720P_30
chat:
enabled: false
virtualBackground:
enabled: false
e2ee:
enabled: false
_extraFields:
- config
pagination:
isTruncated: true
nextPageToken: 'abc123'

View File

@ -1,160 +0,0 @@
description: Room was successfully processed for deletion
content:
application/json:
schema:
type: object
properties:
successCode:
type: string
enum:
- room_deleted
- room_with_active_meeting_deleted
- room_and_recordings_deleted
- room_closed
- room_with_active_meeting_and_recordings_deleted
- room_with_active_meeting_closed
description: A code representing the success scenario
message:
type: string
description: A message providing additional context about the success
room:
$ref: '../schemas/meet-room.yaml'
examples:
room_deleted:
value:
successCode: room_deleted
message: Room 'room-123' deleted successfully
room_with_active_meeting_deleted:
value:
successCode: room_with_active_meeting_deleted
message: Room 'room-123' with active meeting deleted successfully
room_and_recordings_deleted:
value:
successCode: room_and_recordings_deleted
message: Room 'room-123' and its recordings deleted successfully
room_closed:
value:
successCode: room_closed
message: Room 'room-123' has been closed instead of deleted because it has recordings
room:
roomId: room-123
roomName: room
owner: 'admin'
creationDate: 1620000000000
config:
recording:
enabled: false
chat:
enabled: true
virtualBackground:
enabled: true
e2ee:
enabled: false
roles:
moderator:
permissions:
canRecord: true
canRetrieveRecordings: true
canDeleteRecordings: true
canJoinMeeting: true
canShareAccessLinks: true
canMakeModerator: true
canKickParticipants: true
canEndMeeting: true
canPublishVideo: true
canPublishAudio: true
canShareScreen: true
canReadChat: true
canWriteChat: true
canChangeVirtualBackground: true
speaker:
permissions:
canRecord: false
canRetrieveRecordings: true
canDeleteRecordings: false
canJoinMeeting: true
canShareAccessLinks: false
canMakeModerator: false
canKickParticipants: false
canEndMeeting: false
canPublishVideo: true
canPublishAudio: true
canShareScreen: true
canReadChat: true
canWriteChat: true
canChangeVirtualBackground: true
anonymous:
moderator:
enabled: true
accessUrl: 'http://localhost:6080/room/room-123?secret=123456'
speaker:
enabled: true
accessUrl: 'http://localhost:6080/room/room-123?secret=654321'
accessUrl: 'http://localhost:6080/room/room-123'
status: closed
meetingEndAction: none
room_with_active_meeting_and_recordings_deleted:
value:
successCode: room_with_active_meeting_and_recordings_deleted
message: Room 'room-123' with active meeting and its recordings deleted successfully
room_with_active_meeting_closed:
value:
successCode: room_with_active_meeting_closed
message: Room 'room-123' with active meeting has been closed instead of deleted because it has recordings
room:
roomId: room-123
roomName: room
owner: 'admin'
creationDate: 1620000000000
config:
recording:
enabled: false
chat:
enabled: true
virtualBackground:
enabled: true
e2ee:
enabled: false
roles:
moderator:
permissions:
canRecord: true
canRetrieveRecordings: true
canDeleteRecordings: true
canJoinMeeting: true
canShareAccessLinks: true
canMakeModerator: true
canKickParticipants: true
canEndMeeting: true
canPublishVideo: true
canPublishAudio: true
canShareScreen: true
canReadChat: true
canWriteChat: true
canChangeVirtualBackground: true
speaker:
permissions:
canRecord: false
canRetrieveRecordings: true
canDeleteRecordings: false
canJoinMeeting: true
canShareAccessLinks: false
canMakeModerator: false
canKickParticipants: false
canEndMeeting: false
canPublishVideo: true
canPublishAudio: true
canShareScreen: true
canReadChat: true
canWriteChat: true
canChangeVirtualBackground: true
anonymous:
moderator:
enabled: true
accessUrl: 'http://localhost:6080/room/room-123?secret=123456'
speaker:
enabled: true
accessUrl: 'http://localhost:6080/room/room-123?secret=654321'
accessUrl: 'http://localhost:6080/room/room-123'
status: active_meeting
meetingEndAction: close

View File

@ -1,202 +0,0 @@
description: Room was successfully scheduled to be deleted or closed
content:
application/json:
schema:
type: object
properties:
successCode:
type: string
enum:
- room_with_active_meeting_scheduled_to_be_deleted
- room_with_active_meeting_and_recordings_scheduled_to_be_deleted
- room_with_active_meeting_scheduled_to_be_closed
description: A code representing the success scenario
message:
type: string
description: A message providing additional context about the success
room:
$ref: '../schemas/meet-room.yaml'
examples:
room_with_active_meeting_scheduled_to_be_deleted:
value:
successCode: room_with_active_meeting_scheduled_to_be_deleted
message: Room 'room-123' with active meeting scheduled to be deleted when the meeting ends
room:
roomId: room-123
roomName: room
owner: 'admin'
creationDate: 1620000000000
config:
recording:
enabled: false
chat:
enabled: true
virtualBackground:
enabled: true
e2ee:
enabled: false
roles:
moderator:
permissions:
canRecord: true
canRetrieveRecordings: true
canDeleteRecordings: true
canJoinMeeting: true
canShareAccessLinks: true
canMakeModerator: true
canKickParticipants: true
canEndMeeting: true
canPublishVideo: true
canPublishAudio: true
canShareScreen: true
canReadChat: true
canWriteChat: true
canChangeVirtualBackground: true
speaker:
permissions:
canRecord: false
canRetrieveRecordings: true
canDeleteRecordings: false
canJoinMeeting: true
canShareAccessLinks: false
canMakeModerator: false
canKickParticipants: false
canEndMeeting: false
canPublishVideo: true
canPublishAudio: true
canShareScreen: true
canReadChat: true
canWriteChat: true
canChangeVirtualBackground: true
anonymous:
moderator:
enabled: true
accessUrl: 'http://localhost:6080/room/room-123?secret=123456'
speaker:
enabled: true
accessUrl: 'http://localhost:6080/room/room-123?secret=654321'
accessUrl: 'http://localhost:6080/room/room-123'
status: active_meeting
meetingEndAction: delete
room_with_active_meeting_and_recordings_scheduled_to_be_deleted:
value:
successCode: room_with_active_meeting_and_recordings_scheduled_to_be_deleted
message: Room 'room-123' with active meeting and its recordings scheduled to be deleted when the meeting ends
room:
roomId: room-123
roomName: room
owner: 'admin'
creationDate: 1620000000000
config:
recording:
enabled: false
chat:
enabled: true
virtualBackground:
enabled: true
e2ee:
enabled: false
roles:
moderator:
permissions:
canRecord: true
canRetrieveRecordings: true
canDeleteRecordings: true
canJoinMeeting: true
canShareAccessLinks: true
canMakeModerator: true
canKickParticipants: true
canEndMeeting: true
canPublishVideo: true
canPublishAudio: true
canShareScreen: true
canReadChat: true
canWriteChat: true
canChangeVirtualBackground: true
speaker:
permissions:
canRecord: false
canRetrieveRecordings: true
canDeleteRecordings: false
canJoinMeeting: true
canShareAccessLinks: false
canMakeModerator: false
canKickParticipants: false
canEndMeeting: false
canPublishVideo: true
canPublishAudio: true
canShareScreen: true
canReadChat: true
canWriteChat: true
canChangeVirtualBackground: true
anonymous:
moderator:
enabled: true
accessUrl: 'http://localhost:6080/room/room-123?secret=123456'
speaker:
enabled: true
accessUrl: 'http://localhost:6080/room/room-123?secret=654321'
accessUrl: 'http://localhost:6080/room/room-123'
status: active_meeting
meetingEndAction: delete
room_with_active_meeting_scheduled_to_be_closed:
value:
successCode: room_with_active_meeting_scheduled_to_be_closed
message: Room 'room-123' with active meeting scheduled to be closed when the meeting ends because it has recordings
room:
roomId: room-123
roomName: room
owner: 'admin'
creationDate: 1620000000000
config:
chat:
enabled: true
recording:
enabled: false
virtualBackground:
enabled: true
e2ee:
enabled: false
roles:
moderator:
permissions:
canRecord: true
canRetrieveRecordings: true
canDeleteRecordings: true
canJoinMeeting: true
canShareAccessLinks: true
canMakeModerator: true
canKickParticipants: true
canEndMeeting: true
canPublishVideo: true
canPublishAudio: true
canShareScreen: true
canReadChat: true
canWriteChat: true
canChangeVirtualBackground: true
speaker:
permissions:
canRecord: false
canRetrieveRecordings: true
canDeleteRecordings: false
canJoinMeeting: true
canShareAccessLinks: false
canMakeModerator: false
canKickParticipants: false
canEndMeeting: false
canPublishVideo: true
canPublishAudio: true
canShareScreen: true
canReadChat: true
canWriteChat: true
canChangeVirtualBackground: true
anonymous:
moderator:
enabled: true
accessUrl: 'http://localhost:6080/room/room-123?secret=123456'
speaker:
enabled: true
accessUrl: 'http://localhost:6080/room/room-123?secret=654321'
accessUrl: 'http://localhost:6080/room/room-123'
status: active_meeting
meetingEndAction: close

View File

@ -9,6 +9,7 @@ content:
roomName: 'room'
status: 'active'
layout: 'speaker'
encoding: 'H264_720P_30'
filename: 'room-123--XX445.mp4'
startDate: 1600000000000
headers:

View File

@ -15,6 +15,7 @@ content:
roomName: 'room'
status: 'ending'
layout: 'speaker'
encoding: 'H264_720P_30'
filename: 'room-123--XX445.mp4'
startDate: 1600000000000
details: 'End reason: StopEgress API'

View File

@ -1,4 +1,4 @@
description: Success response for updating room anonymous access configuration
description: Success response for updating room access configuration
content:
application/json:
schema:
@ -7,4 +7,4 @@ content:
message:
type: string
example:
message: Anonymous access config for room 'room-123' updated successfully
message: Access config for room 'room-123' updated successfully

View File

@ -7,28 +7,35 @@ properties:
AuthenticationConfig:
type: object
properties:
authMethod:
type: object
properties:
type:
type: string
enum:
- single_user
default: single_user
example: single_user
description: |
Specifies the authentication method used to access the application.
- `single_user`: Only one user account exists, which has administrative privileges and is used for all access.
authModeToAccessRoom:
allowUserCreation:
type: boolean
description: Allow admins to create new user accounts.
example: true
oauthProviders:
type: array
description: Optional list of allowed OAuth providers for user registration.
items:
$ref: '#/OAuthProviderConfig'
OAuthProviderConfig:
type: object
properties:
provider:
type: string
enum:
- none
- moderators_only
- all_users
default: none
example: none
description: |
Specifies who is required to authenticate before accessing a room:
- `none`: No authentication required.
- `moderators_only`: Only moderators need to authenticate.
- `all_users`: All users must authenticate.
- google
- github
description: Supported OAuth provider.
example: google
clientId:
type: string
description: OAuth client ID.
example: your-client-id.apps.googleusercontent.com
clientSecret:
type: string
description: OAuth client secret.
example: your-client-secret
redirectUri:
type: string
description: OAuth redirect URI.
example: https://your-domain.com/auth/callback

View File

@ -10,6 +10,11 @@ properties:
example: 'Alice Smith'
description: |
The display name (profile name) of the user.
registrationDate:
type: number
example: 1620000000000
description: |
The registration date of the user in milliseconds since the Unix epoch.
role:
type: string
enum: ['admin', 'user', 'room_member']

View File

@ -4,17 +4,23 @@ required:
properties:
secret:
type: string
description: A secret key for room access. Determines the member's role.
description: |
A secret key for room access.
Supported secret types:
- Moderator anonymous access secret
- Speaker anonymous access secret
- Recording anonymous access secret (generates a token with read-only recording permissions)
example: 'abc123456'
grantJoinMeetingPermission:
joinMeeting:
type: boolean
description: Whether to grant permission to join the meeting. If true, participantName must be provided.
description: Whether the token is intended for joining a meeting. If true, participantName must be provided.
example: true
participantName:
type: string
description: The name of the participant when joining the meeting. Required if `grantJoinMeetingPermission` is true and this is a new token (not a refresh).
description: The name of the participant when joining the meeting. Required if `joinMeeting` is true.
example: 'Alice'
participantIdentity:
type: string
description: The identity of the participant in the meeting. Required when refreshing an existing token with meeting permissions.
description: The identity of the participant in the meeting. Required when refreshing an existing token used for joining a meeting.
example: 'Alice'

View File

@ -0,0 +1,2 @@
type: string
description: Extra field that can be included in the response if specified in the `X-ExtraFields` header or `extraFields` query parameter.

View File

@ -24,6 +24,7 @@ properties:
description: The status of the recording.
layout:
type: string
enum: ['grid', 'speaker', 'single-speaker']
example: 'grid'
description: The layout of the recording.
encoding:

View File

@ -0,0 +1,46 @@
type: object
properties:
anonymous:
type: object
properties:
moderator:
type: object
properties:
enabled:
type: boolean
default: true
example: true
description: |
Enables or disables anonymous access for the moderator role.
speaker:
type: object
properties:
enabled:
type: boolean
default: true
example: true
description: |
Enables or disables anonymous access for the speaker role.
recording:
type: object
properties:
enabled:
type: boolean
default: true
example: true
description: |
Enables or disables anonymous access for recordings in the room. This also controls whether individual anonymous recording URLs can be generated.
registered:
type: object
properties:
enabled:
type: boolean
default: true
example: true
description: |
Enables or disables access for registered users.
**Note**: admins, owner and members of the room will always have access regardless of this setting.
description: |
Configuration for room access.
All fields are optional. If not specified, current configuration will be maintained.

View File

@ -1,24 +0,0 @@
type: object
properties:
moderator:
type: object
properties:
enabled:
type: boolean
default: true
example: true
description: |
Enables or disables anonymous access for the moderator role.
speaker:
type: object
properties:
enabled:
type: boolean
default: true
example: true
description: |
Enables or disables anonymous access for the speaker role.
description: |
Configuration for anonymous access.
Both moderator and speaker fields are optional. If not specified, current configuration will be maintained.

View File

@ -55,19 +55,6 @@ MeetRecordingConfig:
oneOf:
- $ref: '#/MeetRecordingEncodingPreset'
- $ref: '#/MeetRecordingEncodingOptions'
allowAccessTo:
type: string
enum:
- admin
- admin_moderator
- admin_moderator_speaker
default: admin_moderator_speaker
example: admin_moderator_speaker
description: |
Defines who can access the recording. Options are:
- `admin`: Only administrators can access the recording.
- `admin_moderator`: Administrators and moderators can access the recording.
- `admin_moderator_speaker`: Administrators, moderators and speakers can access the recording.
MeetVirtualBackgroundConfig:
type: object
properties:
@ -188,7 +175,7 @@ MeetRecordingVideoEncodingOptions:
example: 4500
description: |
Video bitrate in kbps
keyframeInterval:
keyFrameInterval:
type: number
minimum: 0
example: 4
@ -253,6 +240,7 @@ MeetRecordingEncodingOptions:
codec: H264_MAIN
bitrate: 3000
keyFrameInterval: 4
depth: 24
audio:
codec: OPUS
bitrate: 128

View File

@ -6,7 +6,7 @@ properties:
description: |
The unique identifier of the room member.
- For internal users: This is set to the userId of the linked Meet user account.
- For registered Meet users: This is set to the userId of the linked Meet user account.
- For external users: This is an automatically generated unique identifier starting from 'ext-'.
name:
type: string
@ -14,8 +14,13 @@ properties:
description: |
The display name for the participant when joining the meeting with this member access.
- For OpenVidu Meet users, this is their profile name.
- For registered Meet users, this is their profile name.
- For external users, this is the assigned name.
membershipDate:
type: number
example: 1620000000000
description: >
The timestamp (in milliseconds since Unix epoch) when this member was added to the room.
accessUrl:
type: string
format: uri
@ -51,3 +56,9 @@ properties:
description: >
The complete set of effective permissions for this member. This object is calculated by applying the customPermissions
overrides to the base role defaults, resulting in the final permissions that will be enforced.
currentParticipantIdentity:
type: string
example: 'alice_smith-1234'
description: |
The identity of the currently connected participant in the meeting associated with this room member.
This value is undefined if the member is not currently connected.

View File

@ -68,12 +68,15 @@ properties:
- Speaker: Permissions to publish audio and video streams.
You can customize this by providing partial permissions for each role (only specify the permissions you want to override).
anonymous:
$ref: meet-room-anonymous-config.yaml
access:
$ref: meet-room-access-config.yaml
description: |
Configuration for anonymous access to the room.
Configuration for room access.
By default (if not specified), anonymous access is enabled for both moderators and speakers.
You can customize this behavior by disabling anonymous access for specific roles (moderator/speaker) with per-role `enabled: false`
By default (if not specified), anonymous access is enabled for both moderator and speaker roles, and for recording access.
However, registered access is disabled by default.
You can customize this behavior by disabling access for specific scopes and roles
(`registered`, `anonymous.moderator`, `anonymous.speaker`, `anonymous.recording`) using `enabled: false`.
Permissions for anonymous users are determined by the room's role permissions.
Permissions for anonymous users are determined by the room's role permissions. For registered users (who are not admins or members of the room),
permissions will be the same as the speaker role.

View File

@ -14,7 +14,7 @@ properties:
type: string
example: 'alice_smith'
description: |
The userId of the internal Meet user who owns this room.
The userId of the registered Meet user who owns this room.
If the room was created by a registered Meet user, this will be their userId.
If the room was created via the REST API using an API key, this will be the userId of the global admin (root user).
@ -30,7 +30,7 @@ properties:
The timestamp (in milliseconds since the Unix epoch) specifying when the room will be automatically deleted.
This must be at least one hour in the future.
After this time, the room is closed to new participants and scheduled for deletion.
After this time, the room is closed to new participants and scheduled for deletion.
It will be removed after the last participant leaves (graceful deletion).
If not set, the room remains active until manually deleted.
@ -63,8 +63,15 @@ properties:
- force: The room and its recordings will be deleted.
- close: The room will be closed instead of deleted, maintaining its recordings.
config:
description: |
Room configuration (chat, recording, virtual background, e2ee, captions).
<br/><br/>
**Excluded from responses by default to reduce payload size.**
To include it in the response:
- **POST requests:** use the `X-ExtraFields: config` header
- **Other requests:** use either the `extraFields=config` query parameter or the `X-ExtraFields: config` header
$ref: meet-room-config.yaml#/MeetRoomConfig
description: The config for the room.
# maxParticipants:
# type: integer
# example: 10
@ -90,49 +97,71 @@ properties:
$ref: meet-permissions.yaml
description: >
The complete set of permissions for the speaker role. These define what speakers can do in the meeting.
anonymous:
access:
description: >
Configuration for anonymous access to the room. Defines which roles have anonymous access enabled and their access URLs.
Access configuration and generated access URLs for the room.
type: object
properties:
moderator:
anonymous:
type: object
properties:
moderator:
type: object
properties:
enabled:
type: boolean
example: true
description: >
Whether anonymous access for the moderator role is enabled.
url:
type: string
format: uri
example: 'http://localhost:6080/room/room-123?secret=123456'
description: >
The URL for anonymous moderators to access the room.
speaker:
type: object
properties:
enabled:
type: boolean
example: true
description: >
Whether anonymous access for the speaker role is enabled.
url:
type: string
format: uri
example: 'http://localhost:6080/room/room-123?secret=654321'
description: >
The URL for anonymous speakers to access the room.
recording:
type: object
properties:
enabled:
type: boolean
example: true
description: >
Whether anonymous access for recordings in the room is enabled.
url:
type: string
format: uri
example: 'http://localhost:6080/room/room-123?secret=987654'
description: >
The URL for anonymous access to the room's recordings.
registered:
type: object
properties:
enabled:
type: boolean
example: true
description: >
Whether anonymous access with moderator role is enabled.
accessUrl:
Whether access for registered users is enabled.
**Note**: admins, owner and members of the room will always have access regardless of this setting.
url:
type: string
format: uri
example: 'http://localhost:6080/room/room-123?secret=123456'
example: 'http://localhost:6080/room/room-123'
description: >
The URL for anonymous moderators to access the room.
speaker:
type: object
properties:
enabled:
type: boolean
example: true
description: >
Whether anonymous access with speaker role is enabled.
accessUrl:
type: string
format: uri
example: 'http://localhost:6080/room/room-123?secret=654321'
description: >
The URL for anonymous speakers to access the room.
accessUrl:
type: string
format: uri
example: 'http://localhost:6080/room/room-123'
description: |
The general access URL for authenticated users to join the room.
This URL should be used by:
- The room owner (internal Meet user who created the room)
- Internal Meet users who are members of the room
The URL for registered users to access the room.
status:
type: string
enum:
@ -142,9 +171,9 @@ properties:
example: open
description: |
The current status of the room. Options are:
- open: The room is open and available to host a meeting.
- active_meeting: There is an active meeting in progress in the room.
- closed: The room is closed to hosting new meetings.
- `open`: The room is open and available to host a meeting.
- `active_meeting`: There is an active meeting in progress in the room.
- `closed`: The room is closed to hosting new meetings.
meetingEndAction:
type: string
enum:
@ -154,6 +183,6 @@ properties:
example: none
description: |
The action to take when the meeting ends. Options are:
- none: No action will be taken.
- close: The room will be closed.
- delete: The room (and its recordings if any) will be deleted.
- `none`: No action will be taken.
- `close`: The room will be closed.
- `delete`: The room (and its recordings if any) will be deleted.

View File

@ -22,9 +22,9 @@ properties:
status:
type: string
description: The status of the recording.
example: active
layout:
type: string
enum: ['grid', 'speaker', 'single-speaker']
description: The layout of the recording.
example: grid
filename:

View File

@ -2,7 +2,7 @@ openapi: 3.1.0
info:
$ref: './info/info.yaml'
servers:
- url: /api/v1
- url: meet/api/v1
description: OpenVidu Meet API
tags:
$ref: './tags/tags.yaml'
@ -17,14 +17,14 @@ paths:
$ref: './paths/rooms.yaml#/~1rooms~1{roomId}~1config'
/rooms/{roomId}/roles:
$ref: './paths/rooms.yaml#/~1rooms~1{roomId}~1roles'
/rooms/{roomId}/anonymous:
$ref: './paths/rooms.yaml#/~1rooms~1{roomId}~1anonymous'
/rooms/{roomId}/access:
$ref: './paths/rooms.yaml#/~1rooms~1{roomId}~1access'
/rooms/{roomId}/status:
$ref: './paths/rooms.yaml#/~1rooms~1{roomId}~1status'
/rooms/{roomId}/members:
$ref: './paths/rooms.yaml#/~1rooms~1{roomId}~1members'
$ref: './paths/room-members.yaml#/~1rooms~1{roomId}~1members'
/rooms/{roomId}/members/{memberId}:
$ref: './paths/rooms.yaml#/~1rooms~1{roomId}~1members~1{memberId}'
$ref: './paths/room-members.yaml#/~1rooms~1{roomId}~1members~1{memberId}'
/recordings:
$ref: './paths/recordings.yaml#/~1recordings'
/recordings/download:

View File

@ -24,6 +24,10 @@ paths:
$ref: './paths/internal/users.yaml#/~1users~1change-password'
/users/{userId}:
$ref: './paths/internal/users.yaml#/~1users~1{userId}'
/users/{userId}/password:
$ref: './paths/internal/users.yaml#/~1users~1{userId}~1password'
/users/{userId}/role:
$ref: './paths/internal/users.yaml#/~1users~1{userId}~1role'
/config/webhooks:
$ref: './paths/internal/meet-global-config.yaml#/~1config~1webhooks'
/config/webhooks/test:
@ -34,8 +38,8 @@ paths:
$ref: './paths/internal/meet-global-config.yaml#/~1config~1rooms~1appearance'
/config/captions:
$ref: './paths/internal/meet-global-config.yaml#/~1config~1captions'
/rooms/{roomId}/token:
$ref: './paths/internal/rooms.yaml#/~1rooms~1{roomId}~1token'
/rooms/{roomId}/members/token:
$ref: './paths/internal/room-members.yaml#/~1rooms~1{roomId}~1members~1token'
/meetings/{roomId}:
$ref: './paths/internal/meetings.yaml#/~1meetings~1{roomId}'
/meetings/{roomId}/participants/{participantIdentity}:

View File

@ -50,6 +50,8 @@
description: AI assistant canceled successfully.
'401':
$ref: '../../components/responses/unauthorized-error.yaml'
'403':
$ref: '../../components/responses/forbidden-error.yaml'
'422':
$ref: '../../components/responses/validation-error.yaml'
'500':

View File

@ -14,6 +14,10 @@
responses:
'201':
$ref: '../../components/responses/internal/success-create-api-key.yaml'
'401':
$ref: '../../components/responses/unauthorized-error.yaml'
'403':
$ref: '../../components/responses/forbidden-error.yaml'
'500':
$ref: '../../components/responses/internal-server-error.yaml'
get:
@ -31,6 +35,10 @@
responses:
'200':
$ref: '../../components/responses/internal/success-get-api-keys.yaml'
'401':
$ref: '../../components/responses/unauthorized-error.yaml'
'403':
$ref: '../../components/responses/forbidden-error.yaml'
'500':
$ref: '../../components/responses/internal-server-error.yaml'
delete:
@ -45,5 +53,9 @@
responses:
'200':
$ref: '../../components/responses/internal/success-delete-api-key.yaml'
'401':
$ref: '../../components/responses/unauthorized-error.yaml'
'403':
$ref: '../../components/responses/forbidden-error.yaml'
'500':
$ref: '../../components/responses/internal-server-error.yaml'

View File

@ -3,7 +3,7 @@
operationId: loginUser
summary: Login to OpenVidu Meet
description: >
Authenticates a user and returns an access and refresh token in cookies.
Authenticates a user and returns an access and refresh token.
tags:
- Internal API - Authentication
requestBody:
@ -22,7 +22,7 @@
operationId: logoutUser
summary: Logout from OpenVidu Meet
description: >
Logs out the user and clears the access and refresh tokens from cookies.
Logs out the user.
tags:
- Internal API - Authentication
responses:
@ -34,7 +34,6 @@
summary: Refresh access token
description: >
Refreshes the access token using the refresh token.
The new access token is returned in a cookie.
tags:
- Internal API - Authentication
security:

View File

@ -113,8 +113,6 @@
$ref: '../../components/responses/unauthorized-error.yaml'
'403':
$ref: '../../components/responses/forbidden-error.yaml'
'404':
$ref: '../../components/responses/internal/error-appearance-config-not-defined.yaml'
'500':
$ref: '../../components/responses/internal-server-error.yaml'
put:

View File

@ -69,5 +69,7 @@
$ref: '../../components/responses/forbidden-error.yaml'
'404':
$ref: '../../components/responses/internal/error-room-participant-not-found.yaml'
'422':
$ref: '../../components/responses/validation-error.yaml'
'500':
$ref: '../../components/responses/internal-server-error.yaml'

View File

@ -1,11 +1,12 @@
/rooms/{roomId}/token:
/rooms/{roomId}/members/token:
post:
operationId: generateRoomMemberToken
summary: Generate room member token
description: >
Generates a token for a user to access an OpenVidu Meet room and its resources.
When using the anonymous recording secret, generated tokens only allow retrieval of room recordings.
tags:
- Internal API - Rooms
- Internal API - Room Members
security:
- accessTokenHeader: []
parameters:

View File

@ -29,15 +29,22 @@
get:
operationId: getUsers
summary: Get all users
description: >
description: |
Retrieves a paginated list of all users in the system.
By default, the users are sorted by registration date in descending order (newest first).
tags:
- Internal API - Users
security:
- accessTokenHeader: []
parameters:
- $ref: '../../components/parameters/internal/user-id-query.yaml'
- $ref: '../../components/parameters/internal/user-name.yaml'
- $ref: '../../components/parameters/internal/user-role.yaml'
- $ref: '../../components/parameters/max-items.yaml'
- $ref: '../../components/parameters/next-page-token.yaml'
- $ref: '../../components/parameters/internal/user-sort-field.yaml'
- $ref: '../../components/parameters/sort-order.yaml'
responses:
'200':
$ref: '../../components/responses/internal/success-get-users.yaml'
@ -49,6 +56,30 @@
$ref: '../../components/responses/validation-error.yaml'
'500':
$ref: '../../components/responses/internal-server-error.yaml'
delete:
operationId: bulkDeleteUsers
summary: Bulk delete users
description: |
Deletes multiple users at once by their userIds.
tags:
- Internal API - Users
security:
- accessTokenHeader: []
parameters:
- $ref: '../../components/parameters/internal/user-ids.yaml'
responses:
'200':
$ref: '../../components/responses/internal/success-bulk-delete-users.yaml'
'400':
$ref: '../../components/responses/internal/error-bulk-delete-users.yaml'
'401':
$ref: '../../components/responses/unauthorized-error.yaml'
'403':
$ref: '../../components/responses/forbidden-error.yaml'
'422':
$ref: '../../components/responses/validation-error.yaml'
'500':
$ref: '../../components/responses/internal-server-error.yaml'
/users/me:
get:
operationId: getMe
@ -98,7 +129,7 @@
security:
- accessTokenHeader: []
parameters:
- $ref: '../../components/parameters/internal/userId-path.yaml'
- $ref: '../../components/parameters/internal/user-id-path.yaml'
responses:
'200':
$ref: '../../components/responses/internal/success-get-user.yaml'
@ -108,8 +139,6 @@
$ref: '../../components/responses/forbidden-error.yaml'
'404':
$ref: '../../components/responses/internal/error-user-not-found.yaml'
'422':
$ref: '../../components/responses/validation-error.yaml'
'500':
$ref: '../../components/responses/internal-server-error.yaml'
delete:
@ -120,12 +149,14 @@
This operation will remove the user account and may affect rooms and resources
associated with this user.
> **Note:** Cannot delete your own user account or the root admin user.
tags:
- Internal API - Users
security:
- accessTokenHeader: []
parameters:
- $ref: '../../components/parameters/internal/userId-path.yaml'
- $ref: '../../components/parameters/internal/user-id-path.yaml'
responses:
'200':
$ref: '../../components/responses/internal/success-delete-user.yaml'
@ -135,6 +166,62 @@
$ref: '../../components/responses/forbidden-error.yaml'
'404':
$ref: '../../components/responses/internal/error-user-not-found.yaml'
'500':
$ref: '../../components/responses/internal-server-error.yaml'
/users/{userId}/password:
put:
operationId: resetUserPassword
summary: Reset user password
description: |
Allows an admin to reset the password of a specific user.
> **Note:** Cannot reset your own password using this endpoint. Use the `change-password` endpoint instead.
tags:
- Internal API - Users
security:
- accessTokenHeader: []
parameters:
- $ref: '../../components/parameters/internal/user-id-path.yaml'
requestBody:
$ref: '../../components/requestBodies/internal/reset-password-request.yaml'
responses:
'200':
$ref: '../../components/responses/internal/success-reset-password.yaml'
'401':
$ref: '../../components/responses/unauthorized-error.yaml'
'403':
$ref: '../../components/responses/forbidden-error.yaml'
'404':
$ref: '../../components/responses/internal/error-user-not-found.yaml'
'422':
$ref: '../../components/responses/validation-error.yaml'
'500':
$ref: '../../components/responses/internal-server-error.yaml'
/users/{userId}/role:
put:
operationId: updateUserRole
summary: Update user role
description: |
Allows an admin to change the role of a specific user.
> **Note:** Cannot change your own role or the role of the root admin user.
tags:
- Internal API - Users
security:
- accessTokenHeader: []
parameters:
- $ref: '../../components/parameters/internal/user-id-path.yaml'
requestBody:
$ref: '../../components/requestBodies/internal/update-user-role-request.yaml'
responses:
'200':
$ref: '../../components/responses/internal/success-update-user-role.yaml'
'401':
$ref: '../../components/responses/unauthorized-error.yaml'
'403':
$ref: '../../components/responses/forbidden-error.yaml'
'404':
$ref: '../../components/responses/internal/error-user-not-found.yaml'
'422':
$ref: '../../components/responses/validation-error.yaml'
'500':

View File

@ -17,6 +17,8 @@
security:
- apiKeyHeader: []
- roomMemberTokenHeader: []
parameters:
- $ref: '../components/parameters/recording-x-fields-header.yaml'
requestBody:
$ref: '../components/requestBodies/start-recording-request.yaml'
responses:
@ -41,13 +43,14 @@
operationId: getRecordings
summary: Get all recordings
description: |
Retrieves a paginated list of all recordings available in the system.
Retrieves a paginated list of recordings available in the system.
You can apply filters to narrow down the results based on specific criteria.
By default, the recordings are sorted by start date in descending order (newest first).
> **Note:** If this endpoint is called using the `roomMemberTokenHeader` authentication method,
> the `roomId` filter will be ignored and only recordings associated with the room included in the token will be returned.
> **Note:**
> - When using `accessTokenHeader` authentication: Only recordings the authenticated user has access to will be returned.
> - When using `roomMemberTokenHeader` authentication: The `roomId` filter is ignored and only recordings associated with the room included in the token will be returned.
tags:
- OpenVidu Meet - Recordings
security:
@ -55,13 +58,14 @@
- accessTokenHeader: []
- roomMemberTokenHeader: []
parameters:
- $ref: '../components/parameters/recording-x-fields-header.yaml'
- $ref: '../components/parameters/room-id-query.yaml'
- $ref: '../components/parameters/room-name.yaml'
- $ref: '../components/parameters/recording-status.yaml'
- $ref: '../components/parameters/recording-fields.yaml'
- $ref: '../components/parameters/max-items.yaml'
- $ref: '../components/parameters/next-page-token.yaml'
- $ref: '../components/parameters/sort-field.yaml'
- $ref: '../components/parameters/recording-sort-field.yaml'
- $ref: '../components/parameters/sort-order.yaml'
responses:
'200':
@ -81,9 +85,9 @@
description: |
Deletes multiple recordings at once with the specified recording IDs.
> **Note:** If this endpoint is called using the `roomMemberTokenHeader` authentication method,
> all specified recordings must belong to the same room included in the token.
> If a recording does not belong to that room, it will not be deleted.
> **Note:**
> - When using `accessTokenHeader` authentication: Only deletes recordings the authenticated user has access to.
> - When using `roomMemberTokenHeader` authentication: Only deletes recordings associated with the room included in the token.
tags:
- OpenVidu Meet - Recordings
security:
@ -114,9 +118,9 @@
Downloads multiple recordings as a ZIP file with the specified recording IDs.
The ZIP file will contain all recordings in MP4 format.
> **Note:** If this endpoint is called using the `roomMemberTokenHeader` authentication method,
> all specified recordings must belong to the same room included in the token.
> If a recording does not belong to that room, it will not be included in the ZIP file.
> **Note:**
> - When using `accessTokenHeader` authentication: Only includes recordings the authenticated user has access to.
> - When using `roomMemberTokenHeader` authentication: Only includes recordings associated with the room included in the token.
tags:
- OpenVidu Meet - Recordings
security:
@ -142,7 +146,7 @@
type: string
format: binary
'400':
$ref: '../components/responses/error-recordings-not-same-room.yaml'
$ref: '../components/responses/error-recordings-zip-empty.yaml'
'401':
$ref: '../components/responses/unauthorized-error.yaml'
'403':
@ -167,6 +171,8 @@
parameters:
- $ref: '../components/parameters/recording-id.yaml'
- $ref: '../components/parameters/recording-secret.yaml'
- $ref: '../components/parameters/recording-x-fields-header.yaml'
- $ref: '../components/parameters/recording-fields.yaml'
responses:
'200':
$ref: '../components/responses/success-get-recording.yaml'
@ -302,6 +308,7 @@
- roomMemberTokenHeader: []
parameters:
- $ref: '../components/parameters/recording-id.yaml'
- $ref: '../components/parameters/recording-x-fields-header.yaml'
responses:
'202':
$ref: '../components/responses/success-stop-recording.yaml'

View File

@ -0,0 +1,185 @@
/rooms/{roomId}/members:
post:
operationId: addRoomMember
summary: Add a member to a room
description: |
Adds a new member to the specified room with custom permissions.
Each member receives a unique access URL that is different from the moderator and speaker URLs.
The member's permissions are based on a base role (moderator or speaker) with optional overrides.
This allows fine-grained control over what each specific participant can do in the meeting.
tags:
- OpenVidu Meet - Room Members
security:
- apiKeyHeader: []
- accessTokenHeader: []
parameters:
- $ref: '../components/parameters/room-id-path.yaml'
requestBody:
$ref: '../components/requestBodies/add-room-member-request.yaml'
responses:
'201':
$ref: '../components/responses/success-add-room-member.yaml'
'401':
$ref: '../components/responses/unauthorized-error.yaml'
'403':
$ref: '../components/responses/forbidden-error.yaml'
'404':
$ref: '../components/responses/error-room-not-found.yaml'
'409':
$ref: '../components/responses/error-room-member-conflict.yaml'
'422':
$ref: '../components/responses/validation-error.yaml'
'500':
$ref: '../components/responses/internal-server-error.yaml'
get:
operationId: getRoomMembers
summary: Get all members of a room
description: |
Retrieves a paginated list of all members in the specified room.
Each member has their own access URL and custom permissions that can differ from the default moderator and speaker roles.
By default, the room members are sorted by membership date in descending order (newest first).
tags:
- OpenVidu Meet - Room Members
security:
- apiKeyHeader: []
- accessTokenHeader: []
parameters:
- $ref: '../components/parameters/room-id-path.yaml'
- $ref: '../components/parameters/room-member-name.yaml'
- $ref: '../components/parameters/room-member-fields.yaml'
- $ref: '../components/parameters/max-items.yaml'
- $ref: '../components/parameters/next-page-token.yaml'
- $ref: '../components/parameters/room-member-sort-field.yaml'
- $ref: '../components/parameters/sort-order.yaml'
responses:
'200':
$ref: '../components/responses/success-get-room-members.yaml'
'401':
$ref: '../components/responses/unauthorized-error.yaml'
'403':
$ref: '../components/responses/forbidden-error.yaml'
'404':
$ref: '../components/responses/error-room-not-found.yaml'
'422':
$ref: '../components/responses/validation-error.yaml'
'500':
$ref: '../components/responses/internal-server-error.yaml'
delete:
operationId: bulkDeleteRoomMembers
summary: Bulk delete room members
description: |
Deletes multiple members from the room at once by their member IDs.
tags:
- OpenVidu Meet - Room Members
security:
- apiKeyHeader: []
- accessTokenHeader: []
parameters:
- $ref: '../components/parameters/room-id-path.yaml'
- $ref: '../components/parameters/room-member-ids.yaml'
responses:
'200':
$ref: '../components/responses/success-bulk-delete-room-members.yaml'
'400':
$ref: '../components/responses/error-bulk-delete-room-members.yaml'
'401':
$ref: '../components/responses/unauthorized-error.yaml'
'403':
$ref: '../components/responses/forbidden-error.yaml'
'404':
$ref: '../components/responses/error-room-not-found.yaml'
'422':
$ref: '../components/responses/validation-error.yaml'
'500':
$ref: '../components/responses/internal-server-error.yaml'
/rooms/{roomId}/members/{memberId}:
get:
operationId: getRoomMember
summary: Get a room member
description: >
Retrieves the details of a specific room member by their member ID.
tags:
- OpenVidu Meet - Room Members
security:
- apiKeyHeader: []
- accessTokenHeader: []
- roomMemberTokenHeader: []
parameters:
- $ref: '../components/parameters/room-id-path.yaml'
- $ref: '../components/parameters/room-member-id.yaml'
responses:
'200':
$ref: '../components/responses/success-get-room-member.yaml'
'401':
$ref: '../components/responses/unauthorized-error.yaml'
'403':
$ref: '../components/responses/forbidden-error.yaml'
'404':
$ref: '../components/responses/error-room-member-not-found.yaml'
'422':
$ref: '../components/responses/validation-error.yaml'
'500':
$ref: '../components/responses/internal-server-error.yaml'
put:
operationId: updateRoomMember
summary: Update a room member
description: |
Updates the permissions and/or base role of a specific room member.
You can modify the member's base role and custom permission overrides.
The effective permissions will be recalculated based on the new base role and custom permissions.
tags:
- OpenVidu Meet - Room Members
security:
- apiKeyHeader: []
- accessTokenHeader: []
parameters:
- $ref: '../components/parameters/room-id-path.yaml'
- $ref: '../components/parameters/room-member-id.yaml'
requestBody:
$ref: '../components/requestBodies/update-room-member-request.yaml'
responses:
'200':
$ref: '../components/responses/success-update-room-member.yaml'
'401':
$ref: '../components/responses/unauthorized-error.yaml'
'403':
$ref: '../components/responses/forbidden-error.yaml'
'404':
$ref: '../components/responses/error-room-member-not-found.yaml'
'422':
$ref: '../components/responses/validation-error.yaml'
'500':
$ref: '../components/responses/internal-server-error.yaml'
delete:
operationId: deleteRoomMember
summary: Delete a room member
description: |
Removes a member from the specified room, revoking their access.
If the member is currently in an active meeting, they will be immediately kicked out.
The member's access URL will no longer be valid after deletion.
tags:
- OpenVidu Meet - Room Members
security:
- apiKeyHeader: []
- accessTokenHeader: []
parameters:
- $ref: '../components/parameters/room-id-path.yaml'
- $ref: '../components/parameters/room-member-id.yaml'
responses:
'200':
$ref: '../components/responses/success-delete-room-member.yaml'
'401':
$ref: '../components/responses/unauthorized-error.yaml'
'403':
$ref: '../components/responses/forbidden-error.yaml'
'404':
$ref: '../components/responses/error-room-member-not-found.yaml'
'422':
$ref: '../components/responses/validation-error.yaml'
'500':
$ref: '../components/responses/internal-server-error.yaml'

View File

@ -2,14 +2,16 @@
post:
operationId: createRoom
summary: Create a room
description: >
Creates a new OpenVidu Meet room.
The room will be available for participants to join using the generated URLs.
description: |
Creates a new OpenVidu Meet room using the provided data and returns the room details along with the generated participant URLs.
tags:
- OpenVidu Meet - Rooms
security:
- apiKeyHeader: []
- accessTokenHeader: []
parameters:
- $ref: '../components/parameters/room-x-fields-header.yaml'
- $ref: '../components/parameters/room-x-extra-fields-header.yaml'
requestBody:
$ref: '../components/requestBodies/create-room-request.yaml'
responses:
@ -26,23 +28,26 @@
get:
operationId: getRooms
summary: Get all rooms
description: >
Retrieves a paginated list of all rooms available in the system.
You can apply filters to narrow down the results based on specific criteria.
description: |
Retrieves a paginated list of rooms in the system, optionally filtered by criteria.
By default, rooms are sorted by creation date, newest first.
By default, the rooms are sorted by creation date in descending order (newest first).
> Only rooms accessible to the authenticated user are returned when using the `accessTokenHeader` authentication method.
tags:
- OpenVidu Meet - Rooms
security:
- apiKeyHeader: []
- accessTokenHeader: []
parameters:
- $ref: '../components/parameters/room-x-extra-fields-header.yaml'
- $ref: '../components/parameters/room-x-fields-header.yaml'
- $ref: '../components/parameters/room-name.yaml'
- $ref: '../components/parameters/room-status.yaml'
- $ref: '../components/parameters/room-fields.yaml'
- $ref: '../components/parameters/room-extra-fields.yaml'
- $ref: '../components/parameters/max-items.yaml'
- $ref: '../components/parameters/next-page-token.yaml'
- $ref: '../components/parameters/sort-field.yaml'
- $ref: '../components/parameters/room-sort-field.yaml'
- $ref: '../components/parameters/sort-order.yaml'
responses:
'200':
@ -59,20 +64,25 @@
operationId: bulkDeleteRooms
summary: Bulk delete rooms
description: |
Delete multiple OpenVidu Meet rooms at once with the specified room IDs.
Deletes multiple OpenVidu Meet rooms using the specified room IDs.
<br/><br/>
Deletion behavior depends on the `withMeeting` and `withRecordings` policies:
- Rooms may be deleted or closed immediately
- Deletion may be scheduled to occur after ongoing meetings end
- The operation may fail if deletion is not permitted
If any of the rooms have active meetings or recordings,
deletion behavior is determined by the provided `withMeeting` and `withRecordings` deletion policies.
Depending on these policies, the rooms may be deleted/closed immediately, scheduled to be deleted/closed once the meetings end,
or the operation may fail if deletion is not permitted.
> Only rooms the authenticated user can manage are affected when using the `accessTokenHeader` authentication method.
tags:
- OpenVidu Meet - Rooms
security:
- apiKeyHeader: []
- accessTokenHeader: []
parameters:
- $ref: '../components/parameters/room-x-extra-fields-header.yaml'
- $ref: '../components/parameters/room-x-fields-header.yaml'
- $ref: '../components/parameters/room-ids.yaml'
- $ref: '../components/parameters/room-fields.yaml'
- $ref: '../components/parameters/room-extra-fields.yaml'
- $ref: '../components/parameters/meeting-deletion-policy.yaml'
- $ref: '../components/parameters/recordings-deletion-policy.yaml'
responses:
@ -92,8 +102,9 @@
get:
operationId: getRoom
summary: Get a room
description: >
Retrieves the details of an OpenVidu Meet room with the specified room ID.
description: |
Retrieves the details of an OpenVidu Meet room by its room ID.
tags:
- OpenVidu Meet - Rooms
security:
@ -102,7 +113,10 @@
- roomMemberTokenHeader: []
parameters:
- $ref: '../components/parameters/room-id-path.yaml'
- $ref: '../components/parameters/room-x-fields-header.yaml'
- $ref: '../components/parameters/room-x-extra-fields-header.yaml'
- $ref: '../components/parameters/room-fields.yaml'
- $ref: '../components/parameters/room-extra-fields.yaml'
responses:
'200':
$ref: '../components/responses/success-get-room.yaml'
@ -122,25 +136,28 @@
description: |
Deletes the specified OpenVidu Meet room by its room ID.
If the room has an active meeting or existing recordings,
deletion behavior is determined by the provided `withMeeting` and `withRecordings` deletion policies.
Depending on these policies, the room may be deleted/closed immediately, scheduled to be deleted/closed once the meeting ends,
or the operation may fail if deletion is not permitted.
Deletion behavior depends on the `withMeeting` and `withRecordings` policies:
- The room may be deleted or closed immediately
- Deletion may be scheduled to occur after the meeting ends
- The operation may fail if deletion is not allowed
tags:
- OpenVidu Meet - Rooms
security:
- apiKeyHeader: []
- accessTokenHeader: []
parameters:
- $ref: '../components/parameters/room-x-fields-header.yaml'
- $ref: '../components/parameters/room-x-extra-fields-header.yaml'
- $ref: '../components/parameters/room-id-path.yaml'
- $ref: '../components/parameters/meeting-deletion-policy.yaml'
- $ref: '../components/parameters/recordings-deletion-policy.yaml'
- $ref: '../components/parameters/room-fields.yaml'
- $ref: '../components/parameters/room-extra-fields.yaml'
responses:
'200':
$ref: '../components/responses/success-room-process-deletion.yaml'
$ref: '../components/responses/success-delete-room-processed.yaml'
'202':
$ref: '../components/responses/success-room-schedule-deletion.yaml'
$ref: '../components/responses/success-delete-room-scheduled.yaml'
'401':
$ref: '../components/responses/unauthorized-error.yaml'
'403':
@ -267,18 +284,21 @@
$ref: '../components/responses/forbidden-error.yaml'
'404':
$ref: '../components/responses/error-room-not-found.yaml'
'409':
$ref: '../components/responses/error-room-active-meeting.yaml'
'422':
$ref: '../components/responses/validation-error.yaml'
'500':
$ref: '../components/responses/internal-server-error.yaml'
/rooms/{roomId}/anonymous:
/rooms/{roomId}/access:
put:
operationId: updateRoomAnonymous
summary: Update anonymous access config for a room
operationId: updateRoomAccess
summary: Update access config for a room
description: |
Updates the anonymous access configuration for the specified room.
Updates the access configuration for the specified room.
This allows you to enable or disable anonymous access for specific roles (moderator/speaker).
This allows you to enable or disable access for registered users and anonymous roles
(moderator/speaker/recording).
tags:
- OpenVidu Meet - Rooms
security:
@ -287,161 +307,18 @@
parameters:
- $ref: '../components/parameters/room-id-path.yaml'
requestBody:
$ref: '../components/requestBodies/update-room-anonymous-request.yaml'
$ref: '../components/requestBodies/update-room-access-request.yaml'
responses:
'200':
$ref: '../components/responses/success-update-room-anonymous.yaml'
$ref: '../components/responses/success-update-room-access.yaml'
'401':
$ref: '../components/responses/unauthorized-error.yaml'
'403':
$ref: '../components/responses/forbidden-error.yaml'
'404':
$ref: '../components/responses/error-room-not-found.yaml'
'422':
$ref: '../components/responses/validation-error.yaml'
'500':
$ref: '../components/responses/internal-server-error.yaml'
/rooms/{roomId}/members:
post:
operationId: addRoomMember
summary: Add a member to a room
description: |
Adds a new member to the specified room with custom permissions.
Each member receives a unique access URL that is different from the moderator and speaker URLs.
The member's permissions are based on a base role (moderator or speaker) with optional overrides.
This allows fine-grained control over what each specific participant can do in the meeting.
tags:
- OpenVidu Meet - Rooms
security:
- apiKeyHeader: []
parameters:
- $ref: '../components/parameters/room-id-path.yaml'
requestBody:
$ref: '../components/requestBodies/add-room-member-request.yaml'
responses:
'201':
$ref: '../components/responses/success-add-room-member.yaml'
'401':
$ref: '../components/responses/unauthorized-error.yaml'
'403':
$ref: '../components/responses/forbidden-error.yaml'
'404':
$ref: '../components/responses/error-room-not-found.yaml'
'422':
$ref: '../components/responses/validation-error.yaml'
'500':
$ref: '../components/responses/internal-server-error.yaml'
get:
operationId: getRoomMembers
summary: Get all members of a room
description: >
Retrieves a paginated list of all members in the specified room.
Each member has custom access URLs and permissions that can differ from the default moderator and speaker roles.
tags:
- OpenVidu Meet - Rooms
security:
- apiKeyHeader: []
parameters:
- $ref: '../components/parameters/room-id-path.yaml'
- $ref: '../components/parameters/room-member-fields.yaml'
- $ref: '../components/parameters/max-items.yaml'
- $ref: '../components/parameters/next-page-token.yaml'
responses:
'200':
$ref: '../components/responses/success-get-room-members.yaml'
'401':
$ref: '../components/responses/unauthorized-error.yaml'
'403':
$ref: '../components/responses/forbidden-error.yaml'
'404':
$ref: '../components/responses/error-room-not-found.yaml'
'422':
$ref: '../components/responses/validation-error.yaml'
'500':
$ref: '../components/responses/internal-server-error.yaml'
/rooms/{roomId}/members/{memberId}:
get:
operationId: getRoomMember
summary: Get a room member
description: >
Retrieves the details of a specific room member by their member ID.
tags:
- OpenVidu Meet - Rooms
security:
- apiKeyHeader: []
- roomMemberTokenHeader: []
parameters:
- $ref: '../components/parameters/room-id-path.yaml'
- $ref: '../components/parameters/member-id-path.yaml'
responses:
'200':
$ref: '../components/responses/success-get-room-member.yaml'
'401':
$ref: '../components/responses/unauthorized-error.yaml'
'403':
$ref: '../components/responses/forbidden-error.yaml'
'404':
$ref: '../components/responses/error-room-member-not-found.yaml'
'422':
$ref: '../components/responses/validation-error.yaml'
'500':
$ref: '../components/responses/internal-server-error.yaml'
put:
operationId: updateRoomMember
summary: Update a room member
description: |
Updates the permissions and/or base role of a specific room member.
You can modify the member's base role and custom permission overrides.
The effective permissions will be recalculated based on the new base role and custom permissions.
tags:
- OpenVidu Meet - Rooms
security:
- apiKeyHeader: []
parameters:
- $ref: '../components/parameters/room-id-path.yaml'
- $ref: '../components/parameters/member-id-path.yaml'
requestBody:
$ref: '../components/requestBodies/update-room-member-request.yaml'
responses:
'200':
$ref: '../components/responses/success-update-room-member.yaml'
'401':
$ref: '../components/responses/unauthorized-error.yaml'
'403':
$ref: '../components/responses/forbidden-error.yaml'
'404':
$ref: '../components/responses/error-room-member-not-found.yaml'
'422':
$ref: '../components/responses/validation-error.yaml'
'500':
$ref: '../components/responses/internal-server-error.yaml'
delete:
operationId: deleteRoomMember
summary: Delete a room member
description: |
Removes a member from the specified room, revoking their access.
If the member is currently in an active meeting, they will be immediately kicked out.
The member's access URL will no longer be valid after deletion.
tags:
- OpenVidu Meet - Rooms
security:
- apiKeyHeader: []
parameters:
- $ref: '../components/parameters/room-id-path.yaml'
- $ref: '../components/parameters/member-id-path.yaml'
responses:
'200':
$ref: '../components/responses/success-delete-room-member.yaml'
'401':
$ref: '../components/responses/unauthorized-error.yaml'
'403':
$ref: '../components/responses/forbidden-error.yaml'
'404':
$ref: '../components/responses/error-room-member-not-found.yaml'
'409':
$ref: '../components/responses/error-room-active-meeting.yaml'
'422':
$ref: '../components/responses/validation-error.yaml'
'500':

View File

@ -1,5 +1,7 @@
- name: OpenVidu Meet - Rooms
description: Operations related to managing OpenVidu Meet rooms
- name: OpenVidu Meet - Room Members
description: Operations related to managing members within OpenVidu Meet rooms
- name: OpenVidu Meet - Recordings
description: Operations related to managing OpenVidu Meet recordings
- name: Internal API - Authentication
@ -12,8 +14,8 @@
description: Operations related to managing users in OpenVidu Meet
- name: Internal API - Global Config
description: Operations related to managing global config in OpenVidu Meet
- name: Internal API - Rooms
description: Operations related to managing OpenVidu Meet rooms
- name: Internal API - Room Members
description: Operations related to managing members within OpenVidu Meet rooms
- name: Internal API - Meetings
description: Operations related to managing meetings in OpenVidu Meet rooms
- name: Internal API - AI Assistants

View File

@ -35,8 +35,9 @@
"start:dev": "NODE_ENV=development concurrently -k -n server,typecheck -c cyan,yellow \"pnpm tsx watch --clear-screen=false --include src ./src/server.ts\" \"pnpm run dev:typecheck\"",
"dev:typecheck": "node ../../scripts/dev/backend-type-checker.mjs",
"package:build": "pnpm run build:prod && pnpm pack",
"test:integration-room-management": "node --experimental-vm-modules ../../node_modules/jest/bin/jest.js --runInBand --forceExit --testPathPattern \"tests/integration/api/(rooms|meetings)\" --ci --reporters=default --reporters=jest-junit",
"test:integration-room-management": "node --experimental-vm-modules ../../node_modules/jest/bin/jest.js --runInBand --forceExit --testPathPattern \"tests/integration/api/(rooms|room-members|meetings)\" --ci --reporters=default --reporters=jest-junit",
"test:integration-rooms": "node --experimental-vm-modules ../../node_modules/jest/bin/jest.js --runInBand --forceExit --testPathPattern 'tests/integration/api/rooms' --ci --reporters=default --reporters=jest-junit",
"test:integration-room-members": "node --experimental-vm-modules ../../node_modules/jest/bin/jest.js --runInBand --forceExit --testPathPattern 'tests/integration/api/rooms-members' --ci --reporters=default --reporters=jest-junit",
"test:integration-meetings": "node --experimental-vm-modules ../../node_modules/.bin/jest --runInBand --forceExit --testPathPattern \"tests/integration/api/meetings\" --ci --reporters=default --reporters=jest-junit",
"test:integration-webhooks": "node --experimental-vm-modules ../../node_modules/.bin/jest --runInBand --forceExit --testPathPattern \"tests/integration/webhooks\" --ci --reporters=default --reporters=jest-junit",
"test:integration-auth-security": "node --experimental-vm-modules ../../node_modules/.bin/jest --runInBand --forceExit --testPathPattern \"tests/integration/api/(security|auth|api-keys|users)\" --ci --reporters=default --reporters=jest-junit",
@ -54,30 +55,30 @@
"clean": "rm -rf node_modules dist public test-results"
},
"dependencies": {
"@aws-sdk/client-s3": "3.846.0",
"@azure/storage-blob": "12.27.0",
"@google-cloud/storage": "7.17.3",
"@aws-sdk/client-s3": "3.995.0",
"@azure/storage-blob": "12.31.0",
"@google-cloud/storage": "7.19.0",
"@openvidu-meet/typings": "workspace:*",
"@sesamecare-oss/redlock": "1.4.0",
"archiver": "7.0.1",
"bcrypt": "5.1.1",
"body-parser": "2.2.0",
"body-parser": "2.2.2",
"chalk": "5.6.2",
"cookie-parser": "1.4.7",
"cors": "2.8.5",
"cron": "4.3.5",
"cors": "2.8.6",
"cron": "4.4.0",
"dotenv": "16.6.1",
"express": "4.21.2",
"express": "5.2.1",
"express-rate-limit": "7.5.1",
"inversify": "6.2.2",
"ioredis": "5.6.1",
"jwt-decode": "4.0.0",
"livekit-server-sdk": "2.13.3",
"lodash.merge": "4.6.2",
"mongoose": "8.19.4",
"mongoose": "9.2.2",
"ms": "2.1.3",
"uid": "2.0.2",
"winston": "3.18.3",
"winston": "3.19.0",
"yamljs": "0.3.0",
"zod": "3.25.76"
},
@ -86,7 +87,7 @@
"@types/bcrypt": "5.0.2",
"@types/cookie-parser": "1.4.9",
"@types/cors": "2.8.19",
"@types/express": "4.17.25",
"@types/express": "5.0.6",
"@types/jest": "29.5.14",
"@types/lodash.merge": "4.6.9",
"@types/ms": "2.1.0",

View File

@ -7,6 +7,7 @@ import { GlobalConfigRepository } from '../repositories/global-config.repository
import { MigrationRepository } from '../repositories/migration.repository.js';
import { RecordingRepository } from '../repositories/recording.repository.js';
import { RoomRepository } from '../repositories/room.repository.js';
import { RoomMemberRepository } from '../repositories/room-member.repository.js';
import { UserRepository } from '../repositories/user.repository.js';
/*
@ -86,6 +87,7 @@ export const registerDependencies = () => {
container.bind(MongoDBService).toSelf().inSingletonScope();
container.bind(BaseRepository).toSelf().inSingletonScope();
container.bind(RoomRepository).toSelf().inSingletonScope();
container.bind(RoomMemberRepository).toSelf().inSingletonScope();
container.bind(UserRepository).toSelf().inSingletonScope();
container.bind(ApiKeyRepository).toSelf().inSingletonScope();
container.bind(GlobalConfigRepository).toSelf().inSingletonScope();

View File

@ -16,10 +16,7 @@ export const INTERNAL_CONFIG = {
ACCESS_TOKEN_EXPIRATION: '2h',
REFRESH_TOKEN_EXPIRATION: '1d',
ROOM_MEMBER_TOKEN_EXPIRATION: '2h',
// Authentication usernames
ANONYMOUS_USER: 'anonymous',
API_USER: 'api-user',
PASSWORD_CHANGE_TOKEN_EXPIRATION: '15m',
// S3 configuration
S3_MAX_RETRIES_ATTEMPTS_ON_SAVE_ERROR: '5',
@ -50,16 +47,18 @@ export const INTERNAL_CONFIG = {
PARTICIPANT_NAME_RESERVATION_TTL: '12h' as StringValue, // Time-to-live for participant name reservations
CAPTIONS_AGENT_NAME: 'speech-processing',
ASSISTANT_STATE_LOCK_TTL: '15s' as StringValue, // Redis lock TTL for AI assistant state (start/stop operations)
// MongoDB Schema Versions
// These define the current schema version for each collection
// Increment when making breaking changes to the schema structure
// IMPORTANT: whenever you increment a schema version, update the MIGRATION_REV timestamp too.
// This helps surface merge conflicts when multiple branches create schema migrations concurrently.
GLOBAL_CONFIG_SCHEMA_VERSION: 1 as SchemaVersion, // MIGRATION_REV: 1771328577054
USER_SCHEMA_VERSION: 1 as SchemaVersion, // MIGRATION_REV: 1771328577054
GLOBAL_CONFIG_SCHEMA_VERSION: 2 as SchemaVersion, // MIGRATION_REV: 1771580869366
USER_SCHEMA_VERSION: 2 as SchemaVersion, // MIGRATION_REV: 1771580869366
API_KEY_SCHEMA_VERSION: 1 as SchemaVersion, // MIGRATION_REV: 1771328577054
ROOM_SCHEMA_VERSION: 2 as SchemaVersion, // MIGRATION_REV: 1771328577054
ROOM_SCHEMA_VERSION: 3 as SchemaVersion, // MIGRATION_REV: 1771580869366
ROOM_MEMBER_SCHEMA_VERSION: 1 as SchemaVersion, // MIGRATION_REV: 1771328577054
RECORDING_SCHEMA_VERSION: 2 as SchemaVersion // MIGRATION_REV: 1771328577054
};

View File

@ -1,42 +1,26 @@
import { Request, Response } from 'express';
import { container } from '../config/dependency-injector.config.js';
import { handleError } from '../models/error.model.js';
import { errorInsufficientPermissions, handleError, rejectRequestFromMeetError } from '../models/error.model.js';
import { AiAssistantService } from '../services/ai-assistant.service.js';
import { LoggerService } from '../services/logger.service.js';
import { RequestSessionService } from '../services/request-session.service.js';
import { TokenService } from '../services/token.service.js';
import { getRoomMemberToken } from '../utils/token.utils.js';
const getRoomMemberIdentityFromRequest = async (req: Request): Promise<string> => {
const tokenService = container.get(TokenService);
const token = getRoomMemberToken(req);
if (!token) {
throw new Error('Room member token not found');
}
const claims = await tokenService.verifyToken(token);
if (!claims.sub) {
throw new Error('Room member token does not include participant identity');
}
return claims.sub;
};
export const createAssistant = async (req: Request, res: Response) => {
const logger = container.get(LoggerService);
const requestSessionService = container.get(RequestSessionService);
const aiAssistantService = container.get(AiAssistantService);
// const payload: MeetCreateAssistantRequest = req.body;
const roomId = requestSessionService.getRoomIdFromToken();
if (!roomId) {
return handleError(res, new Error('Could not resolve room from token'), 'creating assistant');
const roomId = requestSessionService.getRoomIdFromMember();
const participantIdentity = requestSessionService.getParticipantIdentity();
if (!roomId || !participantIdentity) {
logger.warn('Could not resolve room or participant identity from token when creating assistant');
const error = errorInsufficientPermissions();
return rejectRequestFromMeetError(res, error);
}
try {
const participantIdentity = await getRoomMemberIdentityFromRequest(req);
logger.verbose(`Creating assistant for participant '${participantIdentity}' in room '${roomId}'`);
const assistant = await aiAssistantService.createLiveCaptionsAssistant(roomId, participantIdentity);
return res.status(200).json(assistant);
@ -50,14 +34,17 @@ export const cancelAssistant = async (req: Request, res: Response) => {
const requestSessionService = container.get(RequestSessionService);
const aiAssistantService = container.get(AiAssistantService);
const { assistantId } = req.params;
const roomId = requestSessionService.getRoomIdFromToken();
if (!roomId) {
return handleError(res, new Error('Could not resolve room from token'), 'canceling assistant');
const roomId = requestSessionService.getRoomIdFromMember();
const participantIdentity = requestSessionService.getParticipantIdentity();
if (!roomId || !participantIdentity) {
logger.warn('Could not resolve room or participant identity from token when canceling assistant');
const error = errorInsufficientPermissions();
return rejectRequestFromMeetError(res, error);
}
try {
const participantIdentity = await getRoomMemberIdentityFromRequest(req);
logger.verbose(
`Canceling assistant '${assistantId}' for participant '${participantIdentity}' in room '${roomId}'`
);

View File

@ -1,14 +1,15 @@
import { Request, Response } from 'express';
import { ClaimGrants } from 'livekit-server-sdk';
import { container } from '../config/dependency-injector.config.js';
import {
errorInvalidCredentials,
errorInvalidRefreshToken,
errorInvalidTokenSubject,
errorPasswordChangeRequired,
errorRefreshTokenNotPresent,
handleError,
rejectRequestFromMeetError
} from '../models/error.model.js';
import { TokenType } from '../models/token.model.js';
import { LoggerService } from '../services/logger.service.js';
import { TokenService } from '../services/token.service.js';
import { UserService } from '../services/user.service.js';
@ -17,10 +18,10 @@ import { getRefreshToken } from '../utils/token.utils.js';
export const login = async (req: Request, res: Response) => {
const logger = container.get(LoggerService);
logger.verbose('Login request received');
const { username, password } = req.body as { username: string; password: string };
const { userId, password } = req.body as { userId: string; password: string };
const userService = container.get(UserService);
const user = await userService.authenticateUser(username, password);
const user = await userService.authenticateUser(userId, password);
if (!user) {
logger.warn('Login failed');
@ -30,12 +31,27 @@ export const login = async (req: Request, res: Response) => {
try {
const tokenService = container.get(TokenService);
// Check if password change is required
if (user.mustChangePassword) {
// Generate temporary token with limited TTL, no refresh token
const accessToken = await tokenService.generateAccessToken(user, true);
logger.info(`Login succeeded for user '${userId}', but password change is required`);
return res.status(200).json({
message: `User '${userId}' logged in successfully, but password change is required`,
accessToken,
mustChangePassword: true
});
}
// Normal login flow
const accessToken = await tokenService.generateAccessToken(user);
const refreshToken = await tokenService.generateRefreshToken(user);
logger.info(`Login succeeded for user '${username}'`);
logger.info(`Login succeeded for user '${userId}'`);
return res.status(200).json({
message: `User '${username}' logged in successfully`,
message: `User '${userId}' logged in successfully`,
accessToken,
refreshToken
});
@ -54,8 +70,7 @@ export const refreshToken = async (req: Request, res: Response) => {
const logger = container.get(LoggerService);
logger.verbose('Refresh token request received');
// Get refresh token from cookie or header based on transport mode
const refreshToken = await getRefreshToken(req);
const refreshToken = getRefreshToken(req);
if (!refreshToken) {
logger.warn('No refresh token provided');
@ -64,19 +79,32 @@ export const refreshToken = async (req: Request, res: Response) => {
}
const tokenService = container.get(TokenService);
let payload: ClaimGrants;
let userId: string | undefined;
try {
payload = await tokenService.verifyToken(refreshToken);
// Verify the token and extract the user ID and token metadata
const { sub, metadata: tokenMetadata } = await tokenService.verifyToken(refreshToken);
if (!tokenMetadata) {
throw new Error('Missing required token claims');
}
// Validate that this is actually a refresh token
const parsedMetadata = tokenService.parseTokenMetadata(tokenMetadata);
userId = sub;
const tokenType = parsedMetadata.tokenType;
if (tokenType !== TokenType.REFRESH) {
throw new Error('Invalid token type for refresh operation');
}
} catch (error) {
logger.error('Error verifying refresh token:', error);
logger.error('Invalid refresh token:', error);
const meetError = errorInvalidRefreshToken();
return rejectRequestFromMeetError(res, meetError);
}
const username = payload.sub;
const userService = container.get(UserService);
const user = username ? await userService.getUser(username) : null;
const user = userId ? await userService.getUser(userId) : null;
if (!user) {
logger.warn('Invalid refresh token subject');
@ -84,12 +112,19 @@ export const refreshToken = async (req: Request, res: Response) => {
return rejectRequestFromMeetError(res, error);
}
// Restrict refresh if password change is required
if (user.mustChangePassword) {
logger.warn(`Cannot refresh token: password change required for user '${userId}'`);
const error = errorPasswordChangeRequired();
return rejectRequestFromMeetError(res, error);
}
try {
const accessToken = await tokenService.generateAccessToken(user);
logger.info(`Access token refreshed for user '${username}'`);
logger.info(`Access token refreshed for user '${userId}'`);
return res.status(200).json({
message: `Access token for user '${username}' successfully refreshed`,
message: `Access token for user '${userId}' successfully refreshed`,
accessToken
});
} catch (error) {

View File

@ -5,6 +5,7 @@ export * from './global-config.controller.js';
export * from './livekit-webhook.controller.js';
export * from './meeting.controller.js';
export * from './recording.controller.js';
export * from './room-member.controller.js';
export * from './room.controller.js';
export * from './user.controller.js';

View File

@ -1,3 +1,4 @@
import { MeetParticipantModerationAction } from '@openvidu-meet/typings';
import { Request, Response } from 'express';
import { container } from '../config/dependency-injector.config.js';
import { handleError } from '../models/error.model.js';
@ -15,7 +16,7 @@ export const endMeeting = async (req: Request, res: Response) => {
// Check if the room exists
try {
await roomService.getMeetRoom(roomId);
await roomService.getMeetRoom(roomId, ['roomId']);
} catch (error) {
return handleError(res, error, `getting room '${roomId}'`);
}
@ -34,14 +35,22 @@ export const updateParticipantRole = async (req: Request, res: Response) => {
const logger = container.get(LoggerService);
const roomMemberService = container.get(RoomMemberService);
const { roomId, participantIdentity } = req.params;
const { role } = req.body;
const { action } = req.body as { action: MeetParticipantModerationAction };
try {
logger.verbose(`Changing role of participant '${participantIdentity}' in room '${roomId}' to '${role}'`);
await roomMemberService.updateParticipantRole(roomId, participantIdentity, role);
res.status(200).json({ message: `Participant '${participantIdentity}' role updated to '${role}'` });
logger.verbose(
`Applying moderation action '${action}' for participant '${participantIdentity}' in room '${roomId}'`
);
await roomMemberService.updateParticipantRole(roomId, participantIdentity, action);
res.status(200).json({
message: `Moderation action '${action}' applied to participant '${participantIdentity}'`
});
} catch (error) {
handleError(res, error, `changing role for participant '${participantIdentity}' in room '${roomId}'`);
handleError(
res,
error,
`applying moderation action for participant '${participantIdentity}' in room '${roomId}'`
);
}
};
@ -53,7 +62,7 @@ export const kickParticipantFromMeeting = async (req: Request, res: Response) =>
// Check if the room exists
try {
await roomService.getMeetRoom(roomId);
await roomService.getMeetRoom(roomId, ['roomId']);
} catch (error) {
return handleError(res, error, `getting room '${roomId}'`);
}

View File

@ -1,3 +1,4 @@
import { MeetRecordingField, MeetRecordingInfo } from '@openvidu-meet/typings';
import archiver from 'archiver';
import { Request, Response } from 'express';
import { Readable } from 'stream';
@ -5,29 +6,30 @@ import { container } from '../config/dependency-injector.config.js';
import { INTERNAL_CONFIG } from '../config/internal-config.js';
import { RecordingHelper } from '../helpers/recording.helper.js';
import {
errorRecordingsNotFromSameRoom,
errorRecordingsZipEmpty,
handleError,
internalError,
rejectRequestFromMeetError
} from '../models/error.model.js';
import { LoggerService } from '../services/logger.service.js';
import { RecordingService } from '../services/recording.service.js';
import { RequestSessionService } from '../services/request-session.service.js';
import { getBaseUrl } from '../utils/url.utils.js';
export const startRecording = async (req: Request, res: Response) => {
const logger = container.get(LoggerService);
const recordingService = container.get(RecordingService);
const { roomId, config } = req.body;
const { fields } = res.locals.validatedQuery as { fields?: MeetRecordingField[] };
logger.info(`Starting recording in room '${roomId}'`);
try {
const recordingInfo = await recordingService.startRecording(roomId, config);
let recordingInfo = await recordingService.startRecording(roomId, config);
res.setHeader(
'Location',
`${getBaseUrl()}${INTERNAL_CONFIG.API_BASE_PATH_V1}/recordings/${recordingInfo.recordingId}`
);
recordingInfo = RecordingHelper.applyFieldFilters(recordingInfo, fields);
return res.status(201).json(recordingInfo);
} catch (error) {
handleError(res, error, `starting recording in room '${roomId}'`);
@ -37,13 +39,16 @@ export const startRecording = async (req: Request, res: Response) => {
export const stopRecording = async (req: Request, res: Response) => {
const logger = container.get(LoggerService);
const recordingId = req.params.recordingId;
const { fields } = res.locals.validatedQuery as { fields?: MeetRecordingField[] };
try {
logger.info(`Stopping recording '${recordingId}'`);
const recordingService = container.get(RecordingService);
const recordingInfo = await recordingService.stopRecording(recordingId);
let recordingInfo = await recordingService.stopRecording(recordingId);
res.setHeader('Location', `${getBaseUrl()}${INTERNAL_CONFIG.API_BASE_PATH_V1}/recordings/${recordingId}`);
recordingInfo = RecordingHelper.applyFieldFilters(recordingInfo, fields);
return res.status(202).json(recordingInfo);
} catch (error) {
handleError(res, error, `stopping recording '${recordingId}'`);
@ -53,18 +58,9 @@ export const stopRecording = async (req: Request, res: Response) => {
export const getRecordings = async (req: Request, res: Response) => {
const logger = container.get(LoggerService);
const recordingService = container.get(RecordingService);
const requestSessionService = container.get(RequestSessionService);
const queryParams = req.query;
const queryParams = res.locals.validatedQuery ?? {};
// If room member token is present, retrieve only recordings for the room associated with the token
const roomId = requestSessionService.getRoomIdFromToken();
if (roomId) {
queryParams.roomId = roomId;
logger.info(`Getting recordings for room '${roomId}'`);
} else {
logger.info('Getting all recordings');
}
logger.info('Getting all recordings');
try {
const { recordings, isTruncated, nextPageToken } = await recordingService.getAllRecordings(queryParams);
@ -86,18 +82,12 @@ export const getRecordings = async (req: Request, res: Response) => {
export const bulkDeleteRecordings = async (req: Request, res: Response) => {
const logger = container.get(LoggerService);
const recordingService = container.get(RecordingService);
const requestSessionService = container.get(RequestSessionService);
const { recordingIds } = req.query;
const { recordingIds } = res.locals.validatedQuery as { recordingIds: string[] };
logger.info(`Deleting recordings: ${recordingIds}`);
try {
const recordingIdsArray = (recordingIds as string).split(',');
// If room member token is present, delete only recordings for the room associated with the token
const roomId = requestSessionService.getRoomIdFromToken();
const { deleted, failed } = await recordingService.bulkDeleteRecordings(recordingIdsArray, roomId);
const { deleted, failed } = await recordingService.bulkDeleteRecordings(recordingIds);
// All recordings were successfully deleted
if (deleted.length > 0 && failed.length === 0) {
@ -115,7 +105,7 @@ export const getRecording = async (req: Request, res: Response) => {
const logger = container.get(LoggerService);
const recordingService = container.get(RecordingService);
const recordingId = req.params.recordingId;
const fields = req.query.fields as string | undefined;
const { fields } = res.locals.validatedQuery as { fields?: MeetRecordingField[] };
logger.info(`Getting recording '${recordingId}'`);
@ -230,13 +220,14 @@ export const getRecordingUrl = async (req: Request, res: Response) => {
const logger = container.get(LoggerService);
const recordingService = container.get(RecordingService);
const recordingId = req.params.recordingId;
const privateAccess = req.query.privateAccess === 'true';
const { privateAccess } = res.locals.validatedQuery as { privateAccess: string };
const isPrivateAccess = privateAccess === 'true';
logger.info(`Getting URL for recording '${recordingId}'`);
try {
const recordingSecrets = await recordingService.getRecordingAccessSecrets(recordingId);
const secret = privateAccess ? recordingSecrets.privateAccessSecret : recordingSecrets.publicAccessSecret;
const secret = isPrivateAccess ? recordingSecrets.privateAccessSecret : recordingSecrets.publicAccessSecret;
const recordingUrl = `${getBaseUrl()}/recording/${recordingId}?secret=${secret}`;
return res.status(200).json({ url: recordingUrl });
@ -248,38 +239,28 @@ export const getRecordingUrl = async (req: Request, res: Response) => {
export const downloadRecordingsZip = async (req: Request, res: Response) => {
const logger = container.get(LoggerService);
const recordingService = container.get(RecordingService);
const requestSessionService = container.get(RequestSessionService);
const recordingIds = req.query.recordingIds as string;
const recordingIdsArray = (recordingIds as string).split(',');
const { recordingIds } = res.locals.validatedQuery as { recordingIds: string[] };
const validRecordings: MeetRecordingInfo[] = [];
// Filter recording IDs if a room ID is provided
let validRecordingIds = recordingIdsArray;
logger.info(`Preparing ZIP download for recordings: ${recordingIds}`);
// If room member token is present, download only recordings for the room associated with the token
const roomId = requestSessionService.getRoomIdFromToken();
if (roomId) {
validRecordingIds = recordingIdsArray.filter((recordingId) => {
const { roomId: recRoomId } = RecordingHelper.extractInfoFromRecordingId(recordingId);
const isValid = recRoomId === roomId;
if (!isValid) {
logger.warn(`Skipping recording '${recordingId}' as it does not belong to room '${roomId}'`);
}
return isValid;
});
// Validate each recording: first check existence, then permissions
for (const recordingId of recordingIds) {
try {
const recordingInfo = await recordingService.validateRecordingAccess(recordingId, 'canRetrieveRecordings');
validRecordings.push(recordingInfo);
} catch (error) {
logger.warn(`Skipping recording '${recordingId}' for ZIP`);
}
}
if (validRecordingIds.length === 0) {
logger.warn(`None of the provided recording IDs belong to room '${roomId}'`);
const error = errorRecordingsNotFromSameRoom(roomId!);
if (validRecordings.length === 0) {
logger.error(`None of the provided recording IDs are available for ZIP download`);
const error = errorRecordingsZipEmpty();
return rejectRequestFromMeetError(res, error);
}
logger.info(`Creating ZIP for recordings: ${recordingIds}`);
res.setHeader('Content-Type', 'application/zip');
res.setHeader('Content-Disposition', 'attachment; filename="recordings.zip"');
@ -294,13 +275,14 @@ export const downloadRecordingsZip = async (req: Request, res: Response) => {
// Pipe the archive to the response
archive.pipe(res);
for (const recordingId of validRecordingIds) {
for (const recording of validRecordings) {
const recordingId = recording.recordingId;
try {
logger.debug(`Adding recording '${recordingId}' to ZIP`);
const result = await recordingService.getRecordingAsStream(recordingId);
const recordingInfo = await recordingService.getRecording(recordingId, 'filename');
const filename = recordingInfo.filename || `${recordingId}.mp4`;
const filename = recording.filename || `${recordingId}.mp4`;
archive.append(result.fileStream, { name: filename });
} catch (error) {
logger.error(`Error adding recording '${recordingId}' to ZIP: ${error}`);

View File

@ -0,0 +1,137 @@
import { MeetRoomMemberFilters, MeetRoomMemberTokenOptions } from '@openvidu-meet/typings';
import { Request, Response } from 'express';
import { container } from '../config/dependency-injector.config.js';
import { INTERNAL_CONFIG } from '../config/internal-config.js';
import { errorRoomMemberNotFound, handleError } from '../models/error.model.js';
import { LoggerService } from '../services/logger.service.js';
import { RoomMemberService } from '../services/room-member.service.js';
import { getBaseUrl } from '../utils/url.utils.js';
export const createRoomMember = async (req: Request, res: Response) => {
const logger = container.get(LoggerService);
const roomMemberService = container.get(RoomMemberService);
const { roomId } = req.params;
const memberOptions = req.body;
try {
logger.verbose(`Adding member in room '${roomId}'`);
const member = await roomMemberService.createRoomMember(roomId, memberOptions);
res.set(
'Location',
`${getBaseUrl()}${INTERNAL_CONFIG.API_BASE_PATH_V1}/rooms/${roomId}/members/${member.memberId}`
);
return res.status(201).json(member);
} catch (error) {
handleError(res, error, `adding member in room '${roomId}'`);
}
};
export const getRoomMembers = async (req: Request, res: Response) => {
const logger = container.get(LoggerService);
const roomMemberService = container.get(RoomMemberService);
const { roomId } = req.params;
const filters = res.locals.validatedQuery as MeetRoomMemberFilters;
try {
logger.verbose(`Getting members for room '${roomId}'`);
const { members, isTruncated, nextPageToken } = await roomMemberService.getAllRoomMembers(roomId, filters);
const maxItems = Number(filters.maxItems);
return res.status(200).json({ members, pagination: { isTruncated, nextPageToken, maxItems } });
} catch (error) {
handleError(res, error, `getting members for room '${roomId}'`);
}
};
export const getRoomMember = async (req: Request, res: Response) => {
const logger = container.get(LoggerService);
const roomMemberService = container.get(RoomMemberService);
const { roomId, memberId } = req.params;
try {
logger.verbose(`Getting member '${memberId}' from room '${roomId}'`);
const member = await roomMemberService.getRoomMember(roomId, memberId);
if (!member) {
throw errorRoomMemberNotFound(roomId, memberId);
}
return res.status(200).json(member);
} catch (error) {
handleError(res, error, `getting member '${memberId}' from room '${roomId}'`);
}
};
export const updateRoomMember = async (req: Request, res: Response) => {
const logger = container.get(LoggerService);
const roomMemberService = container.get(RoomMemberService);
const { roomId, memberId } = req.params;
const updates = req.body;
try {
logger.verbose(`Updating member '${memberId}' in room '${roomId}'`);
const member = await roomMemberService.updateRoomMember(roomId, memberId, updates);
return res.status(200).json(member);
} catch (error) {
handleError(res, error, `updating member '${memberId}' in room '${roomId}'`);
}
};
export const deleteRoomMember = async (req: Request, res: Response) => {
const logger = container.get(LoggerService);
const roomMemberService = container.get(RoomMemberService);
const { roomId, memberId } = req.params;
try {
logger.verbose(`Deleting member '${memberId}' from room '${roomId}'`);
await roomMemberService.deleteRoomMember(roomId, memberId);
return res.status(200).json({ message: `Member '${memberId}' deleted successfully from room '${roomId}'` });
} catch (error) {
handleError(res, error, `deleting member '${memberId}' from room '${roomId}'`);
}
};
export const bulkDeleteRoomMembers = async (req: Request, res: Response) => {
const logger = container.get(LoggerService);
const roomMemberService = container.get(RoomMemberService);
const { roomId } = req.params;
const { memberIds } = res.locals.validatedQuery as { memberIds: string[] };
try {
logger.verbose(`Deleting members from room '${roomId}' with IDs: ${memberIds}`);
const { deleted, failed } = await roomMemberService.bulkDeleteRoomMembers(roomId, memberIds);
// All room members were successfully deleted
if (deleted.length > 0 && failed.length === 0) {
return res.status(200).json({ message: 'All room members deleted successfully', deleted });
}
// Some or all room members could not be deleted
return res
.status(400)
.json({ message: `${failed.length} room member(s) could not be deleted`, deleted, failed });
} catch (error) {
handleError(res, error, `bulk deleting members from room '${roomId}'`);
}
};
export const generateRoomMemberToken = async (req: Request, res: Response) => {
const logger = container.get(LoggerService);
const roomMemberTokenService = container.get(RoomMemberService);
const { roomId } = req.params;
const tokenOptions: MeetRoomMemberTokenOptions = req.body;
try {
logger.verbose(`Generating room member token for room '${roomId}'`);
const token = await roomMemberTokenService.generateOrRefreshRoomMemberToken(roomId, tokenOptions);
return res.status(200).json({ token });
} catch (error) {
handleError(res, error, `generating room member token for room '${roomId}'`);
}
};

View File

@ -2,18 +2,18 @@ import {
MeetRoomDeletionPolicyWithMeeting,
MeetRoomDeletionPolicyWithRecordings,
MeetRoomDeletionSuccessCode,
MeetRoomExtraField,
MeetRoomField,
MeetRoomFilters,
MeetRoomMemberRole,
MeetRoomMemberRoleAndPermissions,
MeetRoomMemberTokenOptions,
MeetRoomOptions
} from '@openvidu-meet/typings';
import { Request, Response } from 'express';
import { container } from '../config/dependency-injector.config.js';
import { INTERNAL_CONFIG } from '../config/internal-config.js';
import { MeetRoomHelper } from '../helpers/room.helper.js';
import { handleError } from '../models/error.model.js';
import { MeetRoomDeletionOptions } from '../models/request-context.model.js';
import { LoggerService } from '../services/logger.service.js';
import { RoomMemberService } from '../services/room-member.service.js';
import { RoomService } from '../services/room.service.js';
import { getBaseUrl } from '../utils/url.utils.js';
@ -21,11 +21,21 @@ export const createRoom = async (req: Request, res: Response) => {
const logger = container.get(LoggerService);
const roomService = container.get(RoomService);
const options: MeetRoomOptions = req.body;
// Fields are merged from headers into req.query by the middleware
const { fields, extraFields } = res.locals.validatedQuery as {
fields?: MeetRoomField[];
extraFields?: MeetRoomExtraField[];
};
try {
logger.verbose(`Creating room with options '${JSON.stringify(options)}'`);
const room = await roomService.createMeetRoom(options);
// Pass response options to service for consistent handling
let room = await roomService.createMeetRoom(options);
room = MeetRoomHelper.applyFieldFilters(room, fields, extraFields);
room = MeetRoomHelper.addResponseMetadata(room);
res.set('Location', `${getBaseUrl()}${INTERNAL_CONFIG.API_BASE_PATH_V1}/rooms/${room.roomId}`);
return res.status(201).json(room);
} catch (error) {
@ -36,14 +46,21 @@ export const createRoom = async (req: Request, res: Response) => {
export const getRooms = async (req: Request, res: Response) => {
const logger = container.get(LoggerService);
const roomService = container.get(RoomService);
const queryParams = req.query as unknown as MeetRoomFilters;
const queryParams = res.locals.validatedQuery as MeetRoomFilters;
logger.verbose('Getting all rooms');
logger.verbose(`Getting all rooms with filters: ${JSON.stringify(queryParams)}`);
try {
const { rooms, isTruncated, nextPageToken } = await roomService.getAllMeetRooms(queryParams);
const fieldsForQuery = MeetRoomHelper.computeFieldsForRoomQuery(queryParams.fields, queryParams.extraFields);
const optimizedQueryParams = { ...queryParams, fields: fieldsForQuery };
const { rooms, isTruncated, nextPageToken } = await roomService.getAllMeetRooms(optimizedQueryParams);
const maxItems = Number(queryParams.maxItems);
return res.status(200).json({ rooms, pagination: { isTruncated, nextPageToken, maxItems } });
// Add metadata at response root level (multiple rooms strategy)
let response = { rooms, pagination: { isTruncated, nextPageToken, maxItems } };
response = MeetRoomHelper.addResponseMetadata(response);
return res.status(200).json(response);
} catch (error) {
handleError(res, error, 'getting rooms');
}
@ -53,13 +70,25 @@ export const getRoom = async (req: Request, res: Response) => {
const logger = container.get(LoggerService);
const { roomId } = req.params;
const fields = req.query.fields as string | undefined;
// Zod already validated and transformed to typed arrays
const { fields, extraFields } = res.locals.validatedQuery as {
fields?: MeetRoomField[];
extraFields?: MeetRoomExtraField[];
};
try {
logger.verbose(`Getting room '${roomId}'`);
logger.verbose(`Getting room '${roomId}' with filters: ${JSON.stringify({ fields, extraFields })}`);
const roomService = container.get(RoomService);
const room = await roomService.getMeetRoom(roomId, fields);
const fieldsForQuery = MeetRoomHelper.computeFieldsForRoomQuery(fields, extraFields);
let room = await roomService.getMeetRoom(roomId, fieldsForQuery);
// Apply permission filtering to the room based on the authenticated user's permissions
const permissions = await roomService.getAuthenticatedRoomMemberPermissions(roomId);
room = MeetRoomHelper.applyPermissionFiltering(room, permissions);
room = MeetRoomHelper.addResponseMetadata(room);
return res.status(200).json(room);
} catch (error) {
@ -72,14 +101,25 @@ export const deleteRoom = async (req: Request, res: Response) => {
const roomService = container.get(RoomService);
const { roomId } = req.params;
const { withMeeting, withRecordings } = req.query as {
const { fields, extraFields, withMeeting, withRecordings } = res.locals.validatedQuery as {
fields?: MeetRoomField[];
extraFields?: MeetRoomExtraField[];
withMeeting: MeetRoomDeletionPolicyWithMeeting;
withRecordings: MeetRoomDeletionPolicyWithRecordings;
};
try {
logger.verbose(`Deleting room '${roomId}'`);
const response = await roomService.deleteMeetRoom(roomId, withMeeting, withRecordings);
const deleteOpts: MeetRoomDeletionOptions = {
withMeeting,
withRecordings,
fields: MeetRoomHelper.computeFieldsForRoomQuery(fields, extraFields)
};
const response = await roomService.deleteMeetRoom(roomId, deleteOpts);
if (response.room) {
response.room = MeetRoomHelper.addResponseMetadata(response.room);
}
// Determine the status code based on the success code
// If the room action is scheduled, return 202. Otherwise, return 200.
@ -101,15 +141,29 @@ export const bulkDeleteRooms = async (req: Request, res: Response) => {
const logger = container.get(LoggerService);
const roomService = container.get(RoomService);
const { roomIds, withMeeting, withRecordings } = req.query as {
const { roomIds, fields, extraFields, withMeeting, withRecordings } = res.locals.validatedQuery as {
roomIds: string[];
fields?: MeetRoomField[];
extraFields?: MeetRoomExtraField[];
withMeeting: MeetRoomDeletionPolicyWithMeeting;
withRecordings: MeetRoomDeletionPolicyWithRecordings;
};
try {
logger.verbose(`Deleting rooms: ${roomIds}`);
const { successful, failed } = await roomService.bulkDeleteMeetRooms(roomIds, withMeeting, withRecordings);
logger.verbose(`Deleting rooms: ${roomIds} with options: ${JSON.stringify(res.locals.validatedQuery)}`);
const deleteOpts: MeetRoomDeletionOptions = {
withMeeting,
withRecordings,
fields: MeetRoomHelper.computeFieldsForRoomQuery(fields, extraFields)
};
const { successful, failed } = await roomService.bulkDeleteMeetRooms(roomIds, deleteOpts);
successful.forEach((item) => {
if (item.room) {
item.room = MeetRoomHelper.addResponseMetadata(item.room);
}
});
logger.info(
`Bulk delete operation - Successfully processed rooms: ${successful.length}, failed to process: ${failed.length}`
@ -120,9 +174,12 @@ export const bulkDeleteRooms = async (req: Request, res: Response) => {
return res.status(200).json({ message: 'All rooms successfully processed for deletion', successful });
} else {
// Some rooms failed to process
return res
.status(400)
.json({ message: `${failed.length} room(s) failed to process while deleting`, successful, failed });
const response = {
message: `${failed.length} room(s) failed to process while deleting`,
successful,
failed
};
return res.status(400).json(response);
}
} catch (error) {
handleError(res, error, `deleting rooms`);
@ -137,7 +194,7 @@ export const getRoomConfig = async (req: Request, res: Response) => {
logger.verbose(`Getting room config for room '${roomId}'`);
try {
const { config } = await roomService.getMeetRoom(roomId);
const { config } = await roomService.getMeetRoom(roomId, ['config']);
return res.status(200).json(config);
} catch (error) {
handleError(res, error, `getting room config for room '${roomId}'`);
@ -184,72 +241,34 @@ export const updateRoomStatus = async (req: Request, res: Response) => {
}
};
export const generateRoomMemberToken = async (req: Request, res: Response) => {
const logger = container.get(LoggerService);
const roomMemberTokenService = container.get(RoomMemberService);
const { roomId } = req.params;
const tokenOptions: MeetRoomMemberTokenOptions = req.body;
try {
logger.verbose(`Generating room member token for room '${roomId}'`);
const token = await roomMemberTokenService.generateOrRefreshRoomMemberToken(roomId, tokenOptions);
return res.status(200).json({ token });
} catch (error) {
handleError(res, error, `generating room member token for room '${roomId}'`);
}
};
export const getRoomMemberRolesAndPermissions = async (req: Request, res: Response) => {
export const updateRoomRoles = async (req: Request, res: Response) => {
const logger = container.get(LoggerService);
const roomService = container.get(RoomService);
const roomMemberService = container.get(RoomMemberService);
const { roles } = req.body;
const { roomId } = req.params;
// Check if the room exists
logger.verbose(`Updating roles permissions for room '${roomId}'`);
try {
await roomService.getMeetRoom(roomId);
await roomService.updateMeetRoomRoles(roomId, roles);
return res.status(200).json({ message: `Roles permissions for room '${roomId}' updated successfully` });
} catch (error) {
return handleError(res, error, `getting room '${roomId}'`);
handleError(res, error, `updating roles permissions for room '${roomId}'`);
}
logger.verbose(`Getting room member roles and associated permissions for room '${roomId}'`);
const moderatorPermissions = await roomMemberService.getRoomMemberPermissions(roomId, MeetRoomMemberRole.MODERATOR);
const speakerPermissions = await roomMemberService.getRoomMemberPermissions(roomId, MeetRoomMemberRole.SPEAKER);
const rolesAndPermissions: MeetRoomMemberRoleAndPermissions[] = [
{
role: MeetRoomMemberRole.MODERATOR,
permissions: moderatorPermissions
},
{
role: MeetRoomMemberRole.SPEAKER,
permissions: speakerPermissions
}
];
res.status(200).json(rolesAndPermissions);
};
export const getRoomMemberRoleAndPermissions = async (req: Request, res: Response) => {
export const updateRoomAccess = async (req: Request, res: Response) => {
const logger = container.get(LoggerService);
const roomMemberService = container.get(RoomMemberService);
const roomService = container.get(RoomService);
const { access } = req.body;
const { roomId } = req.params;
const { roomId, secret } = req.params;
logger.verbose(`Updating access config for room '${roomId}'`);
try {
logger.verbose(
`Getting room member role and associated permissions for room '${roomId}' and secret '${secret}'`
);
const role = await roomMemberService.getRoomMemberRoleBySecret(roomId, secret);
const permissions = await roomMemberService.getRoomMemberPermissions(roomId, role);
const roleAndPermissions: MeetRoomMemberRoleAndPermissions = {
role,
permissions
};
return res.status(200).json(roleAndPermissions);
await roomService.updateMeetRoomAccess(roomId, access);
return res.status(200).json({ message: `Access config for room '${roomId}' updated successfully` });
} catch (error) {
handleError(res, error, `getting room member role and permissions for room '${roomId}' and secret '${secret}'`);
handleError(res, error, `updating access config for room '${roomId}'`);
}
};

View File

@ -1,12 +1,84 @@
import { MeetUserFilters, MeetUserOptions, MeetUserRole } from '@openvidu-meet/typings';
import { Request, Response } from 'express';
import { container } from '../config/dependency-injector.config.js';
import { errorUnauthorized, handleError, rejectRequestFromMeetError } from '../models/error.model.js';
import { INTERNAL_CONFIG } from '../config/internal-config.js';
import {
errorUnauthorized,
errorUserNotFound,
handleError,
rejectRequestFromMeetError
} from '../models/error.model.js';
import { LoggerService } from '../services/logger.service.js';
import { RequestSessionService } from '../services/request-session.service.js';
import { TokenService } from '../services/token.service.js';
import { UserService } from '../services/user.service.js';
import { getBaseUrl } from '../utils/url.utils.js';
export const getProfile = (_req: Request, res: Response) => {
export const createUser = async (req: Request, res: Response) => {
const userOptions = req.body as MeetUserOptions;
const logger = container.get(LoggerService);
logger.verbose(`Creating user with ID '${userOptions.userId}'`);
try {
const userService = container.get(UserService);
const user = await userService.createUser(userOptions);
res.set('Location', `${getBaseUrl()}${INTERNAL_CONFIG.API_BASE_PATH_V1}/users/${user.userId}`);
return res.status(201).json(userService.convertToDTO(user));
} catch (error) {
handleError(res, error, 'creating user');
}
};
export const getUsers = async (req: Request, res: Response) => {
const queryParams = res.locals.validatedQuery as MeetUserFilters;
const logger = container.get(LoggerService);
logger.verbose(`Getting all users`);
try {
const userService = container.get(UserService);
const { users, isTruncated, nextPageToken } = await userService.getUsers(queryParams);
const usersDTO = users.map((user) => userService.convertToDTO(user));
const maxItems = Number(queryParams.maxItems);
return res.status(200).json({
users: usersDTO,
pagination: {
isTruncated,
nextPageToken,
maxItems
}
});
} catch (error) {
handleError(res, error, 'getting users');
}
};
export const getUser = async (req: Request, res: Response) => {
const { userId } = req.params;
const logger = container.get(LoggerService);
logger.verbose(`Getting user with ID '${userId}'`);
try {
const userService = container.get(UserService);
const user = await userService.getUser(userId);
if (!user) {
throw errorUserNotFound(userId);
}
return res.status(200).json(userService.convertToDTO(user));
} catch (error) {
handleError(res, error, 'getting user');
}
};
export const getMe = (_req: Request, res: Response) => {
const requestSessionService = container.get(RequestSessionService);
const user = requestSessionService.getUser();
const user = requestSessionService.getAuthenticatedUser();
if (!user) {
const error = errorUnauthorized();
@ -18,9 +90,28 @@ export const getProfile = (_req: Request, res: Response) => {
return res.status(200).json(userDTO);
};
export const resetUserPassword = async (req: Request, res: Response) => {
const { userId } = req.params;
const { newPassword } = req.body as { newPassword: string };
const logger = container.get(LoggerService);
logger.verbose(`Admin resetting password for user '${userId}'`);
try {
const userService = container.get(UserService);
await userService.resetUserPassword(userId, newPassword);
return res.status(200).json({
message: `Password for user '${userId}' has been reset successfully. User must change password on next login.`
});
} catch (error) {
handleError(res, error, 'resetting user password');
}
};
export const changePassword = async (req: Request, res: Response) => {
const requestSessionService = container.get(RequestSessionService);
const user = requestSessionService.getUser();
const user = requestSessionService.getAuthenticatedUser();
if (!user) {
const error = errorUnauthorized();
@ -29,11 +120,93 @@ export const changePassword = async (req: Request, res: Response) => {
const { currentPassword, newPassword } = req.body as { currentPassword: string; newPassword: string };
const logger = container.get(LoggerService);
logger.verbose(`Changing password for user '${user.userId}'`);
try {
const userService = container.get(UserService);
await userService.changePassword(user.username, currentPassword, newPassword);
return res.status(200).json({ message: `Password for user '${user.username}' changed successfully` });
await userService.changePassword(user.userId, currentPassword, newPassword);
const message = `Password for user '${user.userId}' changed successfully`;
logger.info(message);
// Generate new tokens if the user had to change password
if (user.mustChangePassword) {
logger.info(`Generating new tokens for user '${user.userId}' after password change`);
const tokenService = container.get(TokenService);
const accessToken = await tokenService.generateAccessToken(user);
const refreshToken = await tokenService.generateRefreshToken(user);
return res.status(200).json({
message,
accessToken,
refreshToken
});
}
return res.status(200).json({ message });
} catch (error) {
handleError(res, error, 'changing password');
}
};
export const updateUserRole = async (req: Request, res: Response) => {
const { userId } = req.params;
const { role } = req.body as { role: MeetUserRole };
const logger = container.get(LoggerService);
logger.verbose(`Admin updating role for user '${userId}' to '${role}'`);
try {
const userService = container.get(UserService);
const user = await userService.changeUserRole(userId, role);
return res.status(200).json({
message: `Role for user '${userId}' updated successfully to '${role}'`,
user: userService.convertToDTO(user)
});
} catch (error) {
handleError(res, error, 'updating user role');
}
};
export const deleteUser = async (req: Request, res: Response) => {
const { userId } = req.params;
const logger = container.get(LoggerService);
logger.verbose(`Deleting user with ID '${userId}'`);
try {
const userService = container.get(UserService);
await userService.deleteUser(userId);
return res.status(200).json({ message: `User '${userId}' deleted successfully` });
} catch (error) {
handleError(res, error, 'deleting user');
}
};
export const bulkDeleteUsers = async (req: Request, res: Response) => {
const { userIds } = res.locals.validatedQuery as { userIds: string[] };
const logger = container.get(LoggerService);
logger.verbose(`Deleting users: ${userIds}`);
try {
const userService = container.get(UserService);
const { deleted, failed } = await userService.bulkDeleteUsers(userIds);
// All users were successfully deleted
if (deleted.length > 0 && failed.length === 0) {
return res.status(200).json({ message: 'All users deleted successfully', deleted });
}
// Some or all users could not be deleted
return res.status(400).json({
message: `${failed.length} user(s) could not be deleted`,
deleted,
failed
});
} catch (error) {
handleError(res, error, 'deleting users');
}
};

View File

@ -0,0 +1,185 @@
/**
* Generic helper for managing field filtering in a two-layer approach:
* 1. Database query optimization (what fields to retrieve from DB)
* 2. HTTP response filtering (what fields to include in the API response)
*
* This helper is designed to be reusable across different entities (Room, Recording, User, etc.)
*
* Key concepts:
* - Base fields: Standard fields included by default
* - Extra fields: Fields excluded by default, must be explicitly requested via extraFields parameter
* - Union logic: Final fields = fields extraFields
*/
/**
* Calculates the optimal set of fields to request from the database.
* This minimizes data transfer and processing by excluding unnecessary extra fields.
*
* Logic:
* - If `fields` is specified: return fields extraFields (explicit selection)
* - If only `extraFields` is specified: return all base fields + requested extra fields
* - If neither is specified: return all base fields (exclude all extra fields from DB query)
*
* @param fields - Explicitly requested fields (e.g., ['roomId', 'roomName'])
* @param extraFields - Extra fields to include (e.g., ['config'])
* @param allFields - Complete list of all possible fields for this entity
* @param extraFieldsList - List of fields that are considered "extra" (excluded by default)
* @returns Array of fields to request from database, or undefined if all fields should be retrieved
*
* @example
* ```typescript
* // No filters → retrieve all base fields only (efficient!)
* buildFieldsForDbQuery(undefined, undefined, MEET_ROOM_FIELDS, MEET_ROOM_EXTRA_FIELDS)
* // Returns: ['roomId', 'roomName', 'owner', ...] (without 'config')
*
* // Only extraFields → retrieve base fields + requested extras
* buildFieldsForDbQuery(undefined, ['config'], MEET_ROOM_FIELDS, MEET_ROOM_EXTRA_FIELDS)
* // Returns: ['roomId', 'roomName', 'owner', ..., 'config']
*
* // Both fields and extraFields → retrieve union
* buildFieldsForDbQuery(['roomId'], ['config'], MEET_ROOM_FIELDS, MEET_ROOM_EXTRA_FIELDS)
* // Returns: ['roomId', 'config']
* ```
*/
export function buildFieldsForDbQuery<TField extends string, TExtraField extends TField>(
fields: readonly TField[] | undefined,
extraFields: readonly TExtraField[] | undefined,
allFields: readonly TField[],
extraFieldsList: readonly TExtraField[]
): TField[] | undefined {
// Case 1: fields is explicitly specified
// Return the union of fields and extraFields for precise DB query
if (fields && fields.length > 0) {
const union = new Set<TField>([...fields, ...(extraFields || [])]);
return Array.from(union);
}
// Case 2: Only extraFields specified (no fields)
// Include all base fields + requested extra fields
if (extraFields && extraFields.length > 0) {
// All fields except extra fields that are NOT requested
const baseFields = allFields.filter((field) => !extraFieldsList.includes(field as TExtraField));
const union = new Set<TField>([...baseFields, ...extraFields]);
return Array.from(union);
}
// Case 3: Neither fields nor extraFields specified
// Return only base fields (exclude all extra fields)
const baseFields = allFields.filter((field) => !extraFieldsList.includes(field as TExtraField));
return baseFields as TField[];
}
/**
* Applies HTTP-level field filtering to an entity object.
* This is the final transformation before sending the response to the client.
*
* The logic follows the union principle: final allowed fields = fields extraFields
*
* Behavior:
* - If neither fields nor extraFields are specified: removes all extra fields from the response
* - If only fields is specified: includes only those fields (removing extra fields unless in the list)
* - If only extraFields is specified: includes all base fields + specified extra fields
* - If both are specified: includes the union of both sets (fields extraFields)
*
* This unified approach prevents bugs from chaining destructive filters on the same object.
*
* @param entity - The entity object to filter
* @param fields - Optional array of field names to include
* @param extraFields - Optional array of extra field names to include
* @param extraFieldsList - List of fields that are considered "extra" (excluded by default)
* @returns The filtered entity object
*
* @example
* ```typescript
* // No filters - removes extra fields only:
* applyHttpFieldFiltering(room, undefined, undefined, MEET_ROOM_EXTRA_FIELDS)
* // Result: room without 'config' property
*
* // Only fields specified - includes only those fields:
* applyHttpFieldFiltering(room, ['roomId', 'roomName'], undefined, MEET_ROOM_EXTRA_FIELDS)
* // Result: { roomId: '123', roomName: 'My Room' }
*
* // Only extraFields specified - includes base fields + extra fields:
* applyHttpFieldFiltering(room, undefined, ['config'], MEET_ROOM_EXTRA_FIELDS)
* // Result: room with all base fields and 'config' property
*
* // Both specified - includes union of both:
* applyHttpFieldFiltering(room, ['roomId'], ['config'], MEET_ROOM_EXTRA_FIELDS)
* // Result: { roomId: '123', config: {...} }
* ```
*/
export function applyHttpFieldFiltering<TEntity, TExtraField extends string>(
entity: TEntity,
fields: readonly string[] | undefined,
extraFields: readonly TExtraField[] | undefined,
extraFieldsList: readonly TExtraField[]
): TEntity {
if (!entity) {
return entity;
}
// Case 1: No filters specified - remove extra fields only
if ((!fields || fields.length === 0) && (!extraFields || extraFields.length === 0)) {
const processedEntity = { ...entity } as Record<string, unknown>;
extraFieldsList.forEach((field) => {
delete processedEntity[field];
});
return processedEntity as TEntity;
}
// Case 2: Only extraFields specified - include all base fields + specified extra fields
if (!fields || fields.length === 0) {
const processedEntity = { ...entity } as Record<string, unknown>;
// Remove extra fields that are NOT in the extraFields list
extraFieldsList.forEach((field) => {
if (!extraFields!.includes(field)) {
delete processedEntity[field];
}
});
return processedEntity as TEntity;
}
// Case 3: fields is specified (with or without extraFields)
// Create the union: fields extraFields
const allowedFields = new Set<string>([...fields, ...(extraFields || [])]);
const filteredEntity = {} as Record<string, unknown>;
const entityAsRecord = entity as Record<string, unknown>;
for (const key of Object.keys(entityAsRecord)) {
if (allowedFields.has(key)) {
filteredEntity[key] = entityAsRecord[key];
}
}
return filteredEntity as TEntity;
}
/**
* Adds metadata to the response indicating which extra fields are available.
* This allows API consumers to discover available extra fields without consulting documentation.
*
* @param obj - The object to enhance with metadata (can be a single entity or a response object)
* @param extraFieldsList - List of available extra fields
* @returns The object with _extraFields metadata added
*
* @example
* ```typescript
* // Single entity
* addResponseMetadata(room, MEET_ROOM_EXTRA_FIELDS)
* // Result: { ...room, _extraFields: ['config'] }
*
* // Response object
* addResponseMetadata({ rooms: [...] }, MEET_ROOM_EXTRA_FIELDS)
* // Result: { rooms: [...], _extraFields: ['config'] }
* ```
*/
export function addHttpResponseMetadata<T, TExtraField extends string>(
obj: T,
extraFieldsList: readonly TExtraField[]
): T & { _extraFields: TExtraField[] } {
return {
...obj,
_extraFields: [...extraFieldsList]
};
}

Some files were not shown because too many files have changed in this diff Show More