Skip to content

Commit ef18445

Browse files
authored
fix: Host schedule not updating upon updating/deleting default schedule (calcom#20750)
* fix: host schdule not updating * Update util.ts * tweak * Update delete.handler.ts * add test * update * Update util.test.ts
1 parent d1f7b04 commit ef18445

5 files changed

Lines changed: 232 additions & 1 deletion

File tree

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import prisma from "@calcom/prisma";
2+
3+
export class HostRepository {
4+
static async updateHostsSchedule(userId: number, oldScheduleId: number, newScheduleId: number) {
5+
return await prisma.host.updateMany({
6+
where: {
7+
userId,
8+
scheduleId: oldScheduleId,
9+
},
10+
data: {
11+
scheduleId: newScheduleId,
12+
},
13+
});
14+
}
15+
}

packages/trpc/server/routers/viewer/availability/schedule/delete.handler.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { prisma } from "@calcom/prisma";
33
import { TRPCError } from "@trpc/server";
44

55
import type { TrpcSessionUser } from "../../../../types";
6+
import { updateHostsWithNewDefaultSchedule } from "../util";
67
import type { TDeleteInputSchema } from "./delete.schema";
78

89
type DeleteOptions = {
@@ -45,6 +46,8 @@ export const deleteHandler = async ({ input, ctx }: DeleteOptions) => {
4546
// to throw the error if there arent any other schedules
4647
if (!scheduleToSetAsDefault) throw new TRPCError({ code: "BAD_REQUEST" });
4748

49+
await updateHostsWithNewDefaultSchedule(user.id, input.scheduleId, scheduleToSetAsDefault.id);
50+
4851
await prisma.user.update({
4952
where: {
5053
id: user.id,
@@ -53,7 +56,10 @@ export const deleteHandler = async ({ input, ctx }: DeleteOptions) => {
5356
defaultScheduleId: scheduleToSetAsDefault?.id || null,
5457
},
5558
});
59+
} else if (user.defaultScheduleId) {
60+
await updateHostsWithNewDefaultSchedule(user.id, input.scheduleId, user.defaultScheduleId);
5661
}
62+
5763
await prisma.schedule.delete({
5864
where: {
5965
id: input.scheduleId,

packages/trpc/server/routers/viewer/availability/schedule/update.handler.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { prisma } from "@calcom/prisma";
66
import { TRPCError } from "@trpc/server";
77

88
import type { TrpcSessionUser } from "../../../../types";
9-
import { setupDefaultSchedule } from "../util";
9+
import { setupDefaultSchedule, updateHostsWithNewDefaultSchedule } from "../util";
1010
import type { TUpdateInputSchema } from "./update.schema";
1111

1212
type User = NonNullable<TrpcSessionUser>;
@@ -61,6 +61,10 @@ export const updateHandler = async ({ input, ctx }: UpdateOptions) => {
6161

6262
let updatedUser;
6363
if (input.isDefault) {
64+
if (user?.defaultScheduleId) {
65+
await updateHostsWithNewDefaultSchedule(user.id, user.defaultScheduleId, input.scheduleId);
66+
}
67+
6468
const setupDefault = await setupDefaultSchedule(user.id, input.scheduleId, prisma);
6569
updatedUser = setupDefault;
6670
}
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
import { describe, expect, it, vi, beforeEach } from "vitest";
2+
3+
import { PrismaClient } from "@calcom/prisma";
4+
5+
import {
6+
getDefaultScheduleId,
7+
hasDefaultSchedule,
8+
setupDefaultSchedule,
9+
updateHostsWithNewDefaultSchedule,
10+
} from "./util";
11+
12+
vi.mock("@calcom/prisma", () => {
13+
const mockPrisma = {
14+
user: {
15+
findUnique: vi.fn(),
16+
update: vi.fn(),
17+
},
18+
schedule: {
19+
findFirst: vi.fn(),
20+
},
21+
host: {
22+
updateMany: vi.fn(),
23+
},
24+
};
25+
return {
26+
__esModule: true,
27+
default: mockPrisma,
28+
PrismaClient: vi.fn(() => mockPrisma),
29+
};
30+
});
31+
32+
describe("Availability Utils", () => {
33+
let prisma: PrismaClient;
34+
35+
beforeEach(() => {
36+
prisma = new PrismaClient();
37+
vi.clearAllMocks();
38+
});
39+
40+
describe("getDefaultScheduleId", () => {
41+
it("should return defaultScheduleId if user has one", async () => {
42+
const userId = 1;
43+
const defaultScheduleId = 123;
44+
45+
(prisma.user.findUnique as any).mockResolvedValue({
46+
defaultScheduleId,
47+
});
48+
49+
const result = await getDefaultScheduleId(userId, prisma);
50+
51+
expect(prisma.user.findUnique).toHaveBeenCalledWith({
52+
where: { id: userId },
53+
select: { defaultScheduleId: true },
54+
});
55+
expect(result).toBe(defaultScheduleId);
56+
});
57+
58+
it("should find and return first schedule if user has no defaultScheduleId", async () => {
59+
const userId = 1;
60+
const scheduleId = 456;
61+
62+
(prisma.user.findUnique as any).mockResolvedValue({
63+
defaultScheduleId: null,
64+
});
65+
66+
(prisma.schedule.findFirst as any).mockResolvedValue({
67+
id: scheduleId,
68+
});
69+
70+
const result = await getDefaultScheduleId(userId, prisma);
71+
72+
expect(prisma.user.findUnique).toHaveBeenCalledWith({
73+
where: { id: userId },
74+
select: { defaultScheduleId: true },
75+
});
76+
expect(prisma.schedule.findFirst).toHaveBeenCalledWith({
77+
where: { userId },
78+
select: { id: true },
79+
});
80+
expect(result).toBe(scheduleId);
81+
});
82+
83+
it("should throw error if no schedules found", async () => {
84+
const userId = 1;
85+
86+
(prisma.user.findUnique as any).mockResolvedValue({
87+
defaultScheduleId: null,
88+
});
89+
90+
(prisma.schedule.findFirst as any).mockResolvedValue(null);
91+
92+
await expect(getDefaultScheduleId(userId, prisma)).rejects.toThrow("No schedules found for user");
93+
});
94+
});
95+
96+
describe("hasDefaultSchedule", () => {
97+
it("should return true if user has defaultScheduleId", async () => {
98+
const user = { id: 1, defaultScheduleId: 123 };
99+
100+
const result = await hasDefaultSchedule(user, prisma);
101+
102+
expect(result).toBe(true);
103+
});
104+
105+
it("should return true if user has a schedule", async () => {
106+
const user = { id: 1, defaultScheduleId: null };
107+
108+
(prisma.schedule.findFirst as any).mockResolvedValue({
109+
id: 456,
110+
});
111+
112+
const result = await hasDefaultSchedule(user, prisma);
113+
114+
expect(prisma.schedule.findFirst).toHaveBeenCalledWith({
115+
where: { userId: user.id },
116+
});
117+
expect(result).toBe(true);
118+
});
119+
120+
it("should return false if user has no defaultScheduleId and no schedules", async () => {
121+
const user = { id: 1, defaultScheduleId: null };
122+
123+
(prisma.schedule.findFirst as any).mockResolvedValue(null);
124+
125+
const result = await hasDefaultSchedule(user, prisma);
126+
127+
expect(prisma.schedule.findFirst).toHaveBeenCalledWith({
128+
where: { userId: user.id },
129+
});
130+
expect(result).toBe(false);
131+
});
132+
});
133+
134+
describe("setupDefaultSchedule", () => {
135+
it("should update user with new defaultScheduleId", async () => {
136+
const userId = 1;
137+
const scheduleId = 123;
138+
const updatedUser = { id: userId, defaultScheduleId: scheduleId };
139+
140+
(prisma.user.update as any).mockResolvedValue(updatedUser);
141+
142+
const result = await setupDefaultSchedule(userId, scheduleId, prisma);
143+
144+
expect(prisma.user.update).toHaveBeenCalledWith({
145+
where: { id: userId },
146+
data: { defaultScheduleId: scheduleId },
147+
});
148+
expect(result).toEqual(updatedUser);
149+
});
150+
});
151+
152+
describe("updateHostsWithNewDefaultSchedule", () => {
153+
it("should update hosts with new scheduleId", async () => {
154+
const userId = 1;
155+
const oldScheduleId = 123;
156+
const newScheduleId = 456;
157+
const updateResult = { count: 2 }; // 2 hosts updated
158+
159+
(prisma.host.updateMany as any).mockResolvedValue(updateResult);
160+
161+
const result = await updateHostsWithNewDefaultSchedule(userId, oldScheduleId, newScheduleId, prisma);
162+
163+
expect(prisma.host.updateMany).toHaveBeenCalledWith({
164+
where: {
165+
userId,
166+
scheduleId: oldScheduleId,
167+
},
168+
data: {
169+
scheduleId: newScheduleId,
170+
},
171+
});
172+
expect(result).toEqual(updateResult);
173+
});
174+
175+
it("should handle case with no hosts to update", async () => {
176+
const userId = 1;
177+
const oldScheduleId = 123;
178+
const newScheduleId = 456;
179+
const updateResult = { count: 0 }; // No hosts updated
180+
181+
(prisma.host.updateMany as any).mockResolvedValue(updateResult);
182+
183+
const result = await updateHostsWithNewDefaultSchedule(userId, oldScheduleId, newScheduleId, prisma);
184+
185+
expect(prisma.host.updateMany).toHaveBeenCalledWith({
186+
where: {
187+
userId,
188+
scheduleId: oldScheduleId,
189+
},
190+
data: {
191+
scheduleId: newScheduleId,
192+
},
193+
});
194+
expect(result).toEqual(updateResult);
195+
});
196+
});
197+
});

packages/trpc/server/routers/viewer/availability/util.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { User } from "@prisma/client";
22

3+
import { HostRepository } from "@calcom/lib/server/repository/host";
34
import type { PrismaClient } from "@calcom/prisma";
45

56
export const getDefaultScheduleId = async (userId: number, prisma: PrismaClient) => {
@@ -53,3 +54,11 @@ export const setupDefaultSchedule = async (userId: number, scheduleId: number, p
5354
},
5455
});
5556
};
57+
58+
export const updateHostsWithNewDefaultSchedule = async (
59+
userId: number,
60+
defaultScheduleId: number,
61+
scheduleId: number
62+
) => {
63+
return await HostRepository.updateHostsSchedule(userId, defaultScheduleId, scheduleId);
64+
};

0 commit comments

Comments
 (0)