From ef1844557f2533c53f6b29a1d72783fbc29cb64d Mon Sep 17 00:00:00 2001 From: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com> Date: Sat, 24 May 2025 03:23:40 +0530 Subject: [PATCH] fix: Host schedule not updating upon updating/deleting default schedule (#20750) * fix: host schdule not updating * Update util.ts * tweak * Update delete.handler.ts * add test * update * Update util.test.ts --- packages/lib/server/repository/host.ts | 15 ++ .../availability/schedule/delete.handler.ts | 6 + .../availability/schedule/update.handler.ts | 6 +- .../routers/viewer/availability/util.test.ts | 197 ++++++++++++++++++ .../routers/viewer/availability/util.ts | 9 + 5 files changed, 232 insertions(+), 1 deletion(-) create mode 100644 packages/lib/server/repository/host.ts create mode 100644 packages/trpc/server/routers/viewer/availability/util.test.ts diff --git a/packages/lib/server/repository/host.ts b/packages/lib/server/repository/host.ts new file mode 100644 index 0000000000..5a8967102d --- /dev/null +++ b/packages/lib/server/repository/host.ts @@ -0,0 +1,15 @@ +import prisma from "@calcom/prisma"; + +export class HostRepository { + static async updateHostsSchedule(userId: number, oldScheduleId: number, newScheduleId: number) { + return await prisma.host.updateMany({ + where: { + userId, + scheduleId: oldScheduleId, + }, + data: { + scheduleId: newScheduleId, + }, + }); + } +} diff --git a/packages/trpc/server/routers/viewer/availability/schedule/delete.handler.ts b/packages/trpc/server/routers/viewer/availability/schedule/delete.handler.ts index 7366d91a4c..521bf5a9d8 100644 --- a/packages/trpc/server/routers/viewer/availability/schedule/delete.handler.ts +++ b/packages/trpc/server/routers/viewer/availability/schedule/delete.handler.ts @@ -3,6 +3,7 @@ import { prisma } from "@calcom/prisma"; import { TRPCError } from "@trpc/server"; import type { TrpcSessionUser } from "../../../../types"; +import { updateHostsWithNewDefaultSchedule } from "../util"; import type { TDeleteInputSchema } from "./delete.schema"; type DeleteOptions = { @@ -45,6 +46,8 @@ export const deleteHandler = async ({ input, ctx }: DeleteOptions) => { // to throw the error if there arent any other schedules if (!scheduleToSetAsDefault) throw new TRPCError({ code: "BAD_REQUEST" }); + await updateHostsWithNewDefaultSchedule(user.id, input.scheduleId, scheduleToSetAsDefault.id); + await prisma.user.update({ where: { id: user.id, @@ -53,7 +56,10 @@ export const deleteHandler = async ({ input, ctx }: DeleteOptions) => { defaultScheduleId: scheduleToSetAsDefault?.id || null, }, }); + } else if (user.defaultScheduleId) { + await updateHostsWithNewDefaultSchedule(user.id, input.scheduleId, user.defaultScheduleId); } + await prisma.schedule.delete({ where: { id: input.scheduleId, 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 44ea40b9db..6b1818606a 100644 --- a/packages/trpc/server/routers/viewer/availability/schedule/update.handler.ts +++ b/packages/trpc/server/routers/viewer/availability/schedule/update.handler.ts @@ -6,7 +6,7 @@ import { prisma } from "@calcom/prisma"; import { TRPCError } from "@trpc/server"; import type { TrpcSessionUser } from "../../../../types"; -import { setupDefaultSchedule } from "../util"; +import { setupDefaultSchedule, updateHostsWithNewDefaultSchedule } from "../util"; import type { TUpdateInputSchema } from "./update.schema"; type User = NonNullable; @@ -61,6 +61,10 @@ export const updateHandler = async ({ input, ctx }: UpdateOptions) => { let updatedUser; if (input.isDefault) { + if (user?.defaultScheduleId) { + await updateHostsWithNewDefaultSchedule(user.id, user.defaultScheduleId, input.scheduleId); + } + const setupDefault = await setupDefaultSchedule(user.id, input.scheduleId, prisma); updatedUser = setupDefault; } diff --git a/packages/trpc/server/routers/viewer/availability/util.test.ts b/packages/trpc/server/routers/viewer/availability/util.test.ts new file mode 100644 index 0000000000..34c1f29c2c --- /dev/null +++ b/packages/trpc/server/routers/viewer/availability/util.test.ts @@ -0,0 +1,197 @@ +import { describe, expect, it, vi, beforeEach } from "vitest"; + +import { PrismaClient } from "@calcom/prisma"; + +import { + getDefaultScheduleId, + hasDefaultSchedule, + setupDefaultSchedule, + updateHostsWithNewDefaultSchedule, +} from "./util"; + +vi.mock("@calcom/prisma", () => { + const mockPrisma = { + user: { + findUnique: vi.fn(), + update: vi.fn(), + }, + schedule: { + findFirst: vi.fn(), + }, + host: { + updateMany: vi.fn(), + }, + }; + return { + __esModule: true, + default: mockPrisma, + PrismaClient: vi.fn(() => mockPrisma), + }; +}); + +describe("Availability Utils", () => { + let prisma: PrismaClient; + + beforeEach(() => { + prisma = new PrismaClient(); + vi.clearAllMocks(); + }); + + describe("getDefaultScheduleId", () => { + it("should return defaultScheduleId if user has one", async () => { + const userId = 1; + const defaultScheduleId = 123; + + (prisma.user.findUnique as any).mockResolvedValue({ + defaultScheduleId, + }); + + const result = await getDefaultScheduleId(userId, prisma); + + expect(prisma.user.findUnique).toHaveBeenCalledWith({ + where: { id: userId }, + select: { defaultScheduleId: true }, + }); + expect(result).toBe(defaultScheduleId); + }); + + it("should find and return first schedule if user has no defaultScheduleId", async () => { + const userId = 1; + const scheduleId = 456; + + (prisma.user.findUnique as any).mockResolvedValue({ + defaultScheduleId: null, + }); + + (prisma.schedule.findFirst as any).mockResolvedValue({ + id: scheduleId, + }); + + const result = await getDefaultScheduleId(userId, prisma); + + expect(prisma.user.findUnique).toHaveBeenCalledWith({ + where: { id: userId }, + select: { defaultScheduleId: true }, + }); + expect(prisma.schedule.findFirst).toHaveBeenCalledWith({ + where: { userId }, + select: { id: true }, + }); + expect(result).toBe(scheduleId); + }); + + it("should throw error if no schedules found", async () => { + const userId = 1; + + (prisma.user.findUnique as any).mockResolvedValue({ + defaultScheduleId: null, + }); + + (prisma.schedule.findFirst as any).mockResolvedValue(null); + + await expect(getDefaultScheduleId(userId, prisma)).rejects.toThrow("No schedules found for user"); + }); + }); + + describe("hasDefaultSchedule", () => { + it("should return true if user has defaultScheduleId", async () => { + const user = { id: 1, defaultScheduleId: 123 }; + + const result = await hasDefaultSchedule(user, prisma); + + expect(result).toBe(true); + }); + + it("should return true if user has a schedule", async () => { + const user = { id: 1, defaultScheduleId: null }; + + (prisma.schedule.findFirst as any).mockResolvedValue({ + id: 456, + }); + + const result = await hasDefaultSchedule(user, prisma); + + expect(prisma.schedule.findFirst).toHaveBeenCalledWith({ + where: { userId: user.id }, + }); + expect(result).toBe(true); + }); + + it("should return false if user has no defaultScheduleId and no schedules", async () => { + const user = { id: 1, defaultScheduleId: null }; + + (prisma.schedule.findFirst as any).mockResolvedValue(null); + + const result = await hasDefaultSchedule(user, prisma); + + expect(prisma.schedule.findFirst).toHaveBeenCalledWith({ + where: { userId: user.id }, + }); + expect(result).toBe(false); + }); + }); + + describe("setupDefaultSchedule", () => { + it("should update user with new defaultScheduleId", async () => { + const userId = 1; + const scheduleId = 123; + const updatedUser = { id: userId, defaultScheduleId: scheduleId }; + + (prisma.user.update as any).mockResolvedValue(updatedUser); + + const result = await setupDefaultSchedule(userId, scheduleId, prisma); + + expect(prisma.user.update).toHaveBeenCalledWith({ + where: { id: userId }, + data: { defaultScheduleId: scheduleId }, + }); + expect(result).toEqual(updatedUser); + }); + }); + + describe("updateHostsWithNewDefaultSchedule", () => { + it("should update hosts with new scheduleId", async () => { + const userId = 1; + const oldScheduleId = 123; + const newScheduleId = 456; + const updateResult = { count: 2 }; // 2 hosts updated + + (prisma.host.updateMany as any).mockResolvedValue(updateResult); + + const result = await updateHostsWithNewDefaultSchedule(userId, oldScheduleId, newScheduleId, prisma); + + expect(prisma.host.updateMany).toHaveBeenCalledWith({ + where: { + userId, + scheduleId: oldScheduleId, + }, + data: { + scheduleId: newScheduleId, + }, + }); + expect(result).toEqual(updateResult); + }); + + it("should handle case with no hosts to update", async () => { + const userId = 1; + const oldScheduleId = 123; + const newScheduleId = 456; + const updateResult = { count: 0 }; // No hosts updated + + (prisma.host.updateMany as any).mockResolvedValue(updateResult); + + const result = await updateHostsWithNewDefaultSchedule(userId, oldScheduleId, newScheduleId, prisma); + + expect(prisma.host.updateMany).toHaveBeenCalledWith({ + where: { + userId, + scheduleId: oldScheduleId, + }, + data: { + scheduleId: newScheduleId, + }, + }); + expect(result).toEqual(updateResult); + }); + }); +}); diff --git a/packages/trpc/server/routers/viewer/availability/util.ts b/packages/trpc/server/routers/viewer/availability/util.ts index 66b0775389..e271da140d 100644 --- a/packages/trpc/server/routers/viewer/availability/util.ts +++ b/packages/trpc/server/routers/viewer/availability/util.ts @@ -1,5 +1,6 @@ import type { User } from "@prisma/client"; +import { HostRepository } from "@calcom/lib/server/repository/host"; import type { PrismaClient } from "@calcom/prisma"; export const getDefaultScheduleId = async (userId: number, prisma: PrismaClient) => { @@ -53,3 +54,11 @@ export const setupDefaultSchedule = async (userId: number, scheduleId: number, p }, }); }; + +export const updateHostsWithNewDefaultSchedule = async ( + userId: number, + defaultScheduleId: number, + scheduleId: number +) => { + return await HostRepository.updateHostsSchedule(userId, defaultScheduleId, scheduleId); +};