Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into 04-13-fix_support_add…
Browse files Browse the repository at this point in the history
…ing_an_non-existent_email_as_the_owner_of_the_organization
  • Loading branch information
hariombalhara committed Apr 13, 2024
2 parents aebad2e + 8ad80f7 commit 1eff72c
Show file tree
Hide file tree
Showing 54 changed files with 1,843 additions and 980 deletions.
24 changes: 24 additions & 0 deletions .github/workflows/cron-changeTimeZone.yml
@@ -0,0 +1,24 @@
name: Cron - changeTimeZone

on:
# "Scheduled workflows run on the latest commit on the default or base branch."
# — https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows#schedule
schedule:
# Runs "At every full hour." (see https://crontab.guru)
- cron: "0 * * * *"

jobs:
cron-scheduleEmailReminders:
env:
APP_URL: ${{ secrets.APP_URL }}
CRON_API_KEY: ${{ secrets.CRON_API_KEY }}
runs-on: ubuntu-latest
steps:
- name: cURL request
if: ${{ env.APP_URL && env.CRON_API_KEY }}
run: |
curl ${{ secrets.APP_URL }}/api/cron/changeTimeZone \
-X POST \
-H 'content-type: application/json' \
-H 'authorization: ${{ secrets.CRON_API_KEY }}' \
-sSf
22 changes: 22 additions & 0 deletions apps/api/v1/middleware.ts
@@ -0,0 +1,22 @@
import { get } from "@vercel/edge-config";
import { NextResponse } from "next/server";

const safeGet = async <T = unknown>(key: string): Promise<T | undefined> => {
try {
return get<T>(key);
} catch (error) {
// Don't crash if EDGE_CONFIG env var is missing
}
};

export const config = { matcher: "/:path*" };

export async function middleware() {
const isInMaintenanceMode = await safeGet<boolean>("isInMaintenanceMode");
if (!isInMaintenanceMode) return NextResponse.next();
// If is in maintenance mode, return a 503 status code
return NextResponse.json(
{ message: "API is currently under maintenance. Please try again at a later time." },
{ status: 503 }
);
}
5 changes: 5 additions & 0 deletions apps/api/v2/src/app.module.ts
@@ -1,11 +1,13 @@
import { AppLoggerMiddleware } from "@/app.logger.middleware";
import { RewriterMiddleware } from "@/app.rewrites.middleware";
import appConfig from "@/config/app";
import { AuthModule } from "@/modules/auth/auth.module";
import { EndpointsModule } from "@/modules/endpoints.module";
import { JwtModule } from "@/modules/jwt/jwt.module";
import { PrismaModule } from "@/modules/prisma/prisma.module";
import { MiddlewareConsumer, Module, NestModule } from "@nestjs/common";
import { ConfigModule } from "@nestjs/config";
import { RouterModule } from "@nestjs/core";

import { AppController } from "./app.controller";

Expand Down Expand Up @@ -34,11 +36,14 @@ import { AppController } from "./app.controller";
EndpointsModule,
AuthModule,
JwtModule,
//register prefix for all routes in EndpointsModule
RouterModule.register([{ path: "/v2", module: EndpointsModule }]),
],
controllers: [AppController],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer): void {
consumer.apply(AppLoggerMiddleware).forRoutes("*");
consumer.apply(RewriterMiddleware).forRoutes("/");
}
}
12 changes: 12 additions & 0 deletions apps/api/v2/src/app.rewrites.middleware.ts
@@ -0,0 +1,12 @@
import { Injectable, NestMiddleware } from "@nestjs/common";
import { Request, Response } from "express";

@Injectable()
export class RewriterMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: () => void) {
if (req.url.startsWith("/api/v2")) {
req.url = req.url.replace("/api/v2", "/v2");
}
next();
}
}
8 changes: 3 additions & 5 deletions apps/api/v2/src/app.ts
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 Expand Up @@ -58,10 +60,6 @@ export const bootstrap = (app: NestExpressApplication): NestExpressApplication =
app.useGlobalFilters(new HttpExceptionFilter());
app.useGlobalFilters(new TRPCExceptionFilter());

app.setGlobalPrefix("api", {
exclude: [{ path: "health", method: RequestMethod.GET }],
});

app.use(cookieParser());

return app;
Expand Down
2 changes: 1 addition & 1 deletion apps/api/v2/src/config/app.ts
Expand Up @@ -14,7 +14,7 @@ const loadConfig = (): AppConfig => {
process.env.API_PORT && getEnv("NODE_ENV", "development") === "development"
? `:${Number(getEnv("API_PORT", "5555"))}`
: ""
}/api/v2`,
}/v2`,
},
db: {
readUrl: getEnv("DATABASE_READ_URL"),
Expand Down
3 changes: 2 additions & 1 deletion apps/api/v2/src/ee/bookings/bookings.module.ts
@@ -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
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
): 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
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

0 comments on commit 1eff72c

Please sign in to comment.