Compare commits
266 Commits
main
...
feat/room-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
94d9c7c50b | ||
|
|
c5bca6e133 | ||
|
|
467ebf6d49 | ||
|
|
8a8951c120 | ||
|
|
f70bd04497 | ||
|
|
1223e3d53b | ||
|
|
eca3acbcf0 | ||
|
|
9cf48fc49e | ||
|
|
2453ce2760 | ||
|
|
7607f134a0 | ||
|
|
5045815a1c | ||
|
|
a81b6edf41 | ||
|
|
b775c37a86 | ||
|
|
a829721ba5 | ||
|
|
e05dfdf001 | ||
|
|
f49fd863b7 | ||
|
|
5b9fa3149c | ||
|
|
fe71d07242 | ||
|
|
63d72c994b | ||
|
|
c563860758 | ||
|
|
43181bd068 | ||
|
|
340d53066c | ||
|
|
e0d811237b | ||
|
|
db279faee4 | ||
|
|
59c464387b | ||
|
|
54bb06adfd | ||
|
|
2572fd3960 | ||
|
|
37023fe077 | ||
|
|
3a8e5e21be | ||
|
|
eaf021f1dc | ||
|
|
22a42e4c63 | ||
|
|
8e5c07710b | ||
|
|
09ac408f32 | ||
|
|
10a9982a57 | ||
|
|
6592dac7fc | ||
|
|
b2380ec12b | ||
|
|
78edf51842 | ||
|
|
cae5a58568 | ||
|
|
cdbfd8c968 | ||
|
|
8ac2631e25 | ||
|
|
161f42f83c | ||
|
|
d51c5ae0c3 | ||
|
|
4794b30ff9 | ||
|
|
177134d2a6 | ||
|
|
78060c0cdf | ||
|
|
fcf21468c2 | ||
|
|
02a9774d72 | ||
|
|
210e5fe3f5 | ||
|
|
e5ebcbaab0 | ||
|
|
1f603a683d | ||
|
|
f1b9f1c6e3 | ||
|
|
a25569b4f7 | ||
|
|
d476b9c715 | ||
|
|
5cbd05be11 | ||
|
|
248015fc06 | ||
|
|
803e4b6704 | ||
|
|
696df4a7a2 | ||
|
|
27d4249c57 | ||
|
|
83583259ba | ||
|
|
a9818f0cdc | ||
|
|
0777f299d9 | ||
|
|
a69c8d0239 | ||
|
|
7f784af12b | ||
|
|
dcf6b5a84e | ||
|
|
99b9f56959 | ||
|
|
41438e20fa | ||
|
|
b695b5f117 | ||
|
|
66e7a8b038 | ||
|
|
7976c99654 | ||
|
|
491c3392ce | ||
|
|
66978509b1 | ||
|
|
c6742fd5eb | ||
|
|
761b205ed0 | ||
|
|
a4d368d856 | ||
|
|
9e85174b69 | ||
|
|
2c216f53c3 | ||
|
|
bc70759837 | ||
|
|
87645efb3c | ||
|
|
b9550aced9 | ||
|
|
333bd0e92f | ||
|
|
92b96f9a34 | ||
|
|
c00c255533 | ||
|
|
c27cc49985 | ||
|
|
dec8307cfb | ||
|
|
2df238f0cc | ||
|
|
583fdbe608 | ||
|
|
71c08dee8c | ||
|
|
bfe97395d0 | ||
|
|
9c187be2b8 | ||
|
|
f780fac60d | ||
|
|
fd13af8e2b | ||
|
|
6c38d615ea | ||
|
|
1856201b87 | ||
|
|
65674e164d | ||
|
|
f42d91ec74 | ||
|
|
4708181628 | ||
|
|
874538a8b7 | ||
|
|
9dc4834edd | ||
|
|
6ca1ace61e | ||
|
|
61b10befec | ||
|
|
14bf154390 | ||
|
|
d55cf45fa5 | ||
|
|
4f96192cc6 | ||
|
|
2566219dd9 | ||
|
|
c7e7674161 | ||
|
|
9c963ccf81 | ||
|
|
5cf2e74eab | ||
|
|
6d3d19ccd0 | ||
|
|
d155846708 | ||
|
|
5c80387244 | ||
|
|
f6dd80e8eb | ||
|
|
28f4bf6b38 | ||
|
|
b8e7baf705 | ||
|
|
c0b77314b5 | ||
|
|
b23445d063 | ||
|
|
cf64838c9b | ||
|
|
d115760e03 | ||
|
|
fedb9c2b44 | ||
|
|
ea5ef99773 | ||
|
|
01c5b695d9 | ||
|
|
5ca46e59d8 | ||
|
|
90edd756a2 | ||
|
|
4ebbaa47ef | ||
|
|
3f108cd161 | ||
|
|
8a7989478a | ||
|
|
ab907bb0e8 | ||
|
|
599a744302 | ||
|
|
4c864b193f | ||
|
|
beb5571983 | ||
|
|
04563a009f | ||
|
|
0e77aba428 | ||
|
|
28bfa609d8 | ||
|
|
9db0e8b29e | ||
|
|
9a2597a997 | ||
|
|
fb4bdbfcfb | ||
|
|
8e8e2670c4 | ||
|
|
01f21a724f | ||
|
|
e45aa91d90 | ||
|
|
7cdfdf20f9 | ||
|
|
dbca91f0c3 | ||
|
|
38b3db6171 | ||
|
|
fd675573fc | ||
|
|
ee55f02aaa | ||
|
|
7099012317 | ||
|
|
9f46d03646 | ||
|
|
5bb9a2f3e1 | ||
|
|
6c4adfeaaa | ||
|
|
35e727cbd0 | ||
|
|
0e37d8cc09 | ||
|
|
1aa6c8a383 | ||
|
|
19e7c48fd0 | ||
|
|
9441343f45 | ||
|
|
eacd629006 | ||
|
|
1c85eaa364 | ||
|
|
af6b5cab28 | ||
|
|
733665b49b | ||
|
|
80ce1a3efd | ||
|
|
d4a87f8a45 | ||
|
|
ecef2844a0 | ||
|
|
336a6751c2 | ||
|
|
70ca7a0fa9 | ||
|
|
f61fa6183c | ||
|
|
ded3c37ab2 | ||
|
|
14c64fdd75 | ||
|
|
eb5144291a | ||
|
|
6c3d87c8bf | ||
|
|
ae4217a4d4 | ||
|
|
85e4a5b8a6 | ||
|
|
b78744b8b6 | ||
|
|
335ab30a48 | ||
|
|
f01ec31c1b | ||
|
|
18426e2111 | ||
|
|
27a6064b61 | ||
|
|
b3ab245dff | ||
|
|
8af5759e4d | ||
|
|
07ac5b91c9 | ||
|
|
6e225fe265 | ||
|
|
d252784a39 | ||
|
|
3df0c54004 | ||
|
|
73e7a1ece7 | ||
|
|
70d51e21a6 | ||
|
|
268a6f9709 | ||
|
|
21f4563202 | ||
|
|
c561cf9bcd | ||
|
|
54c2c79ccb | ||
|
|
cdbb30fc2a | ||
|
|
993681395c | ||
|
|
ad3e0b81e5 | ||
|
|
68477d8ad3 | ||
|
|
1e1d66ae11 | ||
|
|
6a350b07a5 | ||
|
|
0a5852d89a | ||
|
|
7ff040864f | ||
|
|
11f7ac1401 | ||
|
|
1188255210 | ||
|
|
2e7cbeb96a | ||
|
|
89e7d5db88 | ||
|
|
c1a43af260 | ||
|
|
987085a466 | ||
|
|
e0e2fc2a44 | ||
|
|
fb4e7a022c | ||
|
|
503db6c2cb | ||
|
|
e580843b3a | ||
|
|
84a0b2ac6e | ||
|
|
23a25c1c3d | ||
|
|
7c4b5c6724 | ||
|
|
456e890ffe | ||
|
|
de0674d82c | ||
|
|
7d7f66edf3 | ||
|
|
8ab8007c1d | ||
|
|
b2488544e3 | ||
|
|
2bc1d02620 | ||
|
|
35498a5854 | ||
|
|
086e325551 | ||
|
|
136a422fb6 | ||
|
|
086f60d60a | ||
|
|
8d255bd051 | ||
|
|
c0ecc826cb | ||
|
|
1855755cd6 | ||
|
|
4b183fe02c | ||
|
|
6e99b21965 | ||
|
|
1414566f7f | ||
|
|
9293568ccd | ||
|
|
78cfc3035e | ||
|
|
337ba91725 | ||
|
|
c573cf802e | ||
|
|
c9eee9bf9d | ||
|
|
4689c866d6 | ||
|
|
7d462a8f08 | ||
|
|
8d47a7444b | ||
|
|
94fbd55ed8 | ||
|
|
7ee20f31c1 | ||
|
|
f0a6d63f4d | ||
|
|
c60cb244a7 | ||
|
|
23154bd380 | ||
|
|
1498332d28 | ||
|
|
765f8c08d1 | ||
|
|
770a330e7a | ||
|
|
14d10838d6 | ||
|
|
33fd9eabfc | ||
|
|
dccc4bc2f9 | ||
|
|
1d2ebd8be3 | ||
|
|
4390573021 | ||
|
|
f2a84717e5 | ||
|
|
5fe71482f7 | ||
|
|
b8507b824b | ||
|
|
0deae8ad29 | ||
|
|
e7f61aa2c2 | ||
|
|
42c38f82af | ||
|
|
df2d58c3bb | ||
|
|
b017334976 | ||
|
|
92b99764b9 | ||
|
|
577c48fe1b | ||
|
|
a069ebafaf | ||
|
|
93046c8db0 | ||
|
|
a26f2a754b | ||
|
|
6b781aac8e | ||
|
|
459799a716 | ||
|
|
e72566dd8c | ||
|
|
79cef519b8 | ||
|
|
b709ad320a | ||
|
|
5d874f57f5 | ||
|
|
53ac60d37a | ||
|
|
e0736677ca | ||
|
|
e3de4256a8 | ||
|
|
5abd106641 |
46
.github/copilot-instructions.md
vendored
Normal file
46
.github/copilot-instructions.md
vendored
Normal 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.
|
||||
397
.github/workflows/.copilot-instructions.md
vendored
397
.github/workflows/.copilot-instructions.md
vendored
@ -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
|
||||
@ -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:
|
||||
|
||||
2
.github/workflows/backend-unit-test.yaml
vendored
2
.github/workflows/backend-unit-test.yaml
vendored
@ -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:
|
||||
|
||||
2
.github/workflows/wc-e2e-test.yaml
vendored
2
.github/workflows/wc-e2e-test.yaml
vendored
@ -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:
|
||||
|
||||
2
.github/workflows/wc-unit-test.yaml
vendored
2
.github/workflows/wc-unit-test.yaml
vendored
@ -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:
|
||||
|
||||
2
meet-ce/.vscode/settings.json
vendored
2
meet-ce/.vscode/settings.json
vendored
@ -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"
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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'
|
||||
@ -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'
|
||||
@ -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'
|
||||
@ -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'
|
||||
@ -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
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
|
||||
@ -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'
|
||||
@ -6,4 +6,4 @@ description: >
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
example: 'roomId,moderatorUrl'
|
||||
example: 'fields=roomId,roomName'
|
||||
|
||||
@ -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'
|
||||
@ -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'
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
@ -1,6 +0,0 @@
|
||||
name: sortField
|
||||
in: query
|
||||
required: false
|
||||
description: The field by which to sort the results.
|
||||
schema:
|
||||
type: string
|
||||
@ -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.
|
||||
|
||||
@ -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'
|
||||
@ -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'
|
||||
@ -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'
|
||||
@ -1,6 +0,0 @@
|
||||
description: Room anonymous access configuration update options
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../schemas/meet-room-anonymous-config.yaml'
|
||||
@ -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'
|
||||
|
||||
@ -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'
|
||||
@ -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'
|
||||
@ -1,4 +1,4 @@
|
||||
description: Room not found
|
||||
description: Room cannot be updated because it has an active meeting.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
|
||||
@ -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'
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
@ -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'
|
||||
@ -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'
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
@ -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'
|
||||
@ -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.
|
||||
|
||||
@ -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'
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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
|
||||
@ -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
|
||||
@ -9,6 +9,7 @@ content:
|
||||
roomName: 'room'
|
||||
status: 'active'
|
||||
layout: 'speaker'
|
||||
encoding: 'H264_720P_30'
|
||||
filename: 'room-123--XX445.mp4'
|
||||
startDate: 1600000000000
|
||||
headers:
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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
|
||||
@ -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
|
||||
|
||||
@ -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']
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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.
|
||||
@ -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:
|
||||
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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}:
|
||||
|
||||
@ -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':
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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:
|
||||
@ -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':
|
||||
|
||||
@ -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'
|
||||
|
||||
185
meet-ce/backend/openapi/paths/room-members.yaml
Normal file
185
meet-ce/backend/openapi/paths/room-members.yaml
Normal 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'
|
||||
@ -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':
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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
|
||||
};
|
||||
|
||||
|
||||
@ -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}'`
|
||||
);
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -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}'`);
|
||||
}
|
||||
|
||||
@ -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}`);
|
||||
|
||||
137
meet-ce/backend/src/controllers/room-member.controller.ts
Normal file
137
meet-ce/backend/src/controllers/room-member.controller.ts
Normal 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}'`);
|
||||
}
|
||||
};
|
||||
@ -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}'`);
|
||||
}
|
||||
};
|
||||
|
||||
@ -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');
|
||||
}
|
||||
};
|
||||
|
||||
185
meet-ce/backend/src/helpers/field-filter.helper.ts
Normal file
185
meet-ce/backend/src/helpers/field-filter.helper.ts
Normal 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
Loading…
x
Reference in New Issue
Block a user