feat: add api v2 endpoints for cal video (#21775)

* feat: add api v2 endpoints for cal video

* feat: add endpoint for fetching transcripts

* chore: fetch only references

* feat: add cal video and transcript

* chore: undp open api

* chore: move it to service

* chore: bump libraries

---------

Co-authored-by: Lauris Skraucis <lauris.skraucis@gmail.com>
Co-authored-by: supalarry <laurisskraucis@gmail.com>
This commit is contained in:
authored and GitHub committed 2025-06-13 12:02:58 +00:00
1 parent 8078c5657b
commit 874973d78f
14 files changed
+582 -10

No files matched your search

+1 -1
View File
@@ -37,7 +37,7 @@
"@axiomhq/winston": "^1.2.0",
"@calcom/platform-constants": "*",
"@calcom/platform-enums": "*",
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.217",
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.218",
"@calcom/platform-libraries-0.0.2": "npm:@calcom/platform-libraries@0.0.2",
"@calcom/platform-types": "*",
"@calcom/platform-utils": "*",
@@ -3,6 +3,7 @@ import { BookingsRepository_2024_08_13 } from "@/ee/bookings/2024-08-13/bookings
import { BookingsController_2024_08_13 } from "@/ee/bookings/2024-08-13/controllers/bookings.controller";
import { BookingReferencesService_2024_08_13 } from "@/ee/bookings/2024-08-13/services/booking-references.service";
import { BookingsService_2024_08_13 } from "@/ee/bookings/2024-08-13/services/bookings.service";
import { CalVideoService } from "@/ee/bookings/2024-08-13/services/cal-video.service";
import { ErrorsBookingsService_2024_08_13 } from "@/ee/bookings/2024-08-13/services/errors.service";
import { InputBookingsService_2024_08_13 } from "@/ee/bookings/2024-08-13/services/input.service";
import { OutputBookingReferencesService_2024_08_13 } from "@/ee/bookings/2024-08-13/services/output-booking-references.service";
@@ -77,6 +78,7 @@ import { Module } from "@nestjs/common";
ErrorsBookingsService_2024_08_13,
BookingReferencesService_2024_08_13,
BookingReferencesRepository_2024_08_13,
CalVideoService,
],
controllers: [BookingsController_2024_08_13],
exports: [InputBookingsService_2024_08_13, OutputBookingsService_2024_08_13, BookingsService_2024_08_13],
@@ -169,4 +169,15 @@ export class BookingsRepository_2024_08_13 {
},
});
}
async getByUidWithBookingReference(uid: string) {
return this.dbRead.prisma.booking.findUnique({
where: {
uid,
},
select: {
references: true,
},
});
}
}
@@ -9,6 +9,7 @@ import { ReassignBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outpu
import { RescheduleBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/reschedule-booking.output";
import { BookingReferencesService_2024_08_13 } from "@/ee/bookings/2024-08-13/services/booking-references.service";
import { BookingsService_2024_08_13 } from "@/ee/bookings/2024-08-13/services/bookings.service";
import { CalVideoService } from "@/ee/bookings/2024-08-13/services/cal-video.service";
import { VERSION_2024_08_13_VALUE, VERSION_2024_08_13 } from "@/lib/api-versions";
import { API_KEY_OR_ACCESS_TOKEN_HEADER } from "@/lib/docs/headers";
import { PlatformPlan } from "@/modules/auth/decorators/billing/platform-plan.decorator";
@@ -54,6 +55,8 @@ import {
RescheduleBookingInput_2024_08_13,
RescheduleBookingInputPipe,
RescheduleSeatedBookingInput_2024_08_13,
GetBookingRecordingsOutput,
GetBookingTranscriptsOutput,
} from "@calcom/platform-types";
import {
CreateBookingInputPipe,
@@ -88,7 +91,8 @@ export class BookingsController_2024_08_13 {
constructor(
private readonly bookingsService: BookingsService_2024_08_13,
private readonly usersService: UsersService,
private readonly bookingReferencesService: BookingReferencesService_2024_08_13
private readonly bookingReferencesService: BookingReferencesService_2024_08_13,
private readonly calVideoService: CalVideoService
) {}
@Post("/")
@@ -171,6 +175,36 @@ export class BookingsController_2024_08_13 {
};
}
@Get("/:bookingUid/recordings")
@UseGuards(BookingUidGuard)
@ApiOperation({
summary: "Get all the recordings for the booking",
description: `Fetches all the recordings for the booking \`:bookingUid\``,
})
async getBookingRecordings(@Param("bookingUid") bookingUid: string): Promise<GetBookingRecordingsOutput> {
const recordings = await this.calVideoService.getRecordings(bookingUid);
return {
status: SUCCESS_STATUS,
data: recordings,
};
}
@Get("/:bookingUid/transcripts")
@UseGuards(BookingUidGuard)
@ApiOperation({
summary: "Get all the transcripts download links for the booking",
description: `Fetches all the transcripts download links for the booking \`:bookingUid\``,
})
async getBookingTranscripts(@Param("bookingUid") bookingUid: string): Promise<GetBookingTranscriptsOutput> {
const transcripts = await this.calVideoService.getTranscripts(bookingUid);
return {
status: SUCCESS_STATUS,
data: transcripts ?? [],
};
}
@Get("/")
@UseGuards(ApiAuthGuard)
@ApiHeader(API_KEY_OR_ACCESS_TOKEN_HEADER)
@@ -0,0 +1,83 @@
import { BookingsRepository_2024_08_13 } from "@/ee/bookings/2024-08-13/bookings.repository";
import { OutputBookingsService_2024_08_13 } from "@/ee/bookings/2024-08-13/services/output.service";
import { Injectable, Logger, NotFoundException } from "@nestjs/common";
import {
getRecordingsOfCalVideoByRoomName,
getAllTranscriptsAccessLinkFromRoomName,
getDownloadLinkOfCalVideoByRecordingId,
} from "@calcom/platform-libraries";
@Injectable()
export class CalVideoService {
private readonly logger = new Logger("CalVideoService");
constructor(
private readonly bookingsRepository: BookingsRepository_2024_08_13,
private readonly outputService: OutputBookingsService_2024_08_13
) {}
async getRecordings(bookingUid: string) {
const booking = await this.bookingsRepository.getByUidWithBookingReference(bookingUid);
if (!booking) {
throw new NotFoundException(`Booking with uid=${bookingUid} was not found in the database`);
}
const roomName =
booking?.references?.filter((reference) => reference.type === "daily_video")?.pop()?.meetingId ??
undefined;
if (!roomName) {
throw new NotFoundException(`No Cal Video reference found with booking uid ${bookingUid}`);
}
const recordings = await getRecordingsOfCalVideoByRoomName(roomName);
if (!recordings || !("data" in recordings)) return [];
const recordingWithDownloadLink = recordings.data.map((recording) => {
return getDownloadLinkOfCalVideoByRecordingId(recording.id)
.then((res: { download_link: string } | undefined) => ({
id: recording.id,
roomName: recording.room_name,
startTs: recording.start_ts,
status: recording.status,
maxParticipants: recording.max_participants,
duration: recording.duration,
shareToken: recording.share_token,
downloadLink: res?.download_link,
}))
.catch((err: Error) => ({
id: recording.id,
roomName: recording.room_name,
startTs: recording.start_ts,
status: recording.status,
maxParticipants: recording.max_participants,
duration: recording.duration,
shareToken: recording.share_token,
downloadLink: null,
error: err.message,
}));
});
const allRecordingsWithDownloadLink = await Promise.all(recordingWithDownloadLink);
return allRecordingsWithDownloadLink;
}
async getTranscripts(bookingUid: string) {
const booking = await this.bookingsRepository.getByUidWithBookingReference(bookingUid);
if (!booking) {
throw new NotFoundException(`Booking with uid=${bookingUid} was not found in the database`);
}
const roomName =
booking?.references?.filter((reference) => reference.type === "daily_video")?.pop()?.meetingId ??
undefined;
if (!roomName) {
throw new NotFoundException(`No Cal Video reference found with booking uid ${bookingUid}`);
}
const transcripts = await getAllTranscriptsAccessLinkFromRoomName(roomName);
return transcripts;
}
}
@@ -22,7 +22,7 @@ import {
RecurringBookingOutput_2024_08_13,
SeatedAttendee,
} from "@calcom/platform-types";
import { Booking, BookingReference, BookingSeat } from "@calcom/prisma/client";
import { Booking, BookingSeat } from "@calcom/prisma/client";
export const bookingResponsesSchema = z
.object({
+192
View File
@@ -7151,6 +7151,90 @@
]
}
},
"/v2/bookings/{bookingUid}/recordings": {
"get": {
"operationId": "BookingsController_2024_08_13_getBookingRecordings",
"summary": "Get all the recordings for the booking",
"description": "Fetches all the recordings for the booking `:bookingUid`",
"parameters": [
{
"name": "cal-api-version",
"in": "header",
"description": "Must be set to 2024-08-13",
"required": true,
"schema": {
"type": "string",
"default": "2024-08-13"
}
},
{
"name": "bookingUid",
"required": true,
"in": "path",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GetBookingRecordingsOutput"
}
}
}
}
},
"tags": [
"Bookings"
]
}
},
"/v2/bookings/{bookingUid}/transcripts": {
"get": {
"operationId": "BookingsController_2024_08_13_getBookingTranscripts",
"summary": "Get all the transcripts download links for the booking",
"description": "Fetches all the transcripts download links for the booking `:bookingUid`",
"parameters": [
{
"name": "cal-api-version",
"in": "header",
"description": "Must be set to 2024-08-13",
"required": true,
"schema": {
"type": "string",
"default": "2024-08-13"
}
},
{
"name": "bookingUid",
"required": true,
"in": "path",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GetBookingTranscriptsOutput"
}
}
}
}
},
"tags": [
"Bookings"
]
}
},
"/v2/bookings/{bookingUid}/reschedule": {
"post": {
"operationId": "BookingsController_2024_08_13_rescheduleBooking",
@@ -24518,6 +24602,114 @@
"data"
]
},
"RecordingItem": {
"type": "object",
"properties": {
"id": {
"type": "string",
"example": "1234567890"
},
"roomName": {
"type": "string",
"example": "daily-video-room-123"
},
"startTs": {
"type": "number",
"example": 1678901234
},
"status": {
"type": "string",
"example": "completed"
},
"maxParticipants": {
"type": "number",
"example": 10
},
"duration": {
"type": "number",
"example": 3600
},
"shareToken": {
"type": "string",
"example": "share-token-123"
},
"downloadLink": {
"type": "string",
"nullable": true,
"example": "https://cal-video-recordings.s3.us-east-2.amazonaws.com/meetco/123s"
},
"error": {
"type": "string",
"nullable": true,
"example": "Error message"
}
},
"required": [
"id",
"roomName",
"startTs",
"status",
"duration",
"shareToken",
"downloadLink"
]
},
"GetBookingRecordingsOutput": {
"type": "object",
"properties": {
"status": {
"type": "string",
"example": "success",
"enum": [
"success",
"error"
]
},
"error": {
"type": "object"
},
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/RecordingItem"
}
}
},
"required": [
"status",
"data"
]
},
"GetBookingTranscriptsOutput": {
"type": "object",
"properties": {
"status": {
"type": "string",
"example": "success",
"enum": [
"success",
"error"
]
},
"data": {
"example": [
"https://transcript1.com",
"https://transcript2.com"
],
"type": "array",
"items": {
"type": "string"
}
},
"error": {
"type": "object"
}
},
"required": [
"status",
"data"
]
},
"GetBookingsOutput_2024_08_13": {
"type": "object",
"properties": {
+165
View File
@@ -6835,6 +6835,86 @@
"tags": ["Bookings"]
}
},
"/v2/bookings/{bookingUid}/recordings": {
"get": {
"operationId": "BookingsController_2024_08_13_getBookingRecordings",
"summary": "Get all the recordings for the booking",
"description": "Fetches all the recordings for the booking `:bookingUid`",
"parameters": [
{
"name": "cal-api-version",
"in": "header",
"description": "Must be set to 2024-08-13",
"required": true,
"schema": {
"type": "string",
"default": "2024-08-13"
}
},
{
"name": "bookingUid",
"required": true,
"in": "path",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GetBookingRecordingsOutput"
}
}
}
}
},
"tags": ["Bookings"]
}
},
"/v2/bookings/{bookingUid}/transcripts": {
"get": {
"operationId": "BookingsController_2024_08_13_getBookingTranscripts",
"summary": "Get all the transcripts download links for the booking",
"description": "Fetches all the transcripts download links for the booking `:bookingUid`",
"parameters": [
{
"name": "cal-api-version",
"in": "header",
"description": "Must be set to 2024-08-13",
"required": true,
"schema": {
"type": "string",
"default": "2024-08-13"
}
},
{
"name": "bookingUid",
"required": true,
"in": "path",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GetBookingTranscriptsOutput"
}
}
}
}
},
"tags": ["Bookings"]
}
},
"/v2/bookings/{bookingUid}/reschedule": {
"post": {
"operationId": "BookingsController_2024_08_13_rescheduleBooking",
@@ -22496,6 +22576,91 @@
},
"required": ["status", "data"]
},
"RecordingItem": {
"type": "object",
"properties": {
"id": {
"type": "string",
"example": "1234567890"
},
"roomName": {
"type": "string",
"example": "daily-video-room-123"
},
"startTs": {
"type": "number",
"example": 1678901234
},
"status": {
"type": "string",
"example": "completed"
},
"maxParticipants": {
"type": "number",
"example": 10
},
"duration": {
"type": "number",
"example": 3600
},
"shareToken": {
"type": "string",
"example": "share-token-123"
},
"downloadLink": {
"type": "string",
"nullable": true,
"example": "https://cal-video-recordings.s3.us-east-2.amazonaws.com/meetco/123s"
},
"error": {
"type": "string",
"nullable": true,
"example": "Error message"
}
},
"required": ["id", "roomName", "startTs", "status", "duration", "shareToken", "downloadLink"]
},
"GetBookingRecordingsOutput": {
"type": "object",
"properties": {
"status": {
"type": "string",
"example": "success",
"enum": ["success", "error"]
},
"error": {
"type": "object"
},
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/RecordingItem"
}
}
},
"required": ["status", "data"]
},
"GetBookingTranscriptsOutput": {
"type": "object",
"properties": {
"status": {
"type": "string",
"example": "success",
"enum": ["success", "error"]
},
"data": {
"example": ["https://transcript1.com", "https://transcript2.com"],
"type": "array",
"items": {
"type": "string"
}
},
"error": {
"type": "object"
}
},
"required": ["status", "data"]
},
"GetBookingsOutput_2024_08_13": {
"type": "object",
"properties": {
+4 -2
View File
@@ -10,7 +10,7 @@ import logger from "@calcom/lib/logger";
import { getPiiFreeCalendarEvent, getPiiFreeCredential } from "@calcom/lib/piiFreeData";
import { safeStringify } from "@calcom/lib/safeStringify";
import { prisma } from "@calcom/prisma";
import type { GetRecordingsResponseSchema } from "@calcom/prisma/zod-utils";
import type { GetRecordingsResponseSchema, GetAccessLinkResponseSchema } from "@calcom/prisma/zod-utils";
import type { CalendarEvent, EventBusyDate } from "@calcom/types/Calendar";
import type { CredentialPayload } from "@calcom/types/Credential";
import type { EventResult, PartialReference } from "@calcom/types/EventManager";
@@ -258,7 +258,9 @@ const getRecordingsOfCalVideoByRoomName = async (
return videoAdapter?.getRecordings?.(roomName);
};
const getDownloadLinkOfCalVideoByRecordingId = async (recordingId: string) => {
const getDownloadLinkOfCalVideoByRecordingId = async (
recordingId: string
): Promise<GetAccessLinkResponseSchema | undefined> => {
let dailyAppKeys: Awaited<ReturnType<typeof getDailyAppKeys>>;
try {
dailyAppKeys = await getDailyAppKeys();
+6
View File
@@ -74,6 +74,12 @@ export { getTranslation };
export { roundRobinReassignment } from "@calcom/features/ee/round-robin/roundRobinReassignment";
export { roundRobinManualReassignment } from "@calcom/features/ee/round-robin/roundRobinManualReassignment";
export {
getRecordingsOfCalVideoByRoomName,
getDownloadLinkOfCalVideoByRecordingId,
getAllTranscriptsAccessLinkFromRoomName,
} from "@calcom/lib/videoClient";
export { ErrorCode } from "@calcom/lib/errorCodes";
export { validateCustomEventName } from "@calcom/lib/event";
@@ -0,0 +1,58 @@
import { ApiProperty } from "@nestjs/swagger";
import { Type } from "class-transformer";
import { IsEnum, ValidateNested, IsNumber, IsString, IsOptional, IsUrl } from "class-validator";
import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
export class RecordingItem {
@ApiProperty({ example: "1234567890" })
@IsString()
id!: string;
@ApiProperty({ example: "daily-video-room-123" })
@IsString()
roomName!: string;
@ApiProperty({ example: 1678901234 })
@IsNumber()
startTs!: number;
@ApiProperty({ example: "completed" })
@IsString()
status!: string;
@ApiProperty({ example: 10, required: false })
@IsNumber()
@IsOptional()
maxParticipants?: number;
@ApiProperty({ example: 3600 })
@IsNumber()
duration!: number;
@ApiProperty({ example: "share-token-123" })
@IsString()
shareToken!: string;
@ApiProperty({ example: "https://cal-video-recordings.s3.us-east-2.amazonaws.com/meetco/123s" })
@IsUrl()
@IsOptional()
downloadLink?: string | null;
@ApiProperty({ example: "Error message" })
@IsString()
@IsOptional()
error?: string | null;
}
export class GetBookingRecordingsOutput {
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
error?: Error;
@ValidateNested({ each: true })
@Type(() => RecordingItem)
data!: RecordingItem[];
}
@@ -0,0 +1,17 @@
import { ApiProperty } from "@nestjs/swagger";
import { IsArray, IsEnum, IsString } from "class-validator";
import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
export class GetBookingTranscriptsOutput {
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
error?: Error;
@ApiProperty({ type: [String], example: ["https://transcript1.com", "https://transcript2.com"] })
@IsArray()
@IsString({ each: true })
data!: string[];
}
@@ -1,3 +1,5 @@
export * from "./booking.output";
export * from "./get-booking.output";
export * from "./get-bookings.output";
export * from "./get-booking-recordings.output";
export * from "./get-booking-transcripts.output";
+5 -5
View File
@@ -2736,7 +2736,7 @@ __metadata:
"@axiomhq/winston": ^1.2.0
"@calcom/platform-constants": "*"
"@calcom/platform-enums": "*"
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.217"
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.218"
"@calcom/platform-libraries-0.0.2": "npm:@calcom/platform-libraries@0.0.2"
"@calcom/platform-types": "*"
"@calcom/platform-utils": "*"
@@ -3831,13 +3831,13 @@ __metadata:
languageName: node
linkType: hard
"@calcom/platform-libraries@npm:@calcom/platform-libraries@0.0.217":
version: 0.0.217
resolution: "@calcom/platform-libraries@npm:0.0.217"
"@calcom/platform-libraries@npm:@calcom/platform-libraries@0.0.218":
version: 0.0.218
resolution: "@calcom/platform-libraries@npm:0.0.218"
dependencies:
"@calcom/features": "*"
"@calcom/lib": "*"
checksum: 61560539ec098896f63d5a772cce2a2e963b2c39d8ec1943c8dfcfe3fb25bf1bbe21424cc112a3615c95af61307da888cdd9fb8c59ad4c5079e84fb6ff64f209
checksum: 8f73aeffaec3b15707aeaab40d0f056e0156e162d22ba44a19e537d897f654adc32f4bafaa0f7bab846b94211cebeaadc7bb7180ce98204c646c24ed48bdf3ef
languageName: node
linkType: hard