Skip to content

Commit 2b78200

Browse files
authored
Merge branch 'main' into dub-folders
2 parents dc636b1 + 7969789 commit 2b78200

File tree

12 files changed

+308
-229
lines changed

12 files changed

+308
-229
lines changed

apps/api/v2/src/modules/slots/controllers/slots.controller.e2e-spec.ts

+199-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import { NestExpressApplication } from "@nestjs/platform-express";
1212
import { Test } from "@nestjs/testing";
1313
import { User } from "@prisma/client";
1414
import * as request from "supertest";
15+
import { AttendeeRepositoryFixture } from "test/fixtures/repository/attendee.repository.fixture";
16+
import { BookingSeatRepositoryFixture } from "test/fixtures/repository/booking-seat.repository.fixture";
1517
import { BookingsRepositoryFixture } from "test/fixtures/repository/bookings.repository.fixture";
1618
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
1719
import { SelectedSlotsRepositoryFixture } from "test/fixtures/repository/selected-slots.repository.fixture";
@@ -250,6 +252,8 @@ describe("Slots Endpoints", () => {
250252
let eventTypesRepositoryFixture: EventTypesRepositoryFixture;
251253
let selectedSlotsRepositoryFixture: SelectedSlotsRepositoryFixture;
252254
let bookingsRepositoryFixture: BookingsRepositoryFixture;
255+
let bookingSeatsRepositoryFixture: BookingSeatRepositoryFixture;
256+
let attendeesRepositoryFixture: AttendeeRepositoryFixture;
253257

254258
const userEmail = `slots-${randomString()}-controller-e2e@api.com`;
255259
const userName = "bob";
@@ -258,6 +262,9 @@ describe("Slots Endpoints", () => {
258262
let eventTypeSlug: string;
259263
let reservedSlotUid: string;
260264

265+
const seatedEventTypeSlug = "peer-coding-seated";
266+
let seatedEventTypeId: number;
267+
261268
beforeAll(async () => {
262269
const moduleRef = await withApiAuth(
263270
userEmail,
@@ -283,6 +290,8 @@ describe("Slots Endpoints", () => {
283290
eventTypesRepositoryFixture = new EventTypesRepositoryFixture(moduleRef);
284291
selectedSlotsRepositoryFixture = new SelectedSlotsRepositoryFixture(moduleRef);
285292
bookingsRepositoryFixture = new BookingsRepositoryFixture(moduleRef);
293+
bookingSeatsRepositoryFixture = new BookingSeatRepositoryFixture(moduleRef);
294+
attendeesRepositoryFixture = new AttendeeRepositoryFixture(moduleRef);
286295

287296
user = await userRepositoryFixture.create({
288297
email: userEmail,
@@ -291,7 +300,7 @@ describe("Slots Endpoints", () => {
291300
});
292301

293302
// nxte(Lauris): this creates default schedule monday to friday from 9AM to 5PM in Europe/Rome timezone
294-
const userSchedule = await schedulesService.createUserSchedule(user.id, {
303+
await schedulesService.createUserSchedule(user.id, {
295304
name: `slots-schedule-${randomString()}-slots.controller.e2e-spec`,
296305
timeZone: "Europe/Rome",
297306
isDefault: true,
@@ -306,9 +315,24 @@ describe("Slots Endpoints", () => {
306315
},
307316
user.id
308317
);
318+
309319
eventTypeId = eventType.id;
310320
eventTypeSlug = eventType.slug;
311321

322+
const seatedEvent = await eventTypesRepositoryFixture.create(
323+
{
324+
title: `slots-event-type-seated-${randomString()}-slots.controller.e2e-spec`,
325+
slug: `slots-event-type-seated-${randomString()}-slots.controller.e2e-spec`,
326+
length: 60,
327+
seatsPerTimeSlot: 5,
328+
seatsShowAttendees: true,
329+
seatsShowAvailabilityCount: true,
330+
locations: [{ type: "inPerson", address: "via 10, rome, italy" }],
331+
},
332+
user.id
333+
);
334+
seatedEventTypeId = seatedEvent.id;
335+
312336
app = moduleRef.createNestApplication();
313337
bootstrap(app as NestExpressApplication);
314338

@@ -445,7 +469,7 @@ describe("Slots Endpoints", () => {
445469

446470
it("should do a booking and slot should not be available at that time", async () => {
447471
const startTime = "2050-09-05T11:00:00.000Z";
448-
await bookingsRepositoryFixture.create({
472+
const booking = await bookingsRepositoryFixture.create({
449473
uid: `booking-uid-${eventTypeId}`,
450474
title: "booking title",
451475
startTime,
@@ -486,6 +510,179 @@ describe("Slots Endpoints", () => {
486510
expect(slots).toEqual({
487511
slots: { ...expectedSlotsUTC.slots, "2050-09-05": expectedSlotsUTC2050_09_05 },
488512
});
513+
514+
await bookingsRepositoryFixture.deleteById(booking.id);
515+
});
516+
517+
it("should do a booking for seated event and slot should show attendees count and bookingUid", async () => {
518+
const startTime = "2050-09-05T11:00:00.000Z";
519+
const booking = await bookingsRepositoryFixture.create({
520+
uid: `booking-uid-${seatedEventTypeId}`,
521+
title: "booking title",
522+
startTime,
523+
endTime: "2050-09-05T12:00:00.000Z",
524+
eventType: {
525+
connect: {
526+
id: seatedEventTypeId,
527+
},
528+
},
529+
metadata: {},
530+
responses: {
531+
name: "tester",
532+
email: "tester@example.com",
533+
guests: [],
534+
},
535+
user: {
536+
connect: {
537+
id: user.id,
538+
},
539+
},
540+
});
541+
542+
const attendee = await attendeesRepositoryFixture.create({
543+
name: "tester",
544+
email: "tester@example.com",
545+
timeZone: "Europe/London",
546+
booking: {
547+
connect: {
548+
id: booking.id,
549+
},
550+
},
551+
});
552+
553+
bookingSeatsRepositoryFixture.create({
554+
referenceUid: "100",
555+
data: {},
556+
booking: {
557+
connect: {
558+
id: booking.id,
559+
},
560+
},
561+
attendee: {
562+
connect: {
563+
id: attendee.id,
564+
},
565+
},
566+
});
567+
568+
const response = await request(app.getHttpServer())
569+
.get(
570+
`/api/v2/slots/available?eventTypeId=${seatedEventTypeId}&startTime=2050-09-05&endTime=2050-09-10`
571+
)
572+
.expect(200);
573+
574+
const responseBody = response.body;
575+
expect(responseBody.status).toEqual(SUCCESS_STATUS);
576+
const slots = responseBody.data;
577+
578+
expect(slots).toBeDefined();
579+
const days = Object.keys(slots.slots);
580+
expect(days.length).toEqual(5);
581+
582+
const expectedSlotsUTC2050_09_05 = [
583+
{ time: "2050-09-05T07:00:00.000Z" },
584+
{ time: "2050-09-05T08:00:00.000Z" },
585+
{ time: "2050-09-05T09:00:00.000Z" },
586+
{ time: "2050-09-05T10:00:00.000Z" },
587+
{ time: "2050-09-05T11:00:00.000Z", attendees: 1, bookingUid: booking.uid },
588+
{ time: "2050-09-05T12:00:00.000Z" },
589+
{ time: "2050-09-05T13:00:00.000Z" },
590+
{ time: "2050-09-05T14:00:00.000Z" },
591+
];
592+
593+
expect(slots).toEqual({
594+
slots: { ...expectedSlotsUTC.slots, "2050-09-05": expectedSlotsUTC2050_09_05 },
595+
});
596+
597+
await bookingsRepositoryFixture.deleteById(booking.id);
598+
});
599+
600+
it("should do a booking for seated event and slot should show attendees count and bookingUid in range format", async () => {
601+
const startTime = "2050-09-05T11:00:00.000Z";
602+
const booking = await bookingsRepositoryFixture.create({
603+
uid: `booking-uid-${seatedEventTypeId}`,
604+
title: "booking title",
605+
startTime,
606+
endTime: "2050-09-05T12:00:00.000Z",
607+
eventType: {
608+
connect: {
609+
id: seatedEventTypeId,
610+
},
611+
},
612+
metadata: {},
613+
responses: {
614+
name: "tester",
615+
email: "tester@example.com",
616+
guests: [],
617+
},
618+
user: {
619+
connect: {
620+
id: user.id,
621+
},
622+
},
623+
});
624+
625+
const attendee = await attendeesRepositoryFixture.create({
626+
name: "tester",
627+
email: "tester@example.com",
628+
timeZone: "Europe/London",
629+
booking: {
630+
connect: {
631+
id: booking.id,
632+
},
633+
},
634+
});
635+
636+
bookingSeatsRepositoryFixture.create({
637+
referenceUid: "100",
638+
data: {},
639+
booking: {
640+
connect: {
641+
id: booking.id,
642+
},
643+
},
644+
attendee: {
645+
connect: {
646+
id: attendee.id,
647+
},
648+
},
649+
});
650+
651+
const response = await request(app.getHttpServer())
652+
.get(
653+
`/api/v2/slots/available?eventTypeId=${seatedEventTypeId}&startTime=2050-09-05&endTime=2050-09-10&slotFormat=range`
654+
)
655+
.expect(200);
656+
657+
const responseBody = response.body;
658+
expect(responseBody.status).toEqual(SUCCESS_STATUS);
659+
const slots = responseBody.data;
660+
661+
expect(slots).toBeDefined();
662+
const days = Object.keys(slots.slots);
663+
expect(days.length).toEqual(5);
664+
665+
const expectedSlotsUTC2050_09_05 = [
666+
{ startTime: "2050-09-05T07:00:00.000Z", endTime: "2050-09-05T08:00:00.000Z" },
667+
{ startTime: "2050-09-05T08:00:00.000Z", endTime: "2050-09-05T09:00:00.000Z" },
668+
{ startTime: "2050-09-05T09:00:00.000Z", endTime: "2050-09-05T10:00:00.000Z" },
669+
{ startTime: "2050-09-05T10:00:00.000Z", endTime: "2050-09-05T11:00:00.000Z" },
670+
{
671+
startTime: "2050-09-05T11:00:00.000Z",
672+
endTime: "2050-09-05T12:00:00.000Z",
673+
attendees: 1,
674+
bookingUid: booking.uid,
675+
},
676+
{ startTime: "2050-09-05T12:00:00.000Z", endTime: "2050-09-05T13:00:00.000Z" },
677+
{ startTime: "2050-09-05T13:00:00.000Z", endTime: "2050-09-05T14:00:00.000Z" },
678+
{ startTime: "2050-09-05T14:00:00.000Z", endTime: "2050-09-05T15:00:00.000Z" },
679+
];
680+
681+
expect(slots).toEqual({
682+
slots: { ...expectedSlotsUTCRange.slots, "2050-09-05": expectedSlotsUTC2050_09_05 },
683+
});
684+
685+
await bookingsRepositoryFixture.deleteById(booking.id);
489686
});
490687

491688
afterAll(async () => {

apps/api/v2/src/modules/slots/services/slots-output.service.ts

+12-4
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ import { DateTime } from "luxon";
44

55
import { SlotFormat } from "@calcom/platform-enums";
66

7-
type TimeSlots = { slots: Record<string, { time: string }[]> };
8-
type RangeSlots = { slots: Record<string, { startTime: string; endTime: string }[]> };
7+
type TimeSlots = { slots: Record<string, { time: string; attendees?: number; bookingUid?: string }[]> };
8+
type RangeSlots = {
9+
slots: Record<string, { startTime: string; endTime: string; attendees?: number; bookingUid?: string }[]>;
10+
};
911

1012
@Injectable()
1113
export class SlotsOutputService {
@@ -30,6 +32,8 @@ export class SlotsOutputService {
3032
const formattedSlots = Object.entries(slots.slots).reduce((acc, [date, daySlots]) => {
3133
acc[date] = daySlots.map((slot) => ({
3234
time: DateTime.fromISO(slot.time).setZone(timeZone).toISO() || "unknown-time",
35+
...(slot.attendees ? { attendees: slot.attendees } : {}),
36+
...(slot.bookingUid ? { bookingUid: slot.bookingUid } : {}),
3337
}));
3438
return acc;
3539
}, {} as Record<string, { time: string }[]>);
@@ -42,6 +46,8 @@ export class SlotsOutputService {
4246
acc[date] = daySlots.map((slot) => ({
4347
startTime: DateTime.fromISO(slot.startTime).setZone(timeZone).toISO() || "unknown-start-time",
4448
endTime: DateTime.fromISO(slot.endTime).setZone(timeZone).toISO() || "unknown-end-time",
49+
...(slot.attendees ? { attendees: slot.attendees } : {}),
50+
...(slot.bookingUid ? { bookingUid: slot.bookingUid } : {}),
4551
}));
4652
return acc;
4753
}, {} as Record<string, { startTime: string; endTime: string }[]>);
@@ -62,14 +68,16 @@ export class SlotsOutputService {
6268
const slotDuration = await this.getDuration(duration, eventTypeId);
6369

6470
const slots = Object.entries(availableSlots.slots).reduce<
65-
Record<string, { startTime: string; endTime: string }[]>
71+
Record<string, { startTime: string; endTime: string; attendees?: number; bookingUid?: string }[]>
6672
>((acc, [date, slots]) => {
67-
acc[date] = (slots as { time: string }[]).map((slot) => {
73+
acc[date] = (slots as { time: string; attendees?: number; bookingUid?: string }[]).map((slot) => {
6874
const startTime = new Date(slot.time);
6975
const endTime = new Date(startTime.getTime() + slotDuration * 60000);
7076
return {
7177
startTime: startTime.toISOString(),
7278
endTime: endTime.toISOString(),
79+
...(slot.attendees ? { attendees: slot.attendees } : {}),
80+
...(slot.bookingUid ? { bookingUid: slot.bookingUid } : {}),
7381
};
7482
});
7583
return acc;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
2+
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
3+
import { TestingModule } from "@nestjs/testing";
4+
5+
import { Prisma } from "@calcom/prisma/client";
6+
7+
export class AttendeeRepositoryFixture {
8+
private prismaReadClient: PrismaReadService["prisma"];
9+
private prismaWriteClient: PrismaWriteService["prisma"];
10+
11+
constructor(private readonly module: TestingModule) {
12+
this.prismaReadClient = module.get(PrismaReadService).prisma;
13+
this.prismaWriteClient = module.get(PrismaWriteService).prisma;
14+
}
15+
16+
async create(attendee: Prisma.AttendeeCreateInput) {
17+
return this.prismaWriteClient.attendee.create({ data: attendee });
18+
}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
2+
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
3+
import { TestingModule } from "@nestjs/testing";
4+
5+
import { Prisma } from "@calcom/prisma/client";
6+
7+
export class BookingSeatRepositoryFixture {
8+
private prismaReadClient: PrismaReadService["prisma"];
9+
private prismaWriteClient: PrismaWriteService["prisma"];
10+
11+
constructor(private readonly module: TestingModule) {
12+
this.prismaReadClient = module.get(PrismaReadService).prisma;
13+
this.prismaWriteClient = module.get(PrismaWriteService).prisma;
14+
}
15+
16+
async create(bookingSeat: Prisma.BookingSeatCreateInput) {
17+
return this.prismaWriteClient.bookingSeat.create({ data: bookingSeat });
18+
}
19+
}

apps/web/app/not-found.tsx

+2-3
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,7 @@ async function NotFound() {
122122
<span className="mt-2 inline-block text-lg">{t("check_spelling_mistakes_or_go_back")}</span>
123123
) : IS_CALCOM ? (
124124
<a target="_blank" href={url} className="mt-2 inline-block text-lg" rel="noreferrer">
125-
{t(`404_the_${pageType.toLowerCase()}`)}
126-
125+
{t(`404_the_${pageType.toLowerCase()}`)}{" "}
127126
{username ? (
128127
<>
129128
<strong className="text-blue-500">{username}</strong>
@@ -137,7 +136,7 @@ async function NotFound() {
137136
{t(`404_the_${pageType.toLowerCase()}`)}{" "}
138137
{username ? (
139138
<>
140-
<strong className="text-lgtext-green-500 mt-2 inline-block">{username}</strong>{" "}
139+
<strong className="mt-2 inline-block text-lg text-green-500">{username}</strong>{" "}
141140
{t("is_still_available")}
142141
</>
143142
) : null}

apps/web/lib/buildLegacyCtx.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export const buildLegacyCtx = (
5656
searchParams: SearchParams
5757
) => {
5858
return {
59-
query: { ...searchParams, ...params },
59+
query: { ...searchParams, ...decodeParams(params) },
6060
// decoding is required to be backward compatible with Pages Router
6161
// because Next.js App Router does not auto-decode query params while Pages Router does
6262
// e.g., params: { name: "John%20Doe" } => params: { name: "John Doe" }

apps/web/pages/404.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ export default function Custom404() {
155155
) : (
156156
<span className="mt-2 inline-block text-lg">
157157
{t(`404_the_${currentPageType.toLowerCase()}`)}{" "}
158-
<strong className="text-lgtext-green-500 mt-2 inline-block">{username}</strong>{" "}
158+
<strong className="mt-2 inline-block text-lg text-green-500">{username}</strong>{" "}
159159
{t("is_still_available")}
160160
</span>
161161
)}

0 commit comments

Comments
 (0)