Skip to content

Commit 8b65261

Browse files
fix: break circular dependency between reminderScheduler and credit-service (#25312)
* fix: break circular dependency between reminderScheduler and credit-service - Created WorkflowReminderRepository to handle workflow reminder queries - Refactored cancelScheduledMessagesAndScheduleEmails to accept userIdsWithoutCredits parameter - Moved credit-checking logic from workflows to CreditService - Changed dynamic import to static import in CreditService - Updated tests to pass userIdsWithoutCredits parameter - Removed unused imports (prisma, WorkflowMethods) This creates a one-way dependency (billing -> workflows) and follows the repository pattern by removing direct Prisma usage from reminderScheduler. Co-Authored-By: [email protected] <[email protected]> * fix: resolve type check errors in circular dependency fix - Use MembershipRepository.listAcceptedTeamMemberIds instead of findAllAcceptedPublishedTeamMemberships - Re-add prisma import to reminderScheduler.ts for UserRepository - Update WorkflowReminderRepository to use WorkflowActions enum instead of string Co-Authored-By: [email protected] <[email protected]> * refactor * refactor * wip * wip --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1 parent 8e1a099 commit 8b65261

File tree

4 files changed

+134
-100
lines changed

4 files changed

+134
-100
lines changed

packages/features/ee/billing/credit-service.ts

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -605,11 +605,15 @@ export class CreditService {
605605
"@calcom/features/ee/workflows/lib/reminders/reminderScheduler"
606606
);
607607
promises.push(
608-
cancelScheduledMessagesAndScheduleEmails({ teamId: result.teamId, userId: result.userId }).catch(
609-
(error) => {
610-
log.error("Failed to cancel scheduled messages", error, { result });
611-
}
612-
)
608+
cancelScheduledMessagesAndScheduleEmails({
609+
teamId: result.teamId,
610+
userIdsWithNoCredits: await this._getUserIdsWithoutCredits({
611+
teamId: result.teamId ?? null,
612+
userId: result.userId ?? null,
613+
}),
614+
}).catch((error) => {
615+
log.error("Failed to cancel scheduled messages", error, { result });
616+
})
613617
);
614618
}
615619

@@ -826,4 +830,34 @@ export class CreditService {
826830
};
827831
});
828832
}
833+
834+
private async _getUserIdsWithoutCredits({
835+
teamId,
836+
userId,
837+
}: {
838+
teamId: number | null;
839+
userId: number | null;
840+
}) {
841+
let userIdsWithNoCredits: number[] = userId ? [userId] : [];
842+
if (teamId) {
843+
const teamMembers = await prisma.membership.findMany({
844+
where: {
845+
teamId,
846+
accepted: true,
847+
},
848+
});
849+
850+
userIdsWithNoCredits = (
851+
await Promise.all(
852+
teamMembers.map(async (member) => {
853+
const hasCredits = await this.hasAvailableCredits({ userId: member.userId });
854+
return { userId: member.userId, hasCredits };
855+
})
856+
)
857+
)
858+
.filter(({ hasCredits }) => !hasCredits)
859+
.map(({ userId }) => userId);
860+
}
861+
return userIdsWithNoCredits;
862+
}
829863
}

packages/features/ee/workflows/lib/reminders/reminderScheduler.test.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,6 @@ describe("reminderScheduler", () => {
3434

3535
describe("cancelScheduledMessagesAndScheduleEmails", () => {
3636
it("should cancel SMS messages and schedule emails for team", async () => {
37-
prismaMock.membership.findMany.mockResolvedValue([]);
38-
3937
const mockScheduledMessages = [
4038
{
4139
id: 1,
@@ -63,7 +61,7 @@ describe("reminderScheduler", () => {
6361

6462
prismaMock.workflowReminder.updateMany.mockResolvedValue({ count: 1 });
6563

66-
await cancelScheduledMessagesAndScheduleEmails({ teamId: 10 });
64+
await cancelScheduledMessagesAndScheduleEmails({ teamId: 10, userIdsWithNoCredits: [1, 2, 3] });
6765

6866
expect(twilioProvider.cancelSMS).toHaveBeenCalledWith("sms-123");
6967

@@ -76,12 +74,13 @@ describe("reminderScheduler", () => {
7674
);
7775

7876
const callArgs = prismaMock.workflowReminder.findMany.mock.calls[0][0];
79-
expect(callArgs.where.workflowStep.workflow.OR).toEqual([{ userId: { in: [] } }, { teamId: 10 }]);
77+
expect(callArgs.where.workflowStep.workflow.OR).toEqual([
78+
{ userId: { in: [1, 2, 3] } },
79+
{ teamId: 10 },
80+
]);
8081
});
8182

8283
it("should cancel SMS messages and schedule emails for user", async () => {
83-
prismaMock.membership.findMany.mockResolvedValue([]);
84-
8584
const mockScheduledMessages = [
8685
{
8786
id: 1,
@@ -109,7 +108,7 @@ describe("reminderScheduler", () => {
109108

110109
prismaMock.workflowReminder.updateMany.mockResolvedValue({ count: 1 });
111110

112-
await cancelScheduledMessagesAndScheduleEmails({ userId: 11 });
111+
await cancelScheduledMessagesAndScheduleEmails({ userIdsWithNoCredits: [11] });
113112

114113
const callArgs = prismaMock.workflowReminder.findMany.mock.calls[0][0];
115114
expect(callArgs.where.workflowStep.workflow.OR).toEqual([{ userId: { in: [11] } }]);

packages/features/ee/workflows/lib/reminders/reminderScheduler.ts

Lines changed: 12 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@ import * as twilio from "@calcom/features/ee/workflows/lib/reminders/providers/t
1111
import type { Workflow, WorkflowStep } from "@calcom/features/ee/workflows/lib/types";
1212
import { getSubmitterEmail } from "@calcom/features/tasker/tasks/triggerFormSubmittedNoEvent/formSubmissionValidation";
1313
import { UserRepository } from "@calcom/features/users/repositories/UserRepository";
14-
import { checkSMSRateLimit } from "@calcom/lib/smsLockState";
1514
import { SENDER_NAME } from "@calcom/lib/constants";
1615
import { formatCalEventExtended } from "@calcom/lib/formatCalendarEvent";
1716
import { withReporting } from "@calcom/lib/sentryWrapper";
1817
import { getTranslation } from "@calcom/lib/server/i18n";
18+
import { checkSMSRateLimit } from "@calcom/lib/smsLockState";
1919
import prisma from "@calcom/prisma";
2020
import { SchedulingType } from "@calcom/prisma/enums";
21-
import { WorkflowActions, WorkflowMethods, WorkflowTriggerEvents } from "@calcom/prisma/enums";
21+
import { WorkflowActions, WorkflowTriggerEvents } from "@calcom/prisma/enums";
2222
import type { CalendarEvent } from "@calcom/types/Calendar";
2323

2424
import { scheduleAIPhoneCall } from "./aiPhoneCallManager";
@@ -288,86 +288,18 @@ const _sendCancelledReminders = async (args: SendCancelledRemindersArgs) => {
288288

289289
const _cancelScheduledMessagesAndScheduleEmails = async ({
290290
teamId,
291-
userId,
291+
userIdsWithNoCredits,
292292
}: {
293293
teamId?: number | null;
294-
userId?: number | null;
294+
userIdsWithNoCredits: number[];
295295
}) => {
296-
const { CreditService } = await import("@calcom/features/ee/billing/credit-service");
297-
298-
let userIdsWithNoCredits: number[] = userId ? [userId] : [];
299-
300-
if (teamId) {
301-
const teamMembers = await prisma.membership.findMany({
302-
where: {
303-
teamId,
304-
accepted: true,
305-
},
306-
});
307-
308-
const creditService = new CreditService();
309-
310-
userIdsWithNoCredits = (
311-
await Promise.all(
312-
teamMembers.map(async (member) => {
313-
const hasCredits = await creditService.hasAvailableCredits({ userId: member.userId });
314-
return { userId: member.userId, hasCredits };
315-
})
316-
)
317-
)
318-
.filter(({ hasCredits }) => !hasCredits)
319-
.map(({ userId }) => userId);
320-
}
296+
const { WorkflowReminderRepository } = await import(
297+
"@calcom/features/ee/workflows/repositories/WorkflowReminderRepository"
298+
);
321299

322-
const scheduledMessages = await prisma.workflowReminder.findMany({
323-
where: {
324-
workflowStep: {
325-
workflow: {
326-
OR: [
327-
{
328-
userId: {
329-
in: userIdsWithNoCredits,
330-
},
331-
},
332-
...(teamId ? [{ teamId }] : []),
333-
],
334-
},
335-
},
336-
scheduled: true,
337-
OR: [{ cancelled: false }, { cancelled: null }],
338-
referenceId: {
339-
not: null,
340-
},
341-
method: {
342-
in: [WorkflowMethods.SMS, WorkflowMethods.WHATSAPP],
343-
},
344-
},
345-
select: {
346-
referenceId: true,
347-
workflowStep: {
348-
select: {
349-
action: true,
350-
},
351-
},
352-
scheduledDate: true,
353-
uuid: true,
354-
id: true,
355-
booking: {
356-
select: {
357-
attendees: {
358-
select: {
359-
email: true,
360-
locale: true,
361-
},
362-
},
363-
user: {
364-
select: {
365-
email: true,
366-
},
367-
},
368-
},
369-
},
370-
},
300+
const scheduledMessages = await WorkflowReminderRepository.findScheduledMessagesToCancel({
301+
teamId,
302+
userIdsWithNoCredits,
371303
});
372304

373305
await Promise.allSettled(scheduledMessages.map((msg) => twilio.cancelSMS(msg.referenceId ?? "")));
@@ -393,16 +325,8 @@ const _cancelScheduledMessagesAndScheduleEmails = async ({
393325
})
394326
);
395327

396-
await prisma.workflowReminder.updateMany({
397-
where: {
398-
id: {
399-
in: scheduledMessages.map((msg) => msg.id),
400-
},
401-
},
402-
data: {
403-
method: WorkflowMethods.EMAIL,
404-
referenceId: null,
405-
},
328+
await WorkflowReminderRepository.updateRemindersToEmail({
329+
reminderIds: scheduledMessages.map((msg) => msg.id),
406330
});
407331
};
408332
// Export functions wrapped with withReporting
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { prisma } from "@calcom/prisma";
2+
import { WorkflowMethods } from "@calcom/prisma/enums";
3+
4+
export class WorkflowReminderRepository {
5+
static async findScheduledMessagesToCancel({
6+
teamId,
7+
userIdsWithNoCredits,
8+
}: {
9+
teamId?: number | null;
10+
userIdsWithNoCredits: number[];
11+
}) {
12+
return await prisma.workflowReminder.findMany({
13+
where: {
14+
workflowStep: {
15+
workflow: {
16+
OR: [
17+
{
18+
userId: {
19+
in: userIdsWithNoCredits,
20+
},
21+
},
22+
...(teamId ? [{ teamId }] : []),
23+
],
24+
},
25+
},
26+
scheduled: true,
27+
OR: [{ cancelled: false }, { cancelled: null }],
28+
referenceId: {
29+
not: null,
30+
},
31+
method: {
32+
in: [WorkflowMethods.SMS, WorkflowMethods.WHATSAPP],
33+
},
34+
},
35+
select: {
36+
referenceId: true,
37+
workflowStep: {
38+
select: {
39+
action: true,
40+
},
41+
},
42+
scheduledDate: true,
43+
uuid: true,
44+
id: true,
45+
booking: {
46+
select: {
47+
attendees: {
48+
select: {
49+
email: true,
50+
locale: true,
51+
},
52+
},
53+
user: {
54+
select: {
55+
email: true,
56+
},
57+
},
58+
},
59+
},
60+
},
61+
});
62+
}
63+
64+
static async updateRemindersToEmail({ reminderIds }: { reminderIds: number[] }): Promise<void> {
65+
await prisma.workflowReminder.updateMany({
66+
where: {
67+
id: {
68+
in: reminderIds,
69+
},
70+
},
71+
data: {
72+
method: WorkflowMethods.EMAIL,
73+
referenceId: null,
74+
},
75+
});
76+
}
77+
}

0 commit comments

Comments
 (0)