mirror of
https://github.com/hanzoai/calendar.git
synced 2026-10-30 14:46:43 +00:00
* refactor: rename SelectedSlotsRepository to PrismaSelectedSlotRepository - Rename class from SelectedSlotsRepository to PrismaSelectedSlotRepository for consistency - Update all imports and type references throughout the codebase - Update DI module bindings and variable names - Maintain type safety without breaking changes Co-Authored-By: [email protected] <[email protected]> * refactor: complete PrismaSelectedSlotRepository rename - Update DI module binding property name from selectedSlotsRepo to selectedSlotRepo - Update test mock to use PrismaSelectedSlotRepository class name - Ensure all references are consistently updated Co-Authored-By: [email protected] <[email protected]> * fix: rename file to prismaSelectedSlotRepository.ts and fix plural variable references - Rename apps/api/v2/src/lib/repositories/prisma-selected-slots.repository.ts to prismaSelectedSlotRepository.ts - Update class name from PrismaSelectedSlotsRepository to PrismaSelectedSlotRepository - Fix plural variable reference selectedSlotsRepository to selectedSlotRepository in service - Update all import paths to reference the new file name - Maintain consistency with camelCase naming convention Co-Authored-By: [email protected] <[email protected]> * fix: rename SelectedSlotsRepositoryFixture to SelectedSlotRepositoryFixture for consistency - Rename test fixture class from SelectedSlotsRepositoryFixture to SelectedSlotRepositoryFixture (singular) - Update all import statements in test files to use the renamed class - Resolves naming mismatch between fixture class and variable declarations - Fixes TypeScript compilation errors in CI tests Co-Authored-By: [email protected] <[email protected]> * feat: add SelectedSlotRepositoryInterface and update DI to use interface Co-Authored-By: [email protected] <[email protected]> * refactor: rename selectedSlots.ts to selectedSlot.ts and update all imports Co-Authored-By: [email protected] <[email protected]> * fix: update test mock import path after file rename Co-Authored-By: [email protected] <[email protected]> * Update apps/api/v2/src/lib/modules/available-slots.module.ts * Update apps/api/v2/src/lib/modules/available-slots.module.ts * Implement DTO * dont declare dependencies locally, duplicating * Small DTO/token fix * chore: bump @calcom/platform-libraries from 0.0.266 to 0.0.267 * oops. * Update fixture names also * Omg these vscode actions preventing saves * Final fix, hopefully --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Somay Chauhan <[email protected]>
127 lines
4.3 KiB
TypeScript
127 lines
4.3 KiB
TypeScript
import { serialize } from "cookie";
|
||
import type { NextApiRequest, NextApiResponse } from "next";
|
||
import { v4 as uuid } from "uuid";
|
||
|
||
import dayjs from "@calcom/dayjs";
|
||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||
import { MINUTES_TO_BOOK } from "@calcom/lib/constants";
|
||
import { PrismaSelectedSlotRepository } from "@calcom/lib/server/repository/PrismaSelectedSlotRepository";
|
||
import type { PrismaClient } from "@calcom/prisma";
|
||
import { BookingStatus } from "@calcom/prisma/enums";
|
||
|
||
import { TRPCError } from "@trpc/server";
|
||
|
||
import type { TReserveSlotInputSchema } from "./reserveSlot.schema";
|
||
|
||
interface ReserveSlotOptions {
|
||
ctx: {
|
||
prisma: PrismaClient;
|
||
req?: NextApiRequest | undefined;
|
||
res?: NextApiResponse | undefined;
|
||
};
|
||
input: TReserveSlotInputSchema;
|
||
}
|
||
export const reserveSlotHandler = async ({ ctx, input }: ReserveSlotOptions) => {
|
||
const { prisma, req, res } = ctx;
|
||
const uid = req?.cookies?.uid || uuid();
|
||
|
||
const { slotUtcStartDate, slotUtcEndDate, eventTypeId, _isDryRun } = input;
|
||
const releaseAt = dayjs.utc().add(parseInt(MINUTES_TO_BOOK), "minutes").format();
|
||
const eventType = await prisma.eventType.findUnique({
|
||
where: { id: eventTypeId },
|
||
select: { users: { select: { id: true } }, seatsPerTimeSlot: true },
|
||
});
|
||
|
||
if (!eventType) {
|
||
throw new TRPCError({
|
||
message: "Event type not found",
|
||
code: "NOT_FOUND",
|
||
});
|
||
}
|
||
|
||
let shouldReserveSlot = true;
|
||
|
||
// If this is a seated event then don't reserve a slot
|
||
if (eventType.seatsPerTimeSlot) {
|
||
// Check to see if this is the last attendee
|
||
const bookingWithAttendees = await prisma.booking.findFirst({
|
||
where: {
|
||
eventTypeId,
|
||
startTime: slotUtcStartDate,
|
||
endTime: slotUtcEndDate,
|
||
status: BookingStatus.ACCEPTED,
|
||
},
|
||
select: { attendees: true },
|
||
});
|
||
const bookingAttendeesLength = bookingWithAttendees?.attendees?.length;
|
||
if (bookingAttendeesLength) {
|
||
const seatsLeft = eventType.seatsPerTimeSlot - bookingAttendeesLength;
|
||
if (seatsLeft < 1) shouldReserveSlot = false;
|
||
} else {
|
||
// If there is no booking yet then don't reserve the slot
|
||
shouldReserveSlot = false;
|
||
}
|
||
}
|
||
|
||
// Check for existing reservations for the same slot
|
||
const slotsRepo = new PrismaSelectedSlotRepository(prisma);
|
||
const reservedBySomeoneElse = await slotsRepo.findReservedByOthers({
|
||
slot: {
|
||
utcStartIso: slotUtcStartDate,
|
||
utcEndIso: slotUtcEndDate,
|
||
},
|
||
eventTypeId,
|
||
uid,
|
||
});
|
||
|
||
if (eventType && shouldReserveSlot && !reservedBySomeoneElse && !_isDryRun) {
|
||
try {
|
||
await Promise.all(
|
||
// FIXME: In case of team event, users doesn't have assignees, those are in hosts. users just have the creator of the event which is wrong.
|
||
// Also, we must not block all the users' slots, we must use routedTeamMemberIds if set like we do in getSchedule.
|
||
// We could even improve it by identifying the next person being booked now that we have a queue of assignees.
|
||
eventType.users.map((user) =>
|
||
prisma.selectedSlots.upsert({
|
||
where: { selectedSlotUnique: { userId: user.id, slotUtcStartDate, slotUtcEndDate, uid } },
|
||
update: {
|
||
slotUtcStartDate,
|
||
slotUtcEndDate,
|
||
releaseAt,
|
||
eventTypeId,
|
||
},
|
||
create: {
|
||
userId: user.id,
|
||
eventTypeId,
|
||
slotUtcStartDate,
|
||
slotUtcEndDate,
|
||
uid,
|
||
releaseAt,
|
||
isSeat: eventType.seatsPerTimeSlot !== null,
|
||
},
|
||
})
|
||
)
|
||
);
|
||
} catch {
|
||
throw new TRPCError({
|
||
message: "Event type not found",
|
||
code: "NOT_FOUND",
|
||
});
|
||
}
|
||
}
|
||
// We need this cookie to be accessible from embeds where the booking flow is displayed within an iframe on a different origin.
|
||
// For third‑party iframe contexts (embeds on other sites), browsers require SameSite=None and Secure to make the cookie available.
|
||
// For local development on http://localhost we fall back to SameSite=Lax to avoid requiring https during development.
|
||
const useSecureCookies = WEBAPP_URL.startsWith("https://");
|
||
res?.setHeader(
|
||
"Set-Cookie",
|
||
serialize("uid", uid, {
|
||
path: "/",
|
||
sameSite: useSecureCookies ? "none" : "lax",
|
||
secure: useSecureCookies,
|
||
})
|
||
);
|
||
return {
|
||
uid: uid,
|
||
};
|
||
};
|