refactor: getAvailableSlots use repositories (#22290)
This commit is contained in:
@@ -38,7 +38,7 @@
|
||||
"@axiomhq/winston": "^1.2.0",
|
||||
"@calcom/platform-constants": "*",
|
||||
"@calcom/platform-enums": "*",
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.239",
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.241",
|
||||
"@calcom/platform-types": "*",
|
||||
"@calcom/platform-utils": "*",
|
||||
"@calcom/prisma": "*",
|
||||
|
||||
@@ -753,6 +753,27 @@ export class EventTypeRepository {
|
||||
});
|
||||
}
|
||||
|
||||
static async findFirstEventTypeId({
|
||||
slug,
|
||||
teamId,
|
||||
userId,
|
||||
}: {
|
||||
slug: string;
|
||||
teamId?: number;
|
||||
userId?: number;
|
||||
}) {
|
||||
return prisma.eventType.findFirst({
|
||||
where: {
|
||||
slug,
|
||||
...(teamId ? { teamId } : {}),
|
||||
...(userId ? { userId } : {}),
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
static async findByIdIncludeHostsAndTeam({ id }: { id: number }) {
|
||||
const eventType = await prisma.eventType.findUnique({
|
||||
where: {
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
import type { PrismaClient } from "@calcom/prisma";
|
||||
|
||||
export class PrismaOOORepository {
|
||||
constructor(private prismaClient: PrismaClient) {}
|
||||
|
||||
async findManyOOO({
|
||||
startTimeDate,
|
||||
endTimeDate,
|
||||
allUserIds,
|
||||
}: {
|
||||
startTimeDate: Date;
|
||||
endTimeDate: Date;
|
||||
allUserIds: number[];
|
||||
}) {
|
||||
return this.prismaClient.outOfOfficeEntry.findMany({
|
||||
where: {
|
||||
userId: {
|
||||
in: allUserIds,
|
||||
},
|
||||
OR: [
|
||||
// outside of range
|
||||
// (start <= 'dateTo' AND end >= 'dateFrom')
|
||||
{
|
||||
start: {
|
||||
lte: endTimeDate,
|
||||
},
|
||||
end: {
|
||||
gte: startTimeDate,
|
||||
},
|
||||
},
|
||||
// start is between dateFrom and dateTo but end is outside of range
|
||||
// (start <= 'dateTo' AND end >= 'dateTo')
|
||||
{
|
||||
start: {
|
||||
lte: endTimeDate,
|
||||
},
|
||||
|
||||
end: {
|
||||
gte: endTimeDate,
|
||||
},
|
||||
},
|
||||
// end is between dateFrom and dateTo but start is outside of range
|
||||
// (start <= 'dateFrom' OR end <= 'dateTo')
|
||||
{
|
||||
start: {
|
||||
lte: startTimeDate,
|
||||
},
|
||||
|
||||
end: {
|
||||
lte: endTimeDate,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
start: true,
|
||||
end: true,
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
toUser: {
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
reason: {
|
||||
select: {
|
||||
id: true,
|
||||
emoji: true,
|
||||
reason: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -70,4 +70,43 @@ export class SelectedSlotsRepository {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
static async findManyUnexpiredSlots({
|
||||
userIds,
|
||||
currentTimeInUtc,
|
||||
}: {
|
||||
userIds: number[];
|
||||
currentTimeInUtc: string;
|
||||
}) {
|
||||
return prisma.selectedSlots.findMany({
|
||||
where: {
|
||||
userId: { in: userIds },
|
||||
releaseAt: { gt: currentTimeInUtc },
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
slotUtcStartDate: true,
|
||||
slotUtcEndDate: true,
|
||||
userId: true,
|
||||
isSeat: true,
|
||||
eventTypeId: true,
|
||||
uid: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
static async deleteManyExpiredSlots({
|
||||
eventTypeId,
|
||||
currentTimeInUtc,
|
||||
}: {
|
||||
eventTypeId: number;
|
||||
currentTimeInUtc: string;
|
||||
}) {
|
||||
return prisma.selectedSlots.deleteMany({
|
||||
where: {
|
||||
eventTypeId: { equals: eventTypeId },
|
||||
releaseAt: { lt: currentTimeInUtc },
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,6 +212,24 @@ export class TeamRepository {
|
||||
});
|
||||
}
|
||||
|
||||
static async findFirstBySlugAndParentSlug({
|
||||
slug,
|
||||
parentSlug,
|
||||
select = teamSelect,
|
||||
}: {
|
||||
slug: string;
|
||||
parentSlug: string | null;
|
||||
select?: Prisma.TeamSelect;
|
||||
}) {
|
||||
return await prisma.team.findFirst({
|
||||
where: {
|
||||
slug,
|
||||
parent: parentSlug ? whereClauseForOrgWithSlugOrRequestedSlug(parentSlug) : null,
|
||||
},
|
||||
select,
|
||||
});
|
||||
}
|
||||
|
||||
static async deleteById({ id }: { id: number }) {
|
||||
const deletedTeam = await prisma.$transaction(async (tx) => {
|
||||
await tx.eventType.deleteMany({
|
||||
|
||||
@@ -4,7 +4,7 @@ import { whereClauseForOrgWithSlugOrRequestedSlug } from "@calcom/ee/organizatio
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { safeStringify } from "@calcom/lib/safeStringify";
|
||||
import { getTranslation } from "@calcom/lib/server/i18n";
|
||||
import prisma from "@calcom/prisma";
|
||||
import prisma, { availabilityUserSelect } from "@calcom/prisma";
|
||||
import type { Prisma } from "@calcom/prisma/client";
|
||||
import type { User as UserType } from "@calcom/prisma/client";
|
||||
import type { CreationSource } from "@calcom/prisma/enums";
|
||||
@@ -965,4 +965,29 @@ export class UserRepository {
|
||||
data: { whitelistWorkflows },
|
||||
});
|
||||
}
|
||||
|
||||
static async findManyUsersForDynamicEventType({
|
||||
currentOrgDomain,
|
||||
usernameList,
|
||||
}: {
|
||||
currentOrgDomain: string | null;
|
||||
usernameList: string[];
|
||||
}) {
|
||||
const { where } = await UserRepository._getWhereClauseForFindingUsersByUsername({
|
||||
orgSlug: currentOrgDomain,
|
||||
usernameList,
|
||||
});
|
||||
|
||||
// TODO: Should be moved to UserRepository
|
||||
return prisma.user.findMany({
|
||||
where,
|
||||
select: {
|
||||
allowDynamicBooking: true,
|
||||
...availabilityUserSelect,
|
||||
credentials: {
|
||||
select: credentialForCalendarServiceSelect,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { v4 as uuid } from "uuid";
|
||||
|
||||
import type { Dayjs } from "@calcom/dayjs";
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { getSlugOrRequestedSlug, orgDomainConfig } from "@calcom/ee/organizations/lib/orgDomains";
|
||||
import { orgDomainConfig } from "@calcom/ee/organizations/lib/orgDomains";
|
||||
import { checkForConflicts } from "@calcom/features/bookings/lib/conflictChecker/checkForConflicts";
|
||||
import { isEventTypeLoggingEnabled } from "@calcom/features/bookings/lib/isEventTypeLoggingEnabled";
|
||||
import { getShouldServeCache } from "@calcom/features/calendar-cache/lib/getShouldServeCache";
|
||||
@@ -44,15 +44,17 @@ import { getTotalBookingDuration } from "@calcom/lib/server/queries/booking";
|
||||
import { BookingRepository as BookingRepo } from "@calcom/lib/server/repository/booking";
|
||||
import { EventTypeRepository } from "@calcom/lib/server/repository/eventType";
|
||||
import { RoutingFormResponseRepository } from "@calcom/lib/server/repository/formResponse";
|
||||
import { PrismaOOORepository } from "@calcom/lib/server/repository/ooo";
|
||||
import { ScheduleRepository } from "@calcom/lib/server/repository/schedule";
|
||||
import { SelectedSlotsRepository } from "@calcom/lib/server/repository/selectedSlots";
|
||||
import { TeamRepository } from "@calcom/lib/server/repository/team";
|
||||
import { UserRepository, withSelectedCalendars } from "@calcom/lib/server/repository/user";
|
||||
import getSlots from "@calcom/lib/slots";
|
||||
import prisma, { availabilityUserSelect } from "@calcom/prisma";
|
||||
import type { Prisma } from "@calcom/prisma/client";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { PeriodType } from "@calcom/prisma/client";
|
||||
import { SchedulingType } from "@calcom/prisma/enums";
|
||||
import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential";
|
||||
import type { EventBusyDate, EventBusyDetails } from "@calcom/types/Calendar";
|
||||
import type { CredentialForCalendarService, CredentialPayload } from "@calcom/types/Credential";
|
||||
import type { CredentialForCalendarService } from "@calcom/types/Credential";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
@@ -61,25 +63,10 @@ import { handleNotificationWhenNoSlots } from "./handleNotificationWhenNoSlots";
|
||||
import type { GetScheduleOptions } from "./types";
|
||||
|
||||
const log = logger.getSubLogger({ prefix: ["[slots/util]"] });
|
||||
type GetAvailabilityUserWithoutDelegationCredentials = Omit<GetAvailabilityUser, "credentials"> & {
|
||||
credentials: CredentialPayload[];
|
||||
};
|
||||
|
||||
type GetAvailabilityUserWithDelegationCredentials = Omit<GetAvailabilityUser, "credentials"> & {
|
||||
credentials: CredentialForCalendarService[];
|
||||
};
|
||||
const selectSelectedSlots = {
|
||||
select: {
|
||||
id: true,
|
||||
slotUtcStartDate: true,
|
||||
slotUtcEndDate: true,
|
||||
userId: true,
|
||||
isSeat: true,
|
||||
eventTypeId: true,
|
||||
uid: true,
|
||||
},
|
||||
} satisfies Prisma.SelectedSlotsDefaultArgs;
|
||||
|
||||
type SelectedSlots = Prisma.SelectedSlotsGetPayload<typeof selectSelectedSlots>;
|
||||
|
||||
async function getEventTypeId({
|
||||
slug,
|
||||
@@ -107,16 +94,7 @@ async function getEventTypeId({
|
||||
organizationDetails ?? { currentOrgDomain: null, isValidOrgDomain: false }
|
||||
);
|
||||
}
|
||||
const eventType = await prisma.eventType.findFirst({
|
||||
where: {
|
||||
slug: eventTypeSlug,
|
||||
...(teamId ? { teamId } : {}),
|
||||
...(userId ? { userId } : {}),
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
const eventType = await EventTypeRepository.findFirstEventTypeId({ slug: eventTypeSlug, teamId, userId });
|
||||
if (!eventType) {
|
||||
throw new TRPCError({ code: "NOT_FOUND" });
|
||||
}
|
||||
@@ -135,12 +113,9 @@ async function _getReservedSlotsAndCleanupExpired({
|
||||
const currentTimeInUtc = dayjs.utc().format();
|
||||
|
||||
const unexpiredSelectedSlots =
|
||||
(await prisma.selectedSlots.findMany({
|
||||
where: {
|
||||
userId: { in: usersWithCredentials.map((user) => user.id) },
|
||||
releaseAt: { gt: currentTimeInUtc },
|
||||
},
|
||||
...selectSelectedSlots,
|
||||
(await SelectedSlotsRepository.findManyUnexpiredSlots({
|
||||
userIds: usersWithCredentials.map((user) => user.id),
|
||||
currentTimeInUtc,
|
||||
})) || [];
|
||||
|
||||
const slotsSelectedByOtherUsers = unexpiredSelectedSlots.filter((slot) => slot.uid !== bookerClientUid);
|
||||
@@ -152,12 +127,7 @@ async function _getReservedSlotsAndCleanupExpired({
|
||||
return reservedSlots;
|
||||
|
||||
async function _cleanupExpiredSlots({ eventTypeId }: { eventTypeId: number }) {
|
||||
await prisma.selectedSlots.deleteMany({
|
||||
where: {
|
||||
eventTypeId: { equals: eventTypeId },
|
||||
releaseAt: { lt: currentTimeInUtc },
|
||||
},
|
||||
});
|
||||
await SelectedSlotsRepository.deleteManyExpiredSlots({ eventTypeId, currentTimeInUtc });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,8 +169,9 @@ const _getDynamicEventType = async (
|
||||
});
|
||||
}
|
||||
const dynamicEventType = getDefaultEvent(input.eventTypeSlug);
|
||||
const { where } = await UserRepository._getWhereClauseForFindingUsersByUsername({
|
||||
orgSlug: isValidOrgDomain ? currentOrgDomain : null,
|
||||
|
||||
const usersForDynamicEventType = await UserRepository.findManyUsersForDynamicEventType({
|
||||
currentOrgDomain: isValidOrgDomain ? currentOrgDomain : null,
|
||||
usernameList: Array.isArray(input.usernameList)
|
||||
? input.usernameList
|
||||
: input.usernameList
|
||||
@@ -208,20 +179,9 @@ const _getDynamicEventType = async (
|
||||
: [],
|
||||
});
|
||||
|
||||
// TODO: Should be moved to UserRepository
|
||||
const usersWithOldSelectedCalendars = await prisma.user.findMany({
|
||||
where,
|
||||
select: {
|
||||
allowDynamicBooking: true,
|
||||
...availabilityUserSelect,
|
||||
credentials: {
|
||||
select: credentialForCalendarServiceSelect,
|
||||
},
|
||||
},
|
||||
});
|
||||
const users = usersWithOldSelectedCalendars.map((user) => withSelectedCalendars(user));
|
||||
const usersWithOldSelectedCalendars = usersForDynamicEventType.map((user) => withSelectedCalendars(user));
|
||||
|
||||
const isDynamicAllowed = !users.some((user) => !user.allowDynamicBooking);
|
||||
const isDynamicAllowed = !usersWithOldSelectedCalendars.some((user) => !user.allowDynamicBooking);
|
||||
if (!isDynamicAllowed) {
|
||||
throw new TRPCError({
|
||||
message: "Some of the users in this group do not allow dynamic booking",
|
||||
@@ -229,7 +189,7 @@ const _getDynamicEventType = async (
|
||||
});
|
||||
}
|
||||
return Object.assign({}, dynamicEventType, {
|
||||
users,
|
||||
users: usersWithOldSelectedCalendars,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -250,7 +210,10 @@ export const getRegularOrDynamicEventType = withReporting(
|
||||
"getRegularOrDynamicEventType"
|
||||
);
|
||||
|
||||
function applyOccupiedSeatsToCurrentSeats(currentSeats: CurrentSeats, occupiedSeats: SelectedSlots[]) {
|
||||
function applyOccupiedSeatsToCurrentSeats(
|
||||
currentSeats: CurrentSeats,
|
||||
occupiedSeats: { slotUtcStartDate: Date }[]
|
||||
) {
|
||||
const occupiedSeatsMap = new Map<string, number>();
|
||||
|
||||
occupiedSeats.forEach((item) => {
|
||||
@@ -502,37 +465,10 @@ const _getAvailableSlots = async ({ input, ctx }: GetScheduleOptions): Promise<I
|
||||
const bookerClientUid = ctx?.req?.cookies?.uid;
|
||||
const isRestrictionScheduleFeatureEnabled = await isRestrictionScheduleEnabled(eventType.team?.id);
|
||||
if (eventType.restrictionScheduleId && isRestrictionScheduleFeatureEnabled) {
|
||||
const restrictionSchedule = await prisma.schedule.findUnique({
|
||||
where: { id: eventType.restrictionScheduleId },
|
||||
select: {
|
||||
id: true,
|
||||
timeZone: true,
|
||||
userId: true,
|
||||
availability: {
|
||||
select: {
|
||||
days: true,
|
||||
startTime: true,
|
||||
endTime: true,
|
||||
date: true,
|
||||
},
|
||||
},
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
defaultScheduleId: true,
|
||||
travelSchedules: {
|
||||
select: {
|
||||
id: true,
|
||||
timeZone: true,
|
||||
startDate: true,
|
||||
endDate: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
const scheduleRepo = new ScheduleRepository(prisma);
|
||||
const restrictionSchedule = await scheduleRepo.findScheduleByIdForBuildDateRanges({
|
||||
scheduleId: eventType.restrictionScheduleId,
|
||||
});
|
||||
|
||||
if (restrictionSchedule) {
|
||||
if (!eventType.useBookerTimezone && !restrictionSchedule.timeZone) {
|
||||
throw new TRPCError({
|
||||
@@ -859,85 +795,18 @@ async function getTeamIdFromSlug(
|
||||
organizationDetails: { currentOrgDomain: string | null; isValidOrgDomain: boolean }
|
||||
) {
|
||||
const { currentOrgDomain, isValidOrgDomain } = organizationDetails;
|
||||
const team = await prisma.team.findFirst({
|
||||
where: {
|
||||
slug,
|
||||
parent: isValidOrgDomain && currentOrgDomain ? getSlugOrRequestedSlug(currentOrgDomain) : null,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
const team = await TeamRepository.findFirstBySlugAndParentSlug({
|
||||
slug,
|
||||
parentSlug: isValidOrgDomain && currentOrgDomain ? currentOrgDomain : null,
|
||||
select: { id: true },
|
||||
});
|
||||
|
||||
return team?.id;
|
||||
}
|
||||
|
||||
const _getOOODates = async (startTimeDate: Date, endTimeDate: Date, allUserIds: number[]) => {
|
||||
return await prisma.outOfOfficeEntry.findMany({
|
||||
where: {
|
||||
userId: {
|
||||
in: allUserIds,
|
||||
},
|
||||
OR: [
|
||||
// outside of range
|
||||
// (start <= 'dateTo' AND end >= 'dateFrom')
|
||||
{
|
||||
start: {
|
||||
lte: endTimeDate,
|
||||
},
|
||||
end: {
|
||||
gte: startTimeDate,
|
||||
},
|
||||
},
|
||||
// start is between dateFrom and dateTo but end is outside of range
|
||||
// (start <= 'dateTo' AND end >= 'dateTo')
|
||||
{
|
||||
start: {
|
||||
lte: endTimeDate,
|
||||
},
|
||||
|
||||
end: {
|
||||
gte: endTimeDate,
|
||||
},
|
||||
},
|
||||
// end is between dateFrom and dateTo but start is outside of range
|
||||
// (start <= 'dateFrom' OR end <= 'dateTo')
|
||||
{
|
||||
start: {
|
||||
lte: startTimeDate,
|
||||
},
|
||||
|
||||
end: {
|
||||
lte: endTimeDate,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
start: true,
|
||||
end: true,
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
toUser: {
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
reason: {
|
||||
select: {
|
||||
id: true,
|
||||
emoji: true,
|
||||
reason: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const oooRepo = new PrismaOOORepository(prisma);
|
||||
return oooRepo.findManyOOO({ startTimeDate, endTimeDate, allUserIds });
|
||||
};
|
||||
|
||||
export const getOOODates = withReporting(_getOOODates, "getOOODates");
|
||||
|
||||
@@ -2519,7 +2519,7 @@ __metadata:
|
||||
"@axiomhq/winston": ^1.2.0
|
||||
"@calcom/platform-constants": "*"
|
||||
"@calcom/platform-enums": "*"
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.239"
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.241"
|
||||
"@calcom/platform-types": "*"
|
||||
"@calcom/platform-utils": "*"
|
||||
"@calcom/prisma": "*"
|
||||
@@ -3565,13 +3565,13 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@calcom/platform-libraries@npm:@calcom/platform-libraries@0.0.239":
|
||||
version: 0.0.239
|
||||
resolution: "@calcom/platform-libraries@npm:0.0.239"
|
||||
"@calcom/platform-libraries@npm:@calcom/platform-libraries@0.0.241":
|
||||
version: 0.0.241
|
||||
resolution: "@calcom/platform-libraries@npm:0.0.241"
|
||||
dependencies:
|
||||
"@calcom/features": "*"
|
||||
"@calcom/lib": "*"
|
||||
checksum: ddc4b74a41cf477c81024ae7faf26a9b6778213b74c33e056ec543a43b0f959b917bcede820677e040684dfb2bed95831ac01218fa08c2f96ee39063afae9c40
|
||||
checksum: 1eec9dce749b6678427e00d0f2d2f383d798066f2f7ef7aca1ce90d5c649ed41d2fc1496be93c7448e7c22d2e015e333c8fe0b81b7c3b04b9637f6ea1910a898
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
Reference in New Issue
Block a user