Skip to content

Commit

Permalink
Merge branch 'main' into chore/separate-client-vs-server-code-from-in…
Browse files Browse the repository at this point in the history
…sights-layout
  • Loading branch information
anikdhabal authored Feb 14, 2025
2 parents 4074d1b + 1ff4f55 commit cb7a305
Show file tree
Hide file tree
Showing 85 changed files with 8,463 additions and 1,457 deletions.
3 changes: 3 additions & 0 deletions .yarn/versions/3c2036fd.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
undecided:
- calcom-monorepo
- "@calcom/prisma"
6 changes: 6 additions & 0 deletions .yarn/versions/97a32d13.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
undecided:
- "@calcom/app-store-cli"
- "@calcom/platform-constants"
- "@calcom/platform-enums"
- "@calcom/platform-types"
- "@calcom/platform-utils"
1 change: 1 addition & 0 deletions apps/api/v2/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
"helmet": "^7.1.0",
"ioredis": "^5.3.2",
"jsforce": "^1.11.0",
"lodash": "^4.17.21",
"luxon": "^3.4.4",
"nest-winston": "^1.9.4",
"next-auth": "^4.22.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,6 @@ describe("Bookings Endpoints 2024-08-13", () => {
// eslint-disable-next-line
// @ts-ignore
const data: GetSeatedBookingOutput_2024_08_13 = responseBody.data;
console.log("asap data", JSON.stringify(data, null, 2));
expect(data.attendees[0].name).toEqual(`${splitName.firstName} ${splitName.lastName}`);
expect(data.attendees[0].bookingFieldsResponses.name).toEqual(splitName);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
timeZone: "Europe/Rome",
},
},
rating: 10,
});

app = moduleRef.createNestApplication();
Expand Down Expand Up @@ -552,6 +553,30 @@ describe("Bookings Endpoints 2024-08-13", () => {
});
});

it("should should get a booking with rating", async () => {
return request(app.getHttpServer())
.get(`/v2/bookings/${bookingInThePast.uid}`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
.expect(200)
.then(async (response) => {
const responseBody: GetBookingOutput_2024_08_13 = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
expect(responseDataIsBooking(responseBody.data)).toBe(true);

if (responseDataIsBooking(responseBody.data)) {
const data: BookingOutput_2024_08_13 = responseBody.data;
expect(data.id).toEqual(bookingInThePast.id);
expect(data.uid).toEqual(bookingInThePast.uid);
expect(data.rating).toEqual(bookingInThePast.rating);
} else {
throw new Error(
"Invalid response data - expected booking but received array of possibily recurring bookings"
);
}
});
});

it("should should get 1 recurrence of a recurring booking", async () => {
const recurrenceUid = createdRecurringBooking[0].uid;
return request(app.getHttpServer())
Expand Down
16 changes: 12 additions & 4 deletions apps/api/v2/src/ee/bookings/2024-08-13/services/output.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ export class OutputBookingsService_2024_08_13 {
absentHost: !!databaseBooking.noShowHost,
createdAt: databaseBooking.createdAt,
updatedAt: databaseBooking.updatedAt,
rating: databaseBooking.rating,
};

const bookingTransformed = plainToClass(BookingOutput_2024_08_13, booking, { strategy: "excludeAll" });
Expand Down Expand Up @@ -227,6 +228,7 @@ export class OutputBookingsService_2024_08_13 {
bookingFieldsResponses: databaseBooking.responses,
createdAt: databaseBooking.createdAt,
updatedAt: databaseBooking.updatedAt,
rating: databaseBooking.rating,
};

const bookingTransformed = plainToClass(RecurringBookingOutput_2024_08_13, booking, {
Expand Down Expand Up @@ -274,6 +276,7 @@ export class OutputBookingsService_2024_08_13 {
absentHost: !!databaseBooking.noShowHost,
createdAt: databaseBooking.createdAt,
updatedAt: databaseBooking.updatedAt,
rating: databaseBooking.rating,
};

const parsed = plainToClass(GetSeatedBookingOutput_2024_08_13, booking, { strategy: "excludeAll" });
Expand All @@ -283,7 +286,8 @@ export class OutputBookingsService_2024_08_13 {
const { responses } = safeParse(
seatedBookingDataSchema,
attendee.bookingSeat?.data,
defaultSeatedBookingData
defaultSeatedBookingData,
false
);

const attendeeData = {
Expand All @@ -300,7 +304,8 @@ export class OutputBookingsService_2024_08_13 {
attendeeParsed.metadata = safeParse(
seatedBookingMetadataSchema,
attendee.bookingSeat?.metadata,
defaultSeatedBookingMetadata
defaultSeatedBookingMetadata,
false
);
// note(Lauris): as of now email is not returned for privacy
delete attendeeParsed.bookingFieldsResponses.email;
Expand Down Expand Up @@ -380,6 +385,7 @@ export class OutputBookingsService_2024_08_13 {
absentHost: !!databaseBooking.noShowHost,
createdAt: databaseBooking.createdAt,
updatedAt: databaseBooking.updatedAt,
rating: databaseBooking.rating,
};

const parsed = plainToClass(GetRecurringSeatedBookingOutput_2024_08_13, booking, {
Expand All @@ -391,7 +397,8 @@ export class OutputBookingsService_2024_08_13 {
const { responses } = safeParse(
seatedBookingDataSchema,
attendee.bookingSeat?.data,
defaultSeatedBookingData
defaultSeatedBookingData,
false
);

const attendeeData = {
Expand All @@ -408,7 +415,8 @@ export class OutputBookingsService_2024_08_13 {
attendeeParsed.metadata = safeParse(
seatedBookingMetadataSchema,
attendee.bookingSeat?.metadata,
defaultSeatedBookingMetadata
defaultSeatedBookingMetadata,
false
);
// note(Lauris): as of now email is not returned for privacy
delete attendeeParsed.bookingFieldsResponses.email;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,7 @@ import { InputEventTransformed_2024_06_14 } from "@calcom/platform-types";

@Injectable()
export class EventTypesRepository_2024_06_14 {
constructor(
private readonly dbRead: PrismaReadService,
private readonly dbWrite: PrismaWriteService,
private usersService: UsersService
) {}
constructor(private readonly dbRead: PrismaReadService, private readonly dbWrite: PrismaWriteService) {}

async createUserEventType(
userId: number,
Expand Down Expand Up @@ -47,6 +43,13 @@ export class EventTypesRepository_2024_06_14 {
});
}

async getEventTypeWithHosts(eventTypeId: number) {
return this.dbRead.prisma.eventType.findUnique({
where: { id: eventTypeId },
include: { hosts: true },
});
}

async getUserEventType(userId: number, eventTypeId: number) {
return this.dbRead.prisma.eventType.findFirst({
where: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ApiProperty } from "@nestjs/swagger";
import { Type } from "class-transformer";
import { IsEnum, IsString, ValidateNested } from "class-validator";
import { IsEnum, IsString, ValidateNested, IsOptional } from "class-validator";

import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";

Expand All @@ -10,8 +10,9 @@ class Data {
callId!: string;

@IsString()
@IsOptional()
@ApiProperty()
agentId!: string;
agentId?: string;
}

export class CreatePhoneCallOutput {
Expand Down
6 changes: 4 additions & 2 deletions apps/api/v2/src/ee/platform-endpoints-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import { MeModule } from "@/ee/me/me.module";
import { ProviderModule } from "@/ee/provider/provider.module";
import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module";
import { SchedulesModule_2024_06_11 } from "@/ee/schedules/schedules_2024_06_11/schedules.module";
import { SlotsModule } from "@/modules/slots/slots.module";
import { SlotsModule_2024_04_15 } from "@/modules/slots/slots-2024-04-15/slots.module";
import { SlotsModule_2024_09_04 } from "@/modules/slots/slots-2024-09-04/slots.module";
import { TeamsEventTypesModule } from "@/modules/teams/event-types/teams-event-types.module";
import { TeamsMembershipsModule } from "@/modules/teams/memberships/teams-memberships.module";
import { TeamsModule } from "@/modules/teams/teams/teams.module";
Expand All @@ -29,7 +30,8 @@ import { Module } from "@nestjs/common";
BookingsModule_2024_04_15,
BookingsModule_2024_08_13,
TeamsMembershipsModule,
SlotsModule,
SlotsModule_2024_04_15,
SlotsModule_2024_09_04,
TeamsModule,
],
})
Expand Down
3 changes: 3 additions & 0 deletions apps/api/v2/src/lib/api-versions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,18 @@ import {
VERSION_2024_06_11,
VERSION_2024_06_14,
VERSION_2024_08_13,
VERSION_2024_09_04,
} from "@calcom/platform-constants";

export const API_VERSIONS_VALUES: VersionValue = API_VERSIONS as unknown as VersionValue;
export const VERSION_2024_06_14_VALUE: VersionValue = VERSION_2024_06_14 as unknown as VersionValue;
export const VERSION_2024_06_11_VALUE: VersionValue = VERSION_2024_06_11 as unknown as VersionValue;
export const VERSION_2024_04_15_VALUE: VersionValue = VERSION_2024_04_15 as unknown as VersionValue;
export const VERSION_2024_08_13_VALUE: VersionValue = VERSION_2024_08_13 as unknown as VersionValue;
export const VERSION_2024_09_04_VALUE: VersionValue = VERSION_2024_09_04 as unknown as VersionValue;

export { VERSION_2024_04_15 };
export { VERSION_2024_06_11 };
export { VERSION_2024_06_14 };
export { VERSION_2024_08_13 };
export { VERSION_2024_09_04 };
23 changes: 15 additions & 8 deletions apps/api/v2/src/lib/safe-parse/safe-parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,27 @@ import { ZodSchema } from "zod";

const logger = new Logger("safeParse");

export function safeParse<T>(schema: ZodSchema<T>, value: unknown, defaultValue: T): T {
export function safeParse<T>(
schema: ZodSchema<T>,
value: unknown,
defaultValue: T,
logError = true
): T {
const result = schema.safeParse(value);
if (result.success) {
return result.data;
} else {
const errorStack = new Error().stack;

logger.error(
`Zod parsing failed.\n` +
`1. Schema: ${schema.description || "UnnamedSchema"}\n` +
`2. Input: ${JSON.stringify(value, null, 2)}\n` +
`3. Zod Error: ${result.error}\n` +
`4. Call Stack: ${errorStack}`
);
if (logError) {
logger.error(
`Zod parsing failed.\n` +
`1. Schema: ${schema.description || "UnnamedSchema"}\n` +
`2. Input: ${JSON.stringify(value, null, 2)}\n` +
`3. Zod Error: ${result.error}\n` +
`4. Call Stack: ${errorStack}`
);
}

return defaultValue;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { ApiAuthGuardUser } from "@/modules/auth/strategies/api-auth/api-auth.strategy";
import { ExecutionContext } from "@nestjs/common";
import { createParamDecorator } from "@nestjs/common";

export const GetOptionalUser = createParamDecorator<
keyof ApiAuthGuardUser | (keyof ApiAuthGuardUser)[],
ExecutionContext
>((data, ctx) => {
const request = ctx.switchToHttp().getRequest();
const user = request.user as ApiAuthGuardUser;

if (!user) {
return null;
}

if (Array.isArray(data)) {
return data.reduce((prev, curr) => {
return {
...prev,
[curr]: user[curr],
};
}, {});
}

if (data) {
return user[data];
}

return user;
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
import { NO_AUTH_PROVIDED_MESSAGE } from "@/modules/auth/strategies/api-auth/api-auth.strategy";

export class OptionalApiAuthGuard extends ApiAuthGuard {
handleRequest(error: Error, user: any) {
// note(Lauris): optional means that auth is not required but if it is invalid then still throw error.
const noAuthProvided = error && error.message === NO_AUTH_PROVIDED_MESSAGE;
if (user || noAuthProvided || !error) {
return user || null;
} else {
throw error;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import { INVALID_ACCESS_TOKEN, X_CAL_CLIENT_ID, X_CAL_SECRET_KEY } from "@calcom

export type ApiAuthGuardUser = UserWithProfile & { isSystemAdmin: boolean };

export const NO_AUTH_PROVIDED_MESSAGE =
"No authentication method provided. Either pass an API key as 'Bearer' header or OAuth client credentials as 'x-cal-secret-key' and 'x-cal-client-id' headers";
@Injectable()
export class ApiAuthStrategy extends PassportStrategy(BaseStrategy, "api-auth") {
constructor(
Expand Down Expand Up @@ -62,9 +64,7 @@ export class ApiAuthStrategy extends PassportStrategy(BaseStrategy, "api-auth")
return await this.authenticateNextAuth(nextAuthToken);
}

throw new UnauthorizedException(
"No authentication method provided. Either pass an API key as 'Bearer' header or OAuth client credentials as 'x-cal-secret-key' and 'x-cal-client-id' headers"
);
throw new UnauthorizedException(NO_AUTH_PROVIDED_MESSAGE);
} catch (err) {
if (err instanceof Error) {
return this.error(err);
Expand Down
5 changes: 3 additions & 2 deletions apps/api/v2/src/modules/memberships/memberships.module.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { MembershipsRepository } from "@/modules/memberships/memberships.repository";
import { MembershipsService } from "@/modules/memberships/services/memberships.service";
import { PrismaModule } from "@/modules/prisma/prisma.module";
import { Module } from "@nestjs/common";

@Module({
imports: [PrismaModule],
providers: [MembershipsRepository],
exports: [MembershipsRepository],
providers: [MembershipsRepository, MembershipsService],
exports: [MembershipsRepository, MembershipsService],
})
export class MembershipsModule {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { MembershipsRepository } from "@/modules/memberships/memberships.repository";
import { Injectable } from "@nestjs/common";
import { intersectionBy } from "lodash";

@Injectable()
export class MembershipsService {
constructor(private readonly membershipsRepository: MembershipsRepository) {}

async haveMembershipsInCommon(firstUserId: number, secondUserId: number) {
const memberships = await this.membershipsInCommon(firstUserId, secondUserId);
return memberships.length > 0;
}

async membershipsInCommon(firstUserId: number, secondUserId: number) {
const firstUserMemberships = await this.membershipsRepository.findUserMemberships(firstUserId);
const secondUserMemberships = await this.membershipsRepository.findUserMemberships(secondUserId);

return intersectionBy(
firstUserMemberships.filter((m) => m.accepted),
secondUserMemberships.filter((m) => m.accepted),
"teamId"
);
}
}
10 changes: 10 additions & 0 deletions apps/api/v2/src/modules/organizations/organizations.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,4 +131,14 @@ export class OrganizationsRepository {
},
});
}

async findOrgBySlug(slug: string) {
return this.dbRead.prisma.team.findFirst({
where: {
slug,
parentId: null,
isOrganization: true,
},
});
}
}
Loading

0 comments on commit cb7a305

Please sign in to comment.