Skip to content

fix: Host schedule not updating upon updating/deleting default schedule #20750

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
May 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions packages/lib/server/repository/host.ts
Original file line number Diff line number Diff line change
@@ -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,
},
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<TrpcSessionUser>;
Expand Down Expand Up @@ -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;
}
Expand Down
197 changes: 197 additions & 0 deletions packages/trpc/server/routers/viewer/availability/util.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
});
9 changes: 9 additions & 0 deletions packages/trpc/server/routers/viewer/availability/util.ts
Original file line number Diff line number Diff line change
@@ -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) => {
Expand Down Expand Up @@ -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);
};
Loading