From 9df9f9bb6b8b73563a3c6f7db17502b6ce35ae20 Mon Sep 17 00:00:00 2001 From: Lauris Skraucis Date: Wed, 7 Feb 2024 17:43:19 +0100 Subject: [PATCH] refactor: trpc get schedule handler (#13586) --- packages/lib/index.ts | 1 + packages/lib/schedules/client/transformers.ts | 101 ++++++++++++++++++ packages/lib/schedules/index.ts | 1 + .../availability/schedule/get.handler.ts | 57 ++-------- .../availability/schedule/update.handler.ts | 5 +- .../routers/viewer/availability/util.ts | 36 +------ 6 files changed, 118 insertions(+), 83 deletions(-) create mode 100644 packages/lib/schedules/client/transformers.ts create mode 100644 packages/lib/schedules/index.ts diff --git a/packages/lib/index.ts b/packages/lib/index.ts index 04b47e4d3f..6943b61262 100644 --- a/packages/lib/index.ts +++ b/packages/lib/index.ts @@ -4,3 +4,4 @@ export * from "./isRecurringEvent"; export * from "./isBookingLimits"; export * from "./isDurationLimits"; export * from "./validateIntervalLimitOrder"; +export * from "./schedules"; diff --git a/packages/lib/schedules/client/transformers.ts b/packages/lib/schedules/client/transformers.ts new file mode 100644 index 0000000000..c3afe15999 --- /dev/null +++ b/packages/lib/schedules/client/transformers.ts @@ -0,0 +1,101 @@ +import type { Availability as AvailabilityModel, Schedule as ScheduleModel } from "@prisma/client"; + +import dayjs from "@calcom/dayjs"; +import { getWorkingHours } from "@calcom/lib/availability"; +import { yyyymmdd } from "@calcom/lib/date-fns"; +import type { Schedule, TimeRange } from "@calcom/types/schedule"; + +export type ScheduleWithAvailabilities = ScheduleModel & { availability: AvailabilityModel[] }; + +export type ScheduleWithAvailabilitiesForWeb = Pick & { + isManaged: boolean; + workingHours: ReturnType; + schedule: AvailabilityModel[]; + availability: ReturnType; + timeZone: string; + dateOverrides: ReturnType; + isDefault: boolean; + isLastSchedule: boolean; + readOnly: boolean; +}; + +export function transformWorkingHoursForClient(schedule: ScheduleWithAvailabilities) { + return getWorkingHours( + { timeZone: schedule.timeZone || undefined, utcOffset: 0 }, + schedule.availability || [] + ); +} + +export function transformAvailabilityForClient(schedule: ScheduleWithAvailabilities) { + return transformScheduleToAvailabilityForClient(schedule).map((a) => + a.map((startAndEnd) => ({ + ...startAndEnd, + end: new Date(startAndEnd.end.toISOString().replace("23:59:00.000Z", "23:59:59.999Z")), + })) + ); +} + +export function transformDateOverridesForClient(schedule: ScheduleWithAvailabilities, timeZone: string) { + return schedule.availability.reduce((acc, override) => { + // only iff future date override + if (!override.date || dayjs.tz(override.date, timeZone).isBefore(dayjs(), "day")) { + return acc; + } + const newValue = { + start: dayjs + .utc(override.date) + .hour(override.startTime.getUTCHours()) + .minute(override.startTime.getUTCMinutes()) + .toDate(), + end: dayjs + .utc(override.date) + .hour(override.endTime.getUTCHours()) + .minute(override.endTime.getUTCMinutes()) + .toDate(), + }; + const dayRangeIndex = acc.findIndex( + // early return prevents override.date from ever being empty. + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + (item) => yyyymmdd(item.ranges[0].start) === yyyymmdd(override.date!) + ); + if (dayRangeIndex === -1) { + acc.push({ ranges: [newValue] }); + return acc; + } + acc[dayRangeIndex].ranges.push(newValue); + return acc; + }, [] as { ranges: TimeRange[] }[]); +} + +export const transformScheduleToAvailabilityForClient = ( + schedule: Partial & { availability: AvailabilityModel[] } +) => { + return schedule.availability.reduce( + (schedule: Schedule, availability) => { + availability.days.forEach((day) => { + schedule[day].push({ + start: new Date( + Date.UTC( + new Date().getUTCFullYear(), + new Date().getUTCMonth(), + new Date().getUTCDate(), + availability.startTime.getUTCHours(), + availability.startTime.getUTCMinutes() + ) + ), + end: new Date( + Date.UTC( + new Date().getUTCFullYear(), + new Date().getUTCMonth(), + new Date().getUTCDate(), + availability.endTime.getUTCHours(), + availability.endTime.getUTCMinutes() + ) + ), + }); + }); + return schedule; + }, + Array.from([...Array(7)]).map(() => []) + ); +}; diff --git a/packages/lib/schedules/index.ts b/packages/lib/schedules/index.ts new file mode 100644 index 0000000000..abca07a1d6 --- /dev/null +++ b/packages/lib/schedules/index.ts @@ -0,0 +1 @@ +export * from "./client/transformers"; diff --git a/packages/trpc/server/routers/viewer/availability/schedule/get.handler.ts b/packages/trpc/server/routers/viewer/availability/schedule/get.handler.ts index afabbf4542..42fbf5a78b 100644 --- a/packages/trpc/server/routers/viewer/availability/schedule/get.handler.ts +++ b/packages/trpc/server/routers/viewer/availability/schedule/get.handler.ts @@ -1,14 +1,16 @@ -import dayjs from "@calcom/dayjs"; -import { getWorkingHours } from "@calcom/lib/availability"; -import { yyyymmdd } from "@calcom/lib/date-fns"; +import type { ScheduleWithAvailabilitiesForWeb } from "@calcom/lib"; +import { + transformAvailabilityForClient, + transformDateOverridesForClient, + transformWorkingHoursForClient, +} from "@calcom/lib"; import { hasReadPermissionsForUserId } from "@calcom/lib/hasEditPermissionForUser"; import { prisma } from "@calcom/prisma"; -import type { TimeRange } from "@calcom/types/schedule"; import { TRPCError } from "@trpc/server"; import type { TrpcSessionUser } from "../../../../trpc"; -import { convertScheduleToAvailability, getDefaultScheduleId } from "../util"; +import { getDefaultScheduleId } from "../util"; import type { TGetInputSchema } from "./get.schema"; type GetOptions = { @@ -18,7 +20,7 @@ type GetOptions = { input: TGetInputSchema; }; -export const getHandler = async ({ ctx, input }: GetOptions) => { +export const getHandler = async ({ ctx, input }: GetOptions): Promise => { const { user } = ctx; const schedule = await prisma.schedule.findUnique({ @@ -64,48 +66,11 @@ export const getHandler = async ({ ctx, input }: GetOptions) => { id: schedule.id, name: schedule.name, isManaged: schedule.userId !== user.id, - workingHours: getWorkingHours( - { timeZone: schedule.timeZone || undefined, utcOffset: 0 }, - schedule.availability || [] - ), + workingHours: transformWorkingHoursForClient(schedule), schedule: schedule.availability, - availability: convertScheduleToAvailability(schedule).map((a) => - a.map((startAndEnd) => ({ - ...startAndEnd, - // Turn our limited granularity into proper end of day. - end: new Date(startAndEnd.end.toISOString().replace("23:59:00.000Z", "23:59:59.999Z")), - })) - ), + availability: transformAvailabilityForClient(schedule), timeZone, - dateOverrides: schedule.availability.reduce((acc, override) => { - // only iff future date override - if (!override.date || dayjs.tz(override.date, timeZone).isBefore(dayjs(), "day")) { - return acc; - } - const newValue = { - start: dayjs - .utc(override.date) - .hour(override.startTime.getUTCHours()) - .minute(override.startTime.getUTCMinutes()) - .toDate(), - end: dayjs - .utc(override.date) - .hour(override.endTime.getUTCHours()) - .minute(override.endTime.getUTCMinutes()) - .toDate(), - }; - const dayRangeIndex = acc.findIndex( - // early return prevents override.date from ever being empty. - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - (item) => yyyymmdd(item.ranges[0].start) === yyyymmdd(override.date!) - ); - if (dayRangeIndex === -1) { - acc.push({ ranges: [newValue] }); - return acc; - } - acc[dayRangeIndex].ranges.push(newValue); - return acc; - }, [] as { ranges: TimeRange[] }[]), + dateOverrides: transformDateOverridesForClient(schedule, timeZone), isDefault: !input.scheduleId || user.defaultScheduleId === schedule.id, isLastSchedule: schedulesCount <= 1, readOnly: schedule.userId !== user.id && !input.isManagedEventType, diff --git a/packages/trpc/server/routers/viewer/availability/schedule/update.handler.ts b/packages/trpc/server/routers/viewer/availability/schedule/update.handler.ts index 53eca893a3..8cdad6eff0 100644 --- a/packages/trpc/server/routers/viewer/availability/schedule/update.handler.ts +++ b/packages/trpc/server/routers/viewer/availability/schedule/update.handler.ts @@ -1,3 +1,4 @@ +import { transformScheduleToAvailabilityForClient } from "@calcom/lib"; import { getAvailabilityFromSchedule } from "@calcom/lib/availability"; import { hasEditPermissionForUserID } from "@calcom/lib/hasEditPermissionForUser"; import { prisma } from "@calcom/prisma"; @@ -5,7 +6,7 @@ import { prisma } from "@calcom/prisma"; import { TRPCError } from "@trpc/server"; import type { TrpcSessionUser } from "../../../../trpc"; -import { convertScheduleToAvailability, setupDefaultSchedule } from "../util"; +import { setupDefaultSchedule } from "../util"; import type { TUpdateInputSchema } from "./update.schema"; type UpdateOptions = { @@ -116,7 +117,7 @@ export const updateHandler = async ({ input, ctx }: UpdateOptions) => { }, }); - const userAvailability = convertScheduleToAvailability(schedule); + const userAvailability = transformScheduleToAvailabilityForClient(schedule); return { schedule, diff --git a/packages/trpc/server/routers/viewer/availability/util.ts b/packages/trpc/server/routers/viewer/availability/util.ts index 4f93efdd8a..66b0775389 100644 --- a/packages/trpc/server/routers/viewer/availability/util.ts +++ b/packages/trpc/server/routers/viewer/availability/util.ts @@ -1,7 +1,6 @@ -import type { Availability as AvailabilityModel, Schedule as ScheduleModel, User } from "@prisma/client"; +import type { User } from "@prisma/client"; import type { PrismaClient } from "@calcom/prisma"; -import type { Schedule } from "@calcom/types/schedule"; export const getDefaultScheduleId = async (userId: number, prisma: PrismaClient) => { const user = await prisma.user.findUnique({ @@ -44,39 +43,6 @@ export const hasDefaultSchedule = async (user: Partial, prisma: PrismaClie return !!user.defaultScheduleId || !!defaultSchedule; }; -export const convertScheduleToAvailability = ( - schedule: Partial & { availability: AvailabilityModel[] } -) => { - return schedule.availability.reduce( - (schedule: Schedule, availability) => { - availability.days.forEach((day) => { - schedule[day].push({ - start: new Date( - Date.UTC( - new Date().getUTCFullYear(), - new Date().getUTCMonth(), - new Date().getUTCDate(), - availability.startTime.getUTCHours(), - availability.startTime.getUTCMinutes() - ) - ), - end: new Date( - Date.UTC( - new Date().getUTCFullYear(), - new Date().getUTCMonth(), - new Date().getUTCDate(), - availability.endTime.getUTCHours(), - availability.endTime.getUTCMinutes() - ) - ), - }); - }); - return schedule; - }, - Array.from([...Array(7)]).map(() => []) - ); -}; - export const setupDefaultSchedule = async (userId: number, scheduleId: number, prisma: PrismaClient) => { return prisma.user.update({ where: {