diff --git a/apps/api/v2/src/modules/conferencing/conferencing.module.ts b/apps/api/v2/src/modules/conferencing/conferencing.module.ts new file mode 100644 index 0000000000..efad9fd8ee --- /dev/null +++ b/apps/api/v2/src/modules/conferencing/conferencing.module.ts @@ -0,0 +1,22 @@ +import { ConferencingController } from "@/modules/conferencing/controllers/conferencing.controller"; +import { ConferencingRepository } from "@/modules/conferencing/repositories/conferencing.respository"; +import { ConferencingService } from "@/modules/conferencing/services/conferencing.service"; +import { GoogleMeetService } from "@/modules/conferencing/services/google-meet.service"; +import { CredentialsRepository } from "@/modules/credentials/credentials.repository"; +import { PrismaModule } from "@/modules/prisma/prisma.module"; +import { UsersRepository } from "@/modules/users/users.repository"; +import { Module } from "@nestjs/common"; + +@Module({ + imports: [PrismaModule], + providers: [ + ConferencingService, + ConferencingRepository, + GoogleMeetService, + CredentialsRepository, + UsersRepository, + ], + exports: [], + controllers: [ConferencingController], +}) +export class ConferencingModule {} diff --git a/apps/api/v2/src/modules/conferencing/controllers/conferencing.controller.e2e-spec.ts b/apps/api/v2/src/modules/conferencing/controllers/conferencing.controller.e2e-spec.ts new file mode 100644 index 0000000000..fbcd9a3799 --- /dev/null +++ b/apps/api/v2/src/modules/conferencing/controllers/conferencing.controller.e2e-spec.ts @@ -0,0 +1,137 @@ +import { bootstrap } from "@/app"; +import { AppModule } from "@/app.module"; +import { ConferencingAppsOutputDto } from "@/modules/conferencing/outputs/get-conferencing-apps.output"; +import { PrismaModule } from "@/modules/prisma/prisma.module"; +import { TokensModule } from "@/modules/tokens/tokens.module"; +import { UsersModule } from "@/modules/users/users.module"; +import { INestApplication } from "@nestjs/common"; +import { NestExpressApplication } from "@nestjs/platform-express"; +import { Test } from "@nestjs/testing"; +import { User } from "@prisma/client"; +import * as request from "supertest"; +import { CredentialsRepositoryFixture } from "test/fixtures/repository/credentials.repository.fixture"; +import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture"; +import { withApiAuth } from "test/utils/withApiAuth"; + +import { + ERROR_STATUS, + GOOGLE_CALENDAR_ID, + GOOGLE_CALENDAR_TYPE, + GOOGLE_MEET, + GOOGLE_MEET_TYPE, + SUCCESS_STATUS, +} from "@calcom/platform-constants"; +import { ApiErrorResponse, ApiSuccessResponse } from "@calcom/platform-types"; + +describe("Conferencing Endpoints", () => { + describe("conferencing controller e2e tests", () => { + let app: INestApplication; + + let userRepositoryFixture: UserRepositoryFixture; + let credentialsRepositoryFixture: CredentialsRepositoryFixture; + + const userEmail = "conferencing-controller-user-e2e@api.com"; + let user: User; + + beforeAll(async () => { + const moduleRef = await withApiAuth( + userEmail, + Test.createTestingModule({ + imports: [AppModule, PrismaModule, UsersModule, TokensModule], + }) + ).compile(); + + userRepositoryFixture = new UserRepositoryFixture(moduleRef); + credentialsRepositoryFixture = new CredentialsRepositoryFixture(moduleRef); + + user = await userRepositoryFixture.create({ + email: userEmail, + username: userEmail, + }); + + app = moduleRef.createNestApplication(); + bootstrap(app as NestExpressApplication); + + await app.init(); + }); + + it("should get all the conferencing apps of the auth user", async () => { + return request(app.getHttpServer()) + .get(`/v2/conferencing`) + .expect(200) + .then((response) => { + const responseBody: ApiSuccessResponse = response.body; + expect(responseBody.status).toEqual(SUCCESS_STATUS); + expect(responseBody.data).toEqual([]); + }); + }); + + it("should fail to connect google meet if google calendar is not connected ", async () => { + return request(app.getHttpServer()) + .post(`/v2/conferencing/google-meet/connect`) + .expect(400) + .then(async () => { + await credentialsRepositoryFixture.create(GOOGLE_CALENDAR_TYPE, {}, user.id, GOOGLE_CALENDAR_ID); + }); + }); + + it("should connect google meet if google calendar is connected ", async () => { + return request(app.getHttpServer()) + .post(`/v2/conferencing/google-meet/connect`) + .expect(200) + .then((response) => { + const responseBody: ApiSuccessResponse = response.body; + expect(responseBody.status).toEqual(SUCCESS_STATUS); + }); + }); + + it("should set google meet as default conferencing app", async () => { + return request(app.getHttpServer()) + .post(`/v2/conferencing/google-meet/default`) + .expect(200) + .then(async () => { + const updatedUser = await userRepositoryFixture.get(user.id); + + expect(updatedUser).toBeDefined(); + + if (updatedUser) { + const metadata = updatedUser.metadata as { defaultConferencingApp?: { appSlug?: string } }; + expect(metadata?.defaultConferencingApp?.appSlug).toEqual(GOOGLE_MEET); + } + }); + }); + + it("should get all the conferencing apps of the auth user, and contain google meet", async () => { + return request(app.getHttpServer()) + .get(`/v2/conferencing`) + .expect(200) + .then((response) => { + const responseBody: ApiSuccessResponse = response.body; + expect(responseBody.status).toEqual(SUCCESS_STATUS); + const googleMeet = responseBody.data.find((app) => app.type === GOOGLE_MEET_TYPE); + expect(googleMeet?.userId).toEqual(user.id); + }); + }); + + it("should disconnect google meet", async () => { + return request(app.getHttpServer()).delete(`/v2/conferencing/google-meet/disconnect`).expect(200); + }); + + it("should get all the conferencing apps of the auth user, and not contain google meet", async () => { + return request(app.getHttpServer()) + .get(`/v2/conferencing`) + .expect(200) + .then((response) => { + const responseBody: ApiSuccessResponse = response.body; + expect(responseBody.status).toEqual(SUCCESS_STATUS); + const googleMeet = responseBody.data.find((app) => app.type === GOOGLE_MEET_TYPE); + expect(googleMeet).toBeUndefined(); + }); + }); + + afterAll(async () => { + await userRepositoryFixture.deleteByEmail(user.email); + await app.close(); + }); + }); +}); diff --git a/apps/api/v2/src/modules/conferencing/controllers/conferencing.controller.ts b/apps/api/v2/src/modules/conferencing/controllers/conferencing.controller.ts new file mode 100644 index 0000000000..14f0e42f2c --- /dev/null +++ b/apps/api/v2/src/modules/conferencing/controllers/conferencing.controller.ts @@ -0,0 +1,121 @@ +import { API_VERSIONS_VALUES } from "@/lib/api-versions"; +import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator"; +import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard"; +import { + ConferencingAppsOutputResponseDto, + ConferencingAppOutputResponseDto, + ConferencingAppsOutputDto, +} from "@/modules/conferencing/outputs/get-conferencing-apps.output"; +import { SetDefaultConferencingAppOutputResponseDto } from "@/modules/conferencing/outputs/set-default-conferencing-app.output"; +import { ConferencingService } from "@/modules/conferencing/services/conferencing.service"; +import { GoogleMeetService } from "@/modules/conferencing/services/google-meet.service"; +import { + Controller, + Get, + HttpCode, + HttpStatus, + Logger, + UseGuards, + Post, + Param, + BadRequestException, + Delete, +} from "@nestjs/common"; +import { ApiOperation, ApiTags as DocsTags } from "@nestjs/swagger"; +import { plainToInstance } from "class-transformer"; + +import { CONFERENCING_APPS, GOOGLE_MEET, SUCCESS_STATUS } from "@calcom/platform-constants"; + +@Controller({ + path: "/v2/conferencing", + version: API_VERSIONS_VALUES, +}) +@DocsTags("Platform / Conferencing") +export class ConferencingController { + private readonly logger = new Logger("Platform Gcal Provider"); + + constructor( + private readonly conferencingService: ConferencingService, + private readonly googleMeetService: GoogleMeetService + ) {} + + @Post("/:app/connect") + @HttpCode(HttpStatus.OK) + @UseGuards(ApiAuthGuard) + @ApiOperation({ summary: "Connect your conferencing application" }) + async connect( + @GetUser("id") userId: number, + @Param("app") app: string + ): Promise { + switch (app) { + case GOOGLE_MEET: + const credential = await this.googleMeetService.connectGoogleMeetApp(userId); + + return { status: SUCCESS_STATUS, data: plainToInstance(ConferencingAppsOutputDto, credential) }; + + default: + throw new BadRequestException( + "Invalid conferencing app, available apps are: ", + CONFERENCING_APPS.join(", ") + ); + } + } + + @Get("/") + @HttpCode(HttpStatus.OK) + @UseGuards(ApiAuthGuard) + @ApiOperation({ summary: "List your conferencing applications" }) + async listConferencingApps(@GetUser("id") userId: number): Promise { + const conferencingApps = await this.conferencingService.getConferencingApps(userId); + + const data = conferencingApps.map((conferencingApps) => + plainToInstance(ConferencingAppsOutputDto, conferencingApps) + ); + + return { status: SUCCESS_STATUS, data }; + } + + @Post("/:app/default") + @HttpCode(HttpStatus.OK) + @UseGuards(ApiAuthGuard) + @ApiOperation({ summary: "Set your default conferencing application" }) + async default( + @GetUser("id") userId: number, + @Param("app") app: string + ): Promise { + switch (app) { + case GOOGLE_MEET: + await this.googleMeetService.setDefault(userId); + + return { status: SUCCESS_STATUS }; + + default: + throw new BadRequestException( + "Invalid conferencing app, available apps are: ", + CONFERENCING_APPS.join(", ") + ); + } + } + + @Delete("/:app/disconnect") + @HttpCode(HttpStatus.OK) + @UseGuards(ApiAuthGuard) + @ApiOperation({ summary: "Disconnect your conferencing application" }) + async disconnect( + @GetUser("id") userId: number, + @Param("app") app: string + ): Promise { + switch (app) { + case GOOGLE_MEET: + const credential = await this.googleMeetService.disconnectGoogleMeetApp(userId); + + return { status: SUCCESS_STATUS, data: plainToInstance(ConferencingAppsOutputDto, credential) }; + + default: + throw new BadRequestException( + "Invalid conferencing app, available apps are: ", + CONFERENCING_APPS.join(", ") + ); + } + } +} diff --git a/apps/api/v2/src/modules/conferencing/outputs/get-conferencing-apps.output.ts b/apps/api/v2/src/modules/conferencing/outputs/get-conferencing-apps.output.ts new file mode 100644 index 0000000000..c1f4510384 --- /dev/null +++ b/apps/api/v2/src/modules/conferencing/outputs/get-conferencing-apps.output.ts @@ -0,0 +1,50 @@ +import { ApiProperty } from "@nestjs/swagger"; +import { Expose, Type } from "class-transformer"; +import { IsString, ValidateNested, IsEnum, IsNumber, IsOptional, IsBoolean } from "class-validator"; + +import { ERROR_STATUS, GOOGLE_MEET_TYPE, SUCCESS_STATUS } from "@calcom/platform-constants"; + +export class ConferencingAppsOutputDto { + @Expose() + @IsNumber() + @ApiProperty({ description: "Id of the conferencing app credentials" }) + id!: number; + + @ApiProperty({ example: GOOGLE_MEET_TYPE, description: "Type of conferencing app" }) + @Expose() + @IsString() + type!: string; + + @ApiProperty({ description: "Id of the user associated to the conferencing app" }) + @Expose() + @IsNumber() + userId!: number; + + @ApiProperty({ example: true, description: "Whether if the connection is working or not." }) + @Expose() + @IsBoolean() + @IsOptional() + invalid?: boolean | null; +} + +export class ConferencingAppsOutputResponseDto { + @ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] }) + @IsEnum([SUCCESS_STATUS, ERROR_STATUS]) + status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS; + + @Expose() + @ValidateNested() + @Type(() => ConferencingAppsOutputDto) + data!: ConferencingAppsOutputDto[]; +} + +export class ConferencingAppOutputResponseDto { + @ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] }) + @IsEnum([SUCCESS_STATUS, ERROR_STATUS]) + status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS; + + @Expose() + @ValidateNested() + @Type(() => ConferencingAppsOutputDto) + data!: ConferencingAppsOutputDto; +} diff --git a/apps/api/v2/src/modules/conferencing/outputs/set-default-conferencing-app.output.ts b/apps/api/v2/src/modules/conferencing/outputs/set-default-conferencing-app.output.ts new file mode 100644 index 0000000000..8e1660fedb --- /dev/null +++ b/apps/api/v2/src/modules/conferencing/outputs/set-default-conferencing-app.output.ts @@ -0,0 +1,10 @@ +import { ApiProperty } from "@nestjs/swagger"; +import { IsEnum } from "class-validator"; + +import { ERROR_STATUS, SUCCESS_STATUS } from "@calcom/platform-constants"; + +export class SetDefaultConferencingAppOutputResponseDto { + @ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] }) + @IsEnum([SUCCESS_STATUS, ERROR_STATUS]) + status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS; +} diff --git a/apps/api/v2/src/modules/conferencing/repositories/conferencing.respository.ts b/apps/api/v2/src/modules/conferencing/repositories/conferencing.respository.ts new file mode 100644 index 0000000000..481b52b65b --- /dev/null +++ b/apps/api/v2/src/modules/conferencing/repositories/conferencing.respository.ts @@ -0,0 +1,25 @@ +import { PrismaReadService } from "@/modules/prisma/prisma-read.service"; +import { PrismaWriteService } from "@/modules/prisma/prisma-write.service"; +import { Injectable } from "@nestjs/common"; + +import { GOOGLE_MEET_TYPE } from "@calcom/platform-constants"; + +@Injectable() +export class ConferencingRepository { + constructor(private readonly dbRead: PrismaReadService, private readonly dbWrite: PrismaWriteService) {} + + async findConferencingApps(userId: number) { + return this.dbRead.prisma.credential.findMany({ + where: { + userId, + type: { endsWith: "_video" }, + }, + }); + } + + async findGoogleMeet(userId: number) { + return this.dbRead.prisma.credential.findFirst({ + where: { userId, type: GOOGLE_MEET_TYPE }, + }); + } +} diff --git a/apps/api/v2/src/modules/conferencing/services/conferencing.service.ts b/apps/api/v2/src/modules/conferencing/services/conferencing.service.ts new file mode 100644 index 0000000000..1ef8ec4a0d --- /dev/null +++ b/apps/api/v2/src/modules/conferencing/services/conferencing.service.ts @@ -0,0 +1,14 @@ +import { ConferencingRepository } from "@/modules/conferencing/repositories/conferencing.respository"; +import { Logger } from "@nestjs/common"; +import { Injectable } from "@nestjs/common"; + +@Injectable() +export class ConferencingService { + private logger = new Logger("ConferencingService"); + + constructor(private readonly conferencingRepository: ConferencingRepository) {} + + async getConferencingApps(userId: number) { + return this.conferencingRepository.findConferencingApps(userId); + } +} diff --git a/apps/api/v2/src/modules/conferencing/services/google-meet.service.ts b/apps/api/v2/src/modules/conferencing/services/google-meet.service.ts new file mode 100644 index 0000000000..233a641a64 --- /dev/null +++ b/apps/api/v2/src/modules/conferencing/services/google-meet.service.ts @@ -0,0 +1,71 @@ +import { ConferencingRepository } from "@/modules/conferencing/repositories/conferencing.respository"; +import { CredentialsRepository } from "@/modules/credentials/credentials.repository"; +import { UsersRepository } from "@/modules/users/users.repository"; +import { BadRequestException, InternalServerErrorException, Logger } from "@nestjs/common"; +import { Injectable } from "@nestjs/common"; + +import { GOOGLE_CALENDAR_TYPE, GOOGLE_MEET_TYPE, GOOGLE_MEET } from "@calcom/platform-constants"; + +@Injectable() +export class GoogleMeetService { + private logger = new Logger("GoogleMeetService"); + + constructor( + private readonly conferencingRepository: ConferencingRepository, + private readonly credentialsRepository: CredentialsRepository, + private readonly usersRepository: UsersRepository + ) {} + + async connectGoogleMeetApp(userId: number) { + const googleCalendar = await this.credentialsRepository.getByTypeAndUserId(GOOGLE_CALENDAR_TYPE, userId); + + if (!googleCalendar) { + throw new BadRequestException("Google Meet app requires a Google Calendar connection"); + } + + if (googleCalendar.invalid) { + throw new BadRequestException( + "Google Meet app requires a valid Google Calendar connection, please reconnect Google Calendar." + ); + } + + const googleMeet = await this.conferencingRepository.findGoogleMeet(userId); + + if (googleMeet) { + throw new BadRequestException("Google Meet is already connected."); + } + + const googleMeetCredential = await this.credentialsRepository.upsertAppCredential( + GOOGLE_MEET_TYPE, + {}, + userId + ); + + return googleMeetCredential; + } + + async disconnectGoogleMeetApp(userId: number) { + const googleMeet = await this.conferencingRepository.findGoogleMeet(userId); + + if (!googleMeet) { + throw new BadRequestException("Google Meet is not connected."); + } + + const googleMeetCredential = await this.credentialsRepository.deleteUserCredentialById( + userId, + googleMeet.id + ); + + return googleMeetCredential; + } + + async setDefault(userId: number) { + const user = await this.usersRepository.setDefaultConferencingApp(userId, GOOGLE_MEET); + const metadata = user.metadata as { defaultConferencingApp?: { appSlug?: string } }; + + if (metadata?.defaultConferencingApp?.appSlug !== GOOGLE_MEET) { + throw new InternalServerErrorException("Could not set Google Meet as default conferencing app"); + } + return true; + } +} diff --git a/apps/api/v2/src/modules/credentials/credentials.repository.ts b/apps/api/v2/src/modules/credentials/credentials.repository.ts index 3ae5dd5798..ea830c2d64 100644 --- a/apps/api/v2/src/modules/credentials/credentials.repository.ts +++ b/apps/api/v2/src/modules/credentials/credentials.repository.ts @@ -88,6 +88,12 @@ export class CredentialsRepository { }, }); } + + async deleteUserCredentialById(userId: number, credentialId: number) { + return await this.dbWrite.prisma.credential.delete({ + where: { id: credentialId, userId }, + }); + } } export type CredentialsWithUserEmail = Awaited< diff --git a/apps/api/v2/src/modules/endpoints.module.ts b/apps/api/v2/src/modules/endpoints.module.ts index 6401726b20..33e6cf6728 100644 --- a/apps/api/v2/src/modules/endpoints.module.ts +++ b/apps/api/v2/src/modules/endpoints.module.ts @@ -1,6 +1,7 @@ import { PlatformEndpointsModule } from "@/ee/platform-endpoints-module"; import { AtomsModule } from "@/modules/atoms/atoms.module"; import { BillingModule } from "@/modules/billing/billing.module"; +import { ConferencingModule } from "@/modules/conferencing/conferencing.module"; import { DestinationCalendarsModule } from "@/modules/destination-calendars/destination-calendars.module"; import { OAuthClientModule } from "@/modules/oauth-clients/oauth-client.module"; import { StripeModule } from "@/modules/stripe/stripe.module"; @@ -22,6 +23,7 @@ import { WebhooksModule } from "./webhooks/webhooks.module"; DestinationCalendarsModule, AtomsModule, StripeModule, + ConferencingModule, ], }) export class EndpointsModule implements NestModule { diff --git a/apps/api/v2/src/modules/users/users.repository.ts b/apps/api/v2/src/modules/users/users.repository.ts index 444b87a173..6d294361ba 100644 --- a/apps/api/v2/src/modules/users/users.repository.ts +++ b/apps/api/v2/src/modules/users/users.repository.ts @@ -2,7 +2,7 @@ import { PrismaReadService } from "@/modules/prisma/prisma-read.service"; import { PrismaWriteService } from "@/modules/prisma/prisma-write.service"; import { CreateManagedUserInput } from "@/modules/users/inputs/create-managed-user.input"; import { UpdateManagedUserInput } from "@/modules/users/inputs/update-managed-user.input"; -import { Injectable } from "@nestjs/common"; +import { Injectable, NotFoundException } from "@nestjs/common"; import type { Profile, User, Team } from "@prisma/client"; export type UserWithProfile = User & { @@ -223,4 +223,29 @@ export class UsersRepository { }); return profiles.map((profile) => profile.user); } + + async setDefaultConferencingApp(userId: number, appSlug?: string, appLink?: string) { + const user = await this.findById(userId); + + if (!user) { + throw new NotFoundException("user not found"); + } + + return await this.dbWrite.prisma.user.update({ + data: { + metadata: + typeof user.metadata === "object" + ? { + ...user.metadata, + defaultConferencingApp: { + appSlug: appSlug, + appLink: appLink, + }, + } + : {}, + }, + + where: { id: userId }, + }); + } } diff --git a/apps/api/v2/swagger/documentation.json b/apps/api/v2/swagger/documentation.json index ce98a4dd67..984d0b685f 100644 --- a/apps/api/v2/swagger/documentation.json +++ b/apps/api/v2/swagger/documentation.json @@ -63,6 +63,121 @@ ] } }, + "/v2/conferencing/{app}/connect": { + "post": { + "operationId": "ConferencingController_connect", + "summary": "connect your conferencing application", + "parameters": [ + { + "name": "app", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConferencingAppOutputResponseDto" + } + } + } + } + }, + "tags": [ + "Platform / Conferencing" + ] + } + }, + "/v2/conferencing": { + "get": { + "operationId": "ConferencingController_listConferencingApps", + "summary": "list your conferencing applications", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConferencingAppsOutputResponseDto" + } + } + } + } + }, + "tags": [ + "Platform / Conferencing" + ] + } + }, + "/v2/conferencing/{app}/default": { + "post": { + "operationId": "ConferencingController_default", + "summary": "set your default conferencing application", + "parameters": [ + { + "name": "app", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SetDefaultConferencingAppOutputResponseDto" + } + } + } + } + }, + "tags": [ + "Platform / Conferencing" + ] + } + }, + "/v2/conferencing/{app}/disconnect": { + "delete": { + "operationId": "ConferencingController_disconnect", + "summary": "disconnect your conferencing application", + "parameters": [ + { + "name": "app", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConferencingAppOutputResponseDto" + } + } + } + } + }, + "tags": [ + "Platform / Conferencing" + ] + } + }, "/v2/gcal/oauth/auth-url": { "get": { "operationId": "GcalController_redirect", @@ -13000,6 +13115,94 @@ "status", "data" ] + }, + "ConferencingAppsOutputDto": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "Id of the conferencing app credentials" + }, + "type": { + "type": "string", + "example": "google_video", + "description": "type of conferencing app" + }, + "userId": { + "type": "number", + "description": "Id of the user associated to the conferencing app" + }, + "invalid": { + "type": "boolean", + "nullable": true, + "example": true, + "description": "Whether if the connection is working or not." + } + }, + "required": [ + "id", + "type", + "userId" + ] + }, + "ConferencingAppOutputResponseDto": { + "type": "object", + "properties": { + "status": { + "type": "string", + "example": "success", + "enum": [ + "success", + "error" + ] + }, + "data": { + "$ref": "#/components/schemas/ConferencingAppsOutputDto" + } + }, + "required": [ + "status", + "data" + ] + }, + "ConferencingAppsOutputResponseDto": { + "type": "object", + "properties": { + "status": { + "type": "string", + "example": "success", + "enum": [ + "success", + "error" + ] + }, + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ConferencingAppsOutputDto" + } + } + }, + "required": [ + "status", + "data" + ] + }, + "SetDefaultConferencingAppOutputResponseDto": { + "type": "object", + "properties": { + "status": { + "type": "string", + "example": "success", + "enum": [ + "success", + "error" + ] + } + }, + "required": [ + "status" + ] } } } diff --git a/docs/api-reference/v2/openapi.json b/docs/api-reference/v2/openapi.json index b595f935e8..ba677723b1 100644 --- a/docs/api-reference/v2/openapi.json +++ b/docs/api-reference/v2/openapi.json @@ -59,6 +59,84 @@ "tags": ["Platform / Cal Provider"] } }, + "/v2/conferencing": { + "get": { + "operationId": "ConferencingController_listConferencingApps", + "summary": "List your conferencing applications", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConferencingAppsOutputResponseDto" + } + } + } + } + }, + "tags": ["Platform / Conferencing"] + } + }, + "/v2/conferencing/{app}/default": { + "post": { + "operationId": "ConferencingController_default", + "summary": "Set your default conferencing application", + "parameters": [ + { + "name": "app", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SetDefaultConferencingAppOutputResponseDto" + } + } + } + } + }, + "tags": ["Platform / Conferencing"] + } + }, + "/v2/conferencing/{app}/disconnect": { + "delete": { + "operationId": "ConferencingController_disconnect", + "summary": "Disconnect your conferencing application", + "parameters": [ + { + "name": "app", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConferencingAppOutputResponseDto" + } + } + } + } + }, + "tags": ["Platform / Conferencing"] + } + }, "/v2/gcal/oauth/auth-url": { "get": { "operationId": "GcalController_redirect", diff --git a/packages/platform/constants/apps.ts b/packages/platform/constants/apps.ts index 8469988d89..91e0762b2c 100644 --- a/packages/platform/constants/apps.ts +++ b/packages/platform/constants/apps.ts @@ -13,9 +13,16 @@ export const APPLE_CALENDAR_ID = "apple-calendar"; export const CALENDARS = [GOOGLE_CALENDAR, OFFICE_365_CALENDAR, APPLE_CALENDAR] as const; export const CREDENTIAL_CALENDARS = [APPLE_CALENDAR] as const; +export const GOOGLE_MEET = "google-meet"; +export const GOOGLE_MEET_TYPE = "google_video"; +export const GOOGLE_MEET_ID = "google-meet"; + +export const CONFERENCING_APPS = [GOOGLE_MEET]; + export const APPS_TYPE_ID_MAPPING = { [GOOGLE_CALENDAR_TYPE]: GOOGLE_CALENDAR_ID, [OFFICE_365_CALENDAR_TYPE]: OFFICE_365_CALENDAR_ID, [APPLE_CALENDAR_TYPE]: APPLE_CALENDAR_ID, [ICS_CALENDAR_TYPE]: ICS_CALENDAR_ID, + [GOOGLE_MEET_TYPE]: GOOGLE_MEET_ID, } as const;