Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: enable platform emails #14471

Merged
merged 20 commits into from
Apr 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
a2cffe7
feat: enable platform emails
ThyMinimalDev Apr 9, 2024
8b59ce7
fixup! feat: enable platform emails
ThyMinimalDev Apr 9, 2024
161000d
fixup! fixup! feat: enable platform emails
ThyMinimalDev Apr 10, 2024
8ee53b8
Merge branch 'main' into chore-platform-emails
ThyMinimalDev Apr 10, 2024
ed2346b
fixup! Merge branch 'main' into chore-platform-emails
ThyMinimalDev Apr 10, 2024
35ec61c
fixup! fixup! Merge branch 'main' into chore-platform-emails
ThyMinimalDev Apr 10, 2024
4f1f3fa
fixup! fixup! fixup! Merge branch 'main' into chore-platform-emails
ThyMinimalDev Apr 10, 2024
266e449
Merge branch 'main' into chore-platform-emails
ThyMinimalDev Apr 11, 2024
c818556
chore: add oauth client repo
ThyMinimalDev Apr 11, 2024
52e25a8
Merge branch 'main' into chore-platform-emails
ThyMinimalDev Apr 11, 2024
e757cf6
chore: enable platform emails using oauth client
ThyMinimalDev Apr 11, 2024
e1d9ff1
Merge branch 'main' into chore-platform-emails
ThyMinimalDev Apr 11, 2024
20c5749
Merge branch 'chore-platform-emails' of github.com:calcom/cal.com int…
ThyMinimalDev Apr 11, 2024
3d6371b
fix: call getConnectedCalendar with dbWrite since it creates destinat…
ThyMinimalDev Apr 12, 2024
70df08c
chore: update doc
ThyMinimalDev Apr 12, 2024
5c4dae5
fixup! chore: update doc
ThyMinimalDev Apr 12, 2024
a8797ad
fixup! fixup! chore: update doc
ThyMinimalDev Apr 12, 2024
337ca86
Merge branch 'main' into chore-platform-emails
ThyMinimalDev Apr 12, 2024
d9d56a1
Merge branch 'main' into chore-platform-emails
ThyMinimalDev Apr 12, 2024
7f10709
Merge branch 'main' into chore-platform-emails
ThyMinimalDev Apr 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 3 additions & 1 deletion apps/api/v2/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import * as Sentry from "@sentry/node";
import * as cookieParser from "cookie-parser";
import helmet from "helmet";

import { X_CAL_CLIENT_ID, X_CAL_SECRET_KEY } from "@calcom/platform-constants";

import { TRPCExceptionFilter } from "./filters/trpc-exception.filter";

export const bootstrap = (app: NestExpressApplication): NestExpressApplication => {
Expand All @@ -26,7 +28,7 @@ export const bootstrap = (app: NestExpressApplication): NestExpressApplication =
app.enableCors({
origin: "*",
methods: ["GET", "PATCH", "DELETE", "HEAD", "POST", "PUT", "OPTIONS"],
allowedHeaders: ["Accept", "Authorization", "Content-Type", "Origin"],
allowedHeaders: [X_CAL_CLIENT_ID, X_CAL_SECRET_KEY, "Accept", "Authorization", "Content-Type", "Origin"],
maxAge: 86_400,
});

Expand Down
3 changes: 2 additions & 1 deletion apps/api/v2/src/ee/bookings/bookings.module.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { BookingsController } from "@/ee/bookings/controllers/bookings.controller";
import { OAuthClientRepository } from "@/modules/oauth-clients/oauth-client.repository";
import { OAuthFlowService } from "@/modules/oauth-clients/services/oauth-flow.service";
import { PrismaModule } from "@/modules/prisma/prisma.module";
import { TokensModule } from "@/modules/tokens/tokens.module";
Expand All @@ -7,7 +8,7 @@ import { Module } from "@nestjs/common";

@Module({
imports: [PrismaModule, TokensModule],
providers: [TokensRepository, OAuthFlowService],
providers: [TokensRepository, OAuthFlowService, OAuthClientRepository],
controllers: [BookingsController],
})
export class BookingsModule {}
99 changes: 79 additions & 20 deletions apps/api/v2/src/ee/bookings/controllers/bookings.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator";
import { Permissions } from "@/modules/auth/decorators/permissions/permissions.decorator";
import { AccessTokenGuard } from "@/modules/auth/guards/access-token/access-token.guard";
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
import { OAuthClientRepository } from "@/modules/oauth-clients/oauth-client.repository";
import { OAuthFlowService } from "@/modules/oauth-clients/services/oauth-flow.service";
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
import {
Expand All @@ -15,6 +16,7 @@ import {
Req,
InternalServerErrorException,
Body,
Headers,
HttpException,
Param,
Get,
Expand All @@ -27,6 +29,7 @@ import { User } from "@prisma/client";
import { Request } from "express";
import { NextApiRequest } from "next/types";

import { X_CAL_CLIENT_ID } from "@calcom/platform-constants";
import { BOOKING_READ, SUCCESS_STATUS } from "@calcom/platform-constants";
import {
getAllUserBookings,
Expand All @@ -45,6 +48,25 @@ import { GetBookingsInput, CancelBookingInput, Status } from "@calcom/platform-t
import { ApiResponse } from "@calcom/platform-types";
import { PrismaClient } from "@calcom/prisma";

type BookingRequest = Request & {
userId?: number;
};

type OAuthRequestParams = {
platformClientId: string;
platformRescheduleUrl: string;
platformCancelUrl: string;
platformBookingUrl: string;
};

const DEFAULT_PLATFORM_PARAMS = {
platformClientId: "",
platformCancelUrl: "",
platformRescheduleUrl: "",
platformBookingUrl: "",
areEmailsEnabled: true,
};

@Controller({
path: "ee/bookings",
version: "2",
Expand All @@ -56,7 +78,8 @@ export class BookingsController {

constructor(
private readonly oAuthFlowService: OAuthFlowService,
private readonly prismaReadService: PrismaReadService
private readonly prismaReadService: PrismaReadService,
private readonly oAuthClientRepository: OAuthClientRepository
) {}

@Get("/")
Expand Down Expand Up @@ -117,13 +140,13 @@ export class BookingsController {

@Post("/")
async createBooking(
@Req() req: Request & { userId?: number },
@Body() _: CreateBookingInput
@Req() req: BookingRequest,
@Body() _: CreateBookingInput,
@Headers(X_CAL_CLIENT_ID) clientId?: string
): Promise<ApiResponse<unknown>> {
req.userId = (await this.getOwnerId(req)) ?? -1;
req.body = { ...req.body, noEmail: true };
const oAuthClientId = clientId?.toString();
try {
const booking = await handleNewBooking(req as unknown as NextApiRequest & { userId?: number });
const booking = await handleNewBooking(await this.createNextApiBookingRequest(req, oAuthClientId));
return {
status: SUCCESS_STATUS,
data: booking,
Expand All @@ -136,15 +159,15 @@ export class BookingsController {

@Post("/:bookingId/cancel")
async cancelBooking(
@Req() req: Request & { userId?: number },
@Req() req: BookingRequest,
@Param("bookingId") bookingId: string,
@Body() body: CancelBookingInput
@Body() _: CancelBookingInput,
@Headers(X_CAL_CLIENT_ID) clientId?: string
): Promise<ApiResponse> {
const oAuthClientId = clientId?.toString();
if (bookingId) {
req.userId = (await this.getOwnerId(req)) ?? -1;
req.body = { ...body, id: parseInt(bookingId) };
try {
await handleCancelBooking(req as unknown as NextApiRequest & { userId?: number });
await handleCancelBooking(await this.createNextApiBookingRequest(req, oAuthClientId));
return {
status: SUCCESS_STATUS,
};
Expand All @@ -159,14 +182,14 @@ export class BookingsController {

@Post("/reccuring")
async createReccuringBooking(
@Req() req: Request & { userId?: number },
@Body() _: CreateReccuringBookingInput[]
@Req() req: BookingRequest,
@Body() _: CreateReccuringBookingInput[],
@Headers(X_CAL_CLIENT_ID) clientId?: string
): Promise<ApiResponse<BookingResponse[]>> {
req.userId = (await this.getOwnerId(req)) ?? -1;
req.body = { ...req.body, noEmail: true };
const oAuthClientId = clientId?.toString();
try {
const createdBookings: BookingResponse[] = await handleNewRecurringBooking(
req as unknown as NextApiRequest & { userId?: number }
await this.createNextApiBookingRequest(req, oAuthClientId)
);
return {
status: SUCCESS_STATUS,
Expand All @@ -180,14 +203,15 @@ export class BookingsController {

@Post("/instant")
async createInstantBooking(
@Req() req: Request & { userId?: number },
@Body() _: CreateBookingInput
@Req() req: BookingRequest,
@Body() _: CreateBookingInput,
@Headers(X_CAL_CLIENT_ID) clientId?: string
ThyMinimalDev marked this conversation as resolved.
Show resolved Hide resolved
): Promise<ApiResponse<Awaited<ReturnType<typeof handleInstantMeeting>>>> {
const oAuthClientId = clientId?.toString();
req.userId = (await this.getOwnerId(req)) ?? -1;
req.body = { ...req.body, noEmail: true };
try {
const instantMeeting = await handleInstantMeeting(
req as unknown as NextApiRequest & { userId?: number }
await this.createNextApiBookingRequest(req, oAuthClientId)
);
return {
status: SUCCESS_STATUS,
Expand All @@ -209,6 +233,41 @@ export class BookingsController {
this.logger.error(err);
}
}

async getOAuthClientsParams(
req: BookingRequest,
clientId: string
): Promise<OAuthRequestParams & { areEmailsEnabled: boolean }> {
const res = DEFAULT_PLATFORM_PARAMS;
try {
const client = await this.oAuthClientRepository.getOAuthClient(clientId);
// fetch oAuthClient from db and use data stored in db to set these values
if (client) {
res.platformClientId = clientId;
res.platformCancelUrl = client.bookingCancelRedirectUri ?? "";
res.platformRescheduleUrl = client.bookingRescheduleRedirectUri ?? "";
res.platformBookingUrl = client.bookingRedirectUri ?? "";
res.areEmailsEnabled = client.areEmailsEnabled;
}
return res;
} catch (err) {
this.logger.error(err);
return res;
}
}

async createNextApiBookingRequest(
req: BookingRequest,
oAuthClientId?: string
): Promise<NextApiRequest & { userId?: number } & OAuthRequestParams> {
const userId = (await this.getOwnerId(req)) ?? -1;
const oAuthParams = oAuthClientId
? await this.getOAuthClientsParams(req, oAuthClientId)
: DEFAULT_PLATFORM_PARAMS;
Object.assign(req, { userId, ...oAuthParams });
req.body = { ...req.body, areEmailsEnabled: oAuthParams.areEmailsEnabled };
return req as unknown as NextApiRequest & { userId?: number } & OAuthRequestParams;
}
}

function handleBookingErrors(err: Error | HttpError | unknown, type?: "recurring" | `instant`): void {
Expand Down
6 changes: 4 additions & 2 deletions apps/api/v2/src/ee/calendars/services/calendars.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
CredentialsWithUserEmail,
} from "@/modules/credentials/credentials.repository";
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
import { UsersRepository } from "@/modules/users/users.repository";
import {
Injectable,
Expand All @@ -23,7 +24,8 @@ export class CalendarsService {
constructor(
private readonly usersRepository: UsersRepository,
private readonly credentialsRepository: CredentialsRepository,
private readonly dbRead: PrismaReadService
private readonly dbRead: PrismaReadService,
private readonly dbWrite: PrismaWriteService
) {}

async getCalendars(userId: number) {
Expand All @@ -35,7 +37,7 @@ export class CalendarsService {
return getConnectedDestinationCalendars(
userWithCalendars,
false,
this.dbRead.prisma as unknown as PrismaClient
this.dbWrite.prisma as unknown as PrismaClient
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export class CredentialsRepository {
invalid: false,
},
where: {
id: credential?.id,
id: credential?.id ?? 0,
},
});
}
Expand Down
53 changes: 50 additions & 3 deletions apps/api/v2/swagger/documentation.json
Original file line number Diff line number Diff line change
Expand Up @@ -1235,7 +1235,16 @@
},
"post": {
"operationId": "BookingsController_createBooking",
"parameters": [],
"parameters": [
{
"name": "x-cal-client-id",
"required": true,
"in": "header",
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
Expand Down Expand Up @@ -1334,6 +1343,14 @@
"schema": {
"type": "string"
}
},
{
"name": "x-cal-client-id",
"required": true,
"in": "header",
"schema": {
"type": "string"
}
}
],
"requestBody": {
Expand Down Expand Up @@ -1366,7 +1383,16 @@
"/api/v2/ee/bookings/reccuring": {
"post": {
"operationId": "BookingsController_createReccuringBooking",
"parameters": [],
"parameters": [
{
"name": "x-cal-client-id",
"required": true,
"in": "header",
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
Expand Down Expand Up @@ -1400,7 +1426,16 @@
"/api/v2/ee/bookings/instant": {
"post": {
"operationId": "BookingsController_createInstantBooking",
"parameters": [],
"parameters": [
{
"name": "x-cal-client-id",
"required": true,
"in": "header",
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
Expand Down Expand Up @@ -1912,6 +1947,18 @@
"items": {
"type": "string"
}
},
"bookingRedirectUri": {
"type": "string"
},
"bookingCancelRedirectUri": {
"type": "string"
},
"bookingRescheduleRedirectUri": {
"type": "string"
},
"areEmailsEnabled": {
"type": "boolean"
}
}
},
Expand Down