Skip to content

Commit

Permalink
fix: v2 slots not returning attendees and bookingUid info (#18858)
Browse files Browse the repository at this point in the history
* fix: v2 slots not returning attendees and bookingUid info

* rename file

* finish merge main
  • Loading branch information
supalarry authored Feb 12, 2025
1 parent 7f390b1 commit 67fb0e6
Show file tree
Hide file tree
Showing 4 changed files with 249 additions and 6 deletions.
201 changes: 199 additions & 2 deletions apps/api/v2/src/modules/slots/controllers/slots.controller.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { NestExpressApplication } from "@nestjs/platform-express";
import { Test } from "@nestjs/testing";
import { User } from "@prisma/client";
import * as request from "supertest";
import { AttendeeRepositoryFixture } from "test/fixtures/repository/attendee.repository.fixture";
import { BookingSeatRepositoryFixture } from "test/fixtures/repository/booking-seat.repository.fixture";
import { BookingsRepositoryFixture } from "test/fixtures/repository/bookings.repository.fixture";
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
import { SelectedSlotsRepositoryFixture } from "test/fixtures/repository/selected-slots.repository.fixture";
Expand Down Expand Up @@ -250,6 +252,8 @@ describe("Slots Endpoints", () => {
let eventTypesRepositoryFixture: EventTypesRepositoryFixture;
let selectedSlotsRepositoryFixture: SelectedSlotsRepositoryFixture;
let bookingsRepositoryFixture: BookingsRepositoryFixture;
let bookingSeatsRepositoryFixture: BookingSeatRepositoryFixture;
let attendeesRepositoryFixture: AttendeeRepositoryFixture;

const userEmail = `slots-${randomString()}[email protected]`;
const userName = "bob";
Expand All @@ -258,6 +262,9 @@ describe("Slots Endpoints", () => {
let eventTypeSlug: string;
let reservedSlotUid: string;

const seatedEventTypeSlug = "peer-coding-seated";
let seatedEventTypeId: number;

beforeAll(async () => {
const moduleRef = await withApiAuth(
userEmail,
Expand All @@ -283,6 +290,8 @@ describe("Slots Endpoints", () => {
eventTypesRepositoryFixture = new EventTypesRepositoryFixture(moduleRef);
selectedSlotsRepositoryFixture = new SelectedSlotsRepositoryFixture(moduleRef);
bookingsRepositoryFixture = new BookingsRepositoryFixture(moduleRef);
bookingSeatsRepositoryFixture = new BookingSeatRepositoryFixture(moduleRef);
attendeesRepositoryFixture = new AttendeeRepositoryFixture(moduleRef);

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

// nxte(Lauris): this creates default schedule monday to friday from 9AM to 5PM in Europe/Rome timezone
const userSchedule = await schedulesService.createUserSchedule(user.id, {
await schedulesService.createUserSchedule(user.id, {
name: `slots-schedule-${randomString()}-slots.controller.e2e-spec`,
timeZone: "Europe/Rome",
isDefault: true,
Expand All @@ -306,9 +315,24 @@ describe("Slots Endpoints", () => {
},
user.id
);

eventTypeId = eventType.id;
eventTypeSlug = eventType.slug;

const seatedEvent = await eventTypesRepositoryFixture.create(
{
title: `slots-event-type-seated-${randomString()}-slots.controller.e2e-spec`,
slug: `slots-event-type-seated-${randomString()}-slots.controller.e2e-spec`,
length: 60,
seatsPerTimeSlot: 5,
seatsShowAttendees: true,
seatsShowAvailabilityCount: true,
locations: [{ type: "inPerson", address: "via 10, rome, italy" }],
},
user.id
);
seatedEventTypeId = seatedEvent.id;

app = moduleRef.createNestApplication();
bootstrap(app as NestExpressApplication);

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

it("should do a booking and slot should not be available at that time", async () => {
const startTime = "2050-09-05T11:00:00.000Z";
await bookingsRepositoryFixture.create({
const booking = await bookingsRepositoryFixture.create({
uid: `booking-uid-${eventTypeId}`,
title: "booking title",
startTime,
Expand Down Expand Up @@ -486,6 +510,179 @@ describe("Slots Endpoints", () => {
expect(slots).toEqual({
slots: { ...expectedSlotsUTC.slots, "2050-09-05": expectedSlotsUTC2050_09_05 },
});

await bookingsRepositoryFixture.deleteById(booking.id);
});

it("should do a booking for seated event and slot should show attendees count and bookingUid", async () => {
const startTime = "2050-09-05T11:00:00.000Z";
const booking = await bookingsRepositoryFixture.create({
uid: `booking-uid-${seatedEventTypeId}`,
title: "booking title",
startTime,
endTime: "2050-09-05T12:00:00.000Z",
eventType: {
connect: {
id: seatedEventTypeId,
},
},
metadata: {},
responses: {
name: "tester",
email: "[email protected]",
guests: [],
},
user: {
connect: {
id: user.id,
},
},
});

const attendee = await attendeesRepositoryFixture.create({
name: "tester",
email: "[email protected]",
timeZone: "Europe/London",
booking: {
connect: {
id: booking.id,
},
},
});

bookingSeatsRepositoryFixture.create({
referenceUid: "100",
data: {},
booking: {
connect: {
id: booking.id,
},
},
attendee: {
connect: {
id: attendee.id,
},
},
});

const response = await request(app.getHttpServer())
.get(
`/api/v2/slots/available?eventTypeId=${seatedEventTypeId}&startTime=2050-09-05&endTime=2050-09-10`
)
.expect(200);

const responseBody = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
const slots = responseBody.data;

expect(slots).toBeDefined();
const days = Object.keys(slots.slots);
expect(days.length).toEqual(5);

const expectedSlotsUTC2050_09_05 = [
{ time: "2050-09-05T07:00:00.000Z" },
{ time: "2050-09-05T08:00:00.000Z" },
{ time: "2050-09-05T09:00:00.000Z" },
{ time: "2050-09-05T10:00:00.000Z" },
{ time: "2050-09-05T11:00:00.000Z", attendees: 1, bookingUid: booking.uid },
{ time: "2050-09-05T12:00:00.000Z" },
{ time: "2050-09-05T13:00:00.000Z" },
{ time: "2050-09-05T14:00:00.000Z" },
];

expect(slots).toEqual({
slots: { ...expectedSlotsUTC.slots, "2050-09-05": expectedSlotsUTC2050_09_05 },
});

await bookingsRepositoryFixture.deleteById(booking.id);
});

it("should do a booking for seated event and slot should show attendees count and bookingUid in range format", async () => {
const startTime = "2050-09-05T11:00:00.000Z";
const booking = await bookingsRepositoryFixture.create({
uid: `booking-uid-${seatedEventTypeId}`,
title: "booking title",
startTime,
endTime: "2050-09-05T12:00:00.000Z",
eventType: {
connect: {
id: seatedEventTypeId,
},
},
metadata: {},
responses: {
name: "tester",
email: "[email protected]",
guests: [],
},
user: {
connect: {
id: user.id,
},
},
});

const attendee = await attendeesRepositoryFixture.create({
name: "tester",
email: "[email protected]",
timeZone: "Europe/London",
booking: {
connect: {
id: booking.id,
},
},
});

bookingSeatsRepositoryFixture.create({
referenceUid: "100",
data: {},
booking: {
connect: {
id: booking.id,
},
},
attendee: {
connect: {
id: attendee.id,
},
},
});

const response = await request(app.getHttpServer())
.get(
`/api/v2/slots/available?eventTypeId=${seatedEventTypeId}&startTime=2050-09-05&endTime=2050-09-10&slotFormat=range`
)
.expect(200);

const responseBody = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
const slots = responseBody.data;

expect(slots).toBeDefined();
const days = Object.keys(slots.slots);
expect(days.length).toEqual(5);

const expectedSlotsUTC2050_09_05 = [
{ startTime: "2050-09-05T07:00:00.000Z", endTime: "2050-09-05T08:00:00.000Z" },
{ startTime: "2050-09-05T08:00:00.000Z", endTime: "2050-09-05T09:00:00.000Z" },
{ startTime: "2050-09-05T09:00:00.000Z", endTime: "2050-09-05T10:00:00.000Z" },
{ startTime: "2050-09-05T10:00:00.000Z", endTime: "2050-09-05T11:00:00.000Z" },
{
startTime: "2050-09-05T11:00:00.000Z",
endTime: "2050-09-05T12:00:00.000Z",
attendees: 1,
bookingUid: booking.uid,
},
{ startTime: "2050-09-05T12:00:00.000Z", endTime: "2050-09-05T13:00:00.000Z" },
{ startTime: "2050-09-05T13:00:00.000Z", endTime: "2050-09-05T14:00:00.000Z" },
{ startTime: "2050-09-05T14:00:00.000Z", endTime: "2050-09-05T15:00:00.000Z" },
];

expect(slots).toEqual({
slots: { ...expectedSlotsUTCRange.slots, "2050-09-05": expectedSlotsUTC2050_09_05 },
});

await bookingsRepositoryFixture.deleteById(booking.id);
});

afterAll(async () => {
Expand Down
16 changes: 12 additions & 4 deletions apps/api/v2/src/modules/slots/services/slots-output.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import { DateTime } from "luxon";

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

type TimeSlots = { slots: Record<string, { time: string }[]> };
type RangeSlots = { slots: Record<string, { startTime: string; endTime: string }[]> };
type TimeSlots = { slots: Record<string, { time: string; attendees?: number; bookingUid?: string }[]> };
type RangeSlots = {
slots: Record<string, { startTime: string; endTime: string; attendees?: number; bookingUid?: string }[]>;
};

@Injectable()
export class SlotsOutputService {
Expand All @@ -30,6 +32,8 @@ export class SlotsOutputService {
const formattedSlots = Object.entries(slots.slots).reduce((acc, [date, daySlots]) => {
acc[date] = daySlots.map((slot) => ({
time: DateTime.fromISO(slot.time).setZone(timeZone).toISO() || "unknown-time",
...(slot.attendees ? { attendees: slot.attendees } : {}),
...(slot.bookingUid ? { bookingUid: slot.bookingUid } : {}),
}));
return acc;
}, {} as Record<string, { time: string }[]>);
Expand All @@ -42,6 +46,8 @@ export class SlotsOutputService {
acc[date] = daySlots.map((slot) => ({
startTime: DateTime.fromISO(slot.startTime).setZone(timeZone).toISO() || "unknown-start-time",
endTime: DateTime.fromISO(slot.endTime).setZone(timeZone).toISO() || "unknown-end-time",
...(slot.attendees ? { attendees: slot.attendees } : {}),
...(slot.bookingUid ? { bookingUid: slot.bookingUid } : {}),
}));
return acc;
}, {} as Record<string, { startTime: string; endTime: string }[]>);
Expand All @@ -62,14 +68,16 @@ export class SlotsOutputService {
const slotDuration = await this.getDuration(duration, eventTypeId);

const slots = Object.entries(availableSlots.slots).reduce<
Record<string, { startTime: string; endTime: string }[]>
Record<string, { startTime: string; endTime: string; attendees?: number; bookingUid?: string }[]>
>((acc, [date, slots]) => {
acc[date] = (slots as { time: string }[]).map((slot) => {
acc[date] = (slots as { time: string; attendees?: number; bookingUid?: string }[]).map((slot) => {
const startTime = new Date(slot.time);
const endTime = new Date(startTime.getTime() + slotDuration * 60000);
return {
startTime: startTime.toISOString(),
endTime: endTime.toISOString(),
...(slot.attendees ? { attendees: slot.attendees } : {}),
...(slot.bookingUid ? { bookingUid: slot.bookingUid } : {}),
};
});
return acc;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
import { TestingModule } from "@nestjs/testing";

import { Prisma } from "@calcom/prisma/client";

export class AttendeeRepositoryFixture {
private prismaReadClient: PrismaReadService["prisma"];
private prismaWriteClient: PrismaWriteService["prisma"];

constructor(private readonly module: TestingModule) {
this.prismaReadClient = module.get(PrismaReadService).prisma;
this.prismaWriteClient = module.get(PrismaWriteService).prisma;
}

async create(attendee: Prisma.AttendeeCreateInput) {
return this.prismaWriteClient.attendee.create({ data: attendee });
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
import { TestingModule } from "@nestjs/testing";

import { Prisma } from "@calcom/prisma/client";

export class BookingSeatRepositoryFixture {
private prismaReadClient: PrismaReadService["prisma"];
private prismaWriteClient: PrismaWriteService["prisma"];

constructor(private readonly module: TestingModule) {
this.prismaReadClient = module.get(PrismaReadService).prisma;
this.prismaWriteClient = module.get(PrismaWriteService).prisma;
}

async create(bookingSeat: Prisma.BookingSeatCreateInput) {
return this.prismaWriteClient.bookingSeat.create({ data: bookingSeat });
}
}

0 comments on commit 67fb0e6

Please sign in to comment.