feat: implement frontend video platform configuration and abstraction

- Add NEXT_PUBLIC_VIDEO_PLATFORM environment variable support
- Create video platform abstraction layer with factory pattern
- Implement Whereby and Jitsi platform providers
- Update room meeting page to use platform-agnostic component
- Add platform display in room management (cards and table views)
- Support single platform per deployment configuration
- Maintain backward compatibility with existing Whereby integration

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-09-04 12:21:51 -06:00
parent 41224a424c
commit 293f7d4f1f
20 changed files with 1254 additions and 293 deletions

View File

@@ -99,6 +99,9 @@ export const $CreateRoom = {
type: "string",
title: "Webhook Secret",
},
platform: {
$ref: "#/components/schemas/VideoPlatform",
},
},
type: "object",
required: [
@@ -113,6 +116,7 @@ export const $CreateRoom = {
"is_shared",
"webhook_url",
"webhook_secret",
"platform",
],
title: "CreateRoom",
} as const;
@@ -697,6 +701,58 @@ export const $HTTPValidationError = {
title: "HTTPValidationError",
} as const;
export const $JibriRecordingEvent = {
properties: {
room_name: {
type: "string",
title: "Room Name",
},
recording_file: {
type: "string",
title: "Recording File",
},
recording_status: {
type: "string",
title: "Recording Status",
},
timestamp: {
type: "string",
format: "date-time",
title: "Timestamp",
},
},
type: "object",
required: ["room_name", "recording_file", "recording_status", "timestamp"],
title: "JibriRecordingEvent",
} as const;
export const $JitsiWebhookEvent = {
properties: {
event: {
type: "string",
title: "Event",
},
room: {
type: "string",
title: "Room",
},
timestamp: {
type: "string",
format: "date-time",
title: "Timestamp",
},
data: {
additionalProperties: true,
type: "object",
title: "Data",
default: {},
},
},
type: "object",
required: ["event", "room", "timestamp"],
title: "JitsiWebhookEvent",
} as const;
export const $Meeting = {
properties: {
id: {
@@ -960,6 +1016,10 @@ export const $Room = {
type: "boolean",
title: "Is Shared",
},
platform: {
$ref: "#/components/schemas/VideoPlatform",
default: "whereby",
},
},
type: "object",
required: [
@@ -1030,12 +1090,30 @@ export const $RoomDetails = {
type: "boolean",
title: "Is Shared",
},
platform: {
$ref: "#/components/schemas/VideoPlatform",
default: "whereby",
},
webhook_url: {
type: "string",
anyOf: [
{
type: "string",
},
{
type: "null",
},
],
title: "Webhook Url",
},
webhook_secret: {
type: "string",
anyOf: [
{
type: "string",
},
{
type: "null",
},
],
title: "Webhook Secret",
},
},
@@ -1091,10 +1169,17 @@ export const $SearchResponse = {
description: "Total number of search results",
},
query: {
type: "string",
minLength: 0,
anyOf: [
{
type: "string",
minLength: 1,
description: "Search query text",
},
{
type: "null",
},
],
title: "Query",
description: "Search query text",
},
limit: {
type: "integer",
@@ -1111,7 +1196,7 @@ export const $SearchResponse = {
},
},
type: "object",
required: ["results", "total", "query", "limit", "offset"],
required: ["results", "total", "limit", "offset"],
title: "SearchResponse",
} as const;
@@ -1449,6 +1534,9 @@ export const $UpdateRoom = {
type: "string",
title: "Webhook Secret",
},
platform: {
$ref: "#/components/schemas/VideoPlatform",
},
},
type: "object",
required: [
@@ -1463,6 +1551,7 @@ export const $UpdateRoom = {
"is_shared",
"webhook_url",
"webhook_secret",
"platform",
],
title: "UpdateRoom",
} as const;
@@ -1641,6 +1730,12 @@ export const $ValidationError = {
title: "ValidationError",
} as const;
export const $VideoPlatform = {
type: "string",
enum: ["whereby", "jitsi"],
title: "VideoPlatform",
} as const;
export const $WebhookTestResult = {
properties: {
success: {

View File

@@ -74,6 +74,11 @@ import type {
V1ZulipGetTopicsResponse,
V1WherebyWebhookData,
V1WherebyWebhookResponse,
V1JitsiEventsWebhookData,
V1JitsiEventsWebhookResponse,
V1JibriRecordingCompleteData,
V1JibriRecordingCompleteResponse,
V1JitsiHealthCheckResponse,
} from "./types.gen";
export class DefaultService {
@@ -255,7 +260,6 @@ export class DefaultService {
/**
* Rooms Test Webhook
* Test webhook configuration by sending a sample payload.
* @param data The data for the request.
* @param data.roomId
* @returns WebhookTestResult Successful Response
@@ -939,4 +943,70 @@ export class DefaultService {
},
});
}
/**
* Jitsi Events Webhook
* Handle Prosody event-sync webhooks from Jitsi Meet.
*
* Expected event types:
* - muc-occupant-joined: participant joined the room
* - muc-occupant-left: participant left the room
* - jibri-recording-on: recording started
* - jibri-recording-off: recording stopped
* @param data The data for the request.
* @param data.requestBody
* @returns unknown Successful Response
* @throws ApiError
*/
public v1JitsiEventsWebhook(
data: V1JitsiEventsWebhookData,
): CancelablePromise<V1JitsiEventsWebhookResponse> {
return this.httpRequest.request({
method: "POST",
url: "/v1/jitsi/events",
body: data.requestBody,
mediaType: "application/json",
errors: {
422: "Validation Error",
},
});
}
/**
* Jibri Recording Complete
* Handle Jibri recording completion webhook.
*
* This endpoint is called by the Jibri finalize script when a recording
* is completed and uploaded to storage.
* @param data The data for the request.
* @param data.requestBody
* @returns unknown Successful Response
* @throws ApiError
*/
public v1JibriRecordingComplete(
data: V1JibriRecordingCompleteData,
): CancelablePromise<V1JibriRecordingCompleteResponse> {
return this.httpRequest.request({
method: "POST",
url: "/v1/jibri/recording-complete",
body: data.requestBody,
mediaType: "application/json",
errors: {
422: "Validation Error",
},
});
}
/**
* Jitsi Health Check
* Simple health check endpoint for Jitsi webhook configuration.
* @returns unknown Successful Response
* @throws ApiError
*/
public v1JitsiHealthCheck(): CancelablePromise<V1JitsiHealthCheckResponse> {
return this.httpRequest.request({
method: "GET",
url: "/v1/jitsi/health",
});
}
}

View File

@@ -26,6 +26,7 @@ export type CreateRoom = {
is_shared: boolean;
webhook_url: string;
webhook_secret: string;
platform: VideoPlatform;
};
export type CreateTranscript = {
@@ -125,6 +126,22 @@ export type HTTPValidationError = {
detail?: Array<ValidationError>;
};
export type JibriRecordingEvent = {
room_name: string;
recording_file: string;
recording_status: string;
timestamp: string;
};
export type JitsiWebhookEvent = {
event: string;
room: string;
timestamp: string;
data?: {
[key: string]: unknown;
};
};
export type Meeting = {
id: string;
room_name: string;
@@ -176,6 +193,7 @@ export type Room = {
recording_type: string;
recording_trigger: string;
is_shared: boolean;
platform?: VideoPlatform;
};
export type RoomDetails = {
@@ -191,8 +209,9 @@ export type RoomDetails = {
recording_type: string;
recording_trigger: string;
is_shared: boolean;
webhook_url: string;
webhook_secret: string;
platform?: VideoPlatform;
webhook_url: string | null;
webhook_secret: string | null;
};
export type RtcOffer = {
@@ -206,10 +225,7 @@ export type SearchResponse = {
* Total number of search results
*/
total: number;
/**
* Search query text
*/
query: string;
query?: string | null;
/**
* Results per page
*/
@@ -302,6 +318,7 @@ export type UpdateRoom = {
is_shared: boolean;
webhook_url: string;
webhook_secret: string;
platform: VideoPlatform;
};
export type UpdateTranscript = {
@@ -328,6 +345,8 @@ export type ValidationError = {
type: string;
};
export type VideoPlatform = "whereby" | "jitsi";
export type WebhookTestResult = {
success: boolean;
message?: string;
@@ -621,6 +640,20 @@ export type V1WherebyWebhookData = {
export type V1WherebyWebhookResponse = unknown;
export type V1JitsiEventsWebhookData = {
requestBody: JitsiWebhookEvent;
};
export type V1JitsiEventsWebhookResponse = unknown;
export type V1JibriRecordingCompleteData = {
requestBody: JibriRecordingEvent;
};
export type V1JibriRecordingCompleteResponse = unknown;
export type V1JitsiHealthCheckResponse = unknown;
export type $OpenApiTs = {
"/metrics": {
get: {
@@ -1142,4 +1175,44 @@ export type $OpenApiTs = {
};
};
};
"/v1/jitsi/events": {
post: {
req: V1JitsiEventsWebhookData;
res: {
/**
* Successful Response
*/
200: unknown;
/**
* Validation Error
*/
422: HTTPValidationError;
};
};
};
"/v1/jibri/recording-complete": {
post: {
req: V1JibriRecordingCompleteData;
res: {
/**
* Successful Response
*/
200: unknown;
/**
* Validation Error
*/
422: HTTPValidationError;
};
};
};
"/v1/jitsi/health": {
get: {
res: {
/**
* Successful Response
*/
200: unknown;
};
};
};
};