diff --git a/packages/app-store/routing-forms/trpc/onFormSubmission.test.ts b/packages/app-store/routing-forms/trpc/onFormSubmission.test.ts index 7d02313e4a..f9a3404803 100644 --- a/packages/app-store/routing-forms/trpc/onFormSubmission.test.ts +++ b/packages/app-store/routing-forms/trpc/onFormSubmission.test.ts @@ -102,7 +102,11 @@ describe("_onFormSubmission", () => { "triggerFormSubmittedNoEventWebhook", { responseId, - form: mockForm, + form: { + id: mockForm.id, + name: mockForm.name, + teamId: mockForm.teamId, + }, responses: { email: { value: "test@response.com", diff --git a/packages/app-store/routing-forms/trpc/utils.ts b/packages/app-store/routing-forms/trpc/utils.ts index 828efd6e75..0757660de2 100644 --- a/packages/app-store/routing-forms/trpc/utils.ts +++ b/packages/app-store/routing-forms/trpc/utils.ts @@ -170,7 +170,11 @@ export async function _onFormSubmission( "triggerFormSubmittedNoEventWebhook", { responseId, - form, + form: { + id: form.id, + name: form.name, + teamId: form.teamId ?? null, + }, responses: fieldResponsesByIdentifier, redirect: chosenAction, webhook, diff --git a/packages/features/ee/workflows/lib/test/workflows.test.ts b/packages/features/ee/workflows/lib/test/workflows.test.ts index 9f9a43dff9..612eec1fe8 100644 --- a/packages/features/ee/workflows/lib/test/workflows.test.ts +++ b/packages/features/ee/workflows/lib/test/workflows.test.ts @@ -13,6 +13,8 @@ import { } from "@calcom/web/test/utils/bookingScenario/expects"; import { setupAndTeardown } from "@calcom/web/test/utils/bookingScenario/setupAndTeardown"; +import type { Prisma } from "@prisma/client"; +import { v4 as uuidv4 } from "uuid"; import { describe, expect, beforeAll, vi, beforeEach } from "vitest"; import dayjs from "@calcom/dayjs"; @@ -57,7 +59,7 @@ const workflowSelect = { steps: true, activeOn: true, activeOnTeams: true, -}; +} satisfies Prisma.WorkflowSelect; beforeAll(() => { vi.setSystemTime(new Date("2024-05-20T11:59:59Z")); @@ -149,21 +151,25 @@ async function createWorkflowRemindersAndTasksForWorkflow(workflowName: string) sender: true, numberVerificationPending: true, includeCalendarEvent: true, + verifiedAt: true, }, }, }, }); - const workflowRemindersData = [ + const workflowRemindersData: Prisma.WorkflowReminderCreateInput[] = [ { booking: { connect: { - bookingUid: "jK7Rf8iYsOpmQUw9hB1vZxP", + uid: "jK7Rf8iYsOpmQUw9hB1vZxP", + }, + }, + uuid: uuidv4(), + workflowStep: { + connect: { + id: workflow?.steps[0]?.id, }, }, - uuid: "uuid-1", - bookingUid: "jK7Rf8iYsOpmQUw9hB1vZxP", - workflowStepId: workflow?.steps[0]?.id, method: WorkflowMethods.EMAIL, scheduledDate: `2024-05-22T06:00:00.000Z`, scheduled: false, @@ -172,12 +178,15 @@ async function createWorkflowRemindersAndTasksForWorkflow(workflowName: string) { booking: { connect: { - bookingUid: "mL4Dx9jTkQbnWEu3pR7yNcF", + uid: "mL4Dx9jTkQbnWEu3pR7yNcF", + }, + }, + uuid: uuidv4(), + workflowStep: { + connect: { + id: workflow?.steps[0]?.id, }, }, - uuid: "uuid-2", - bookingUid: "mL4Dx9jTkQbnWEu3pR7yNcF", - workflowStepId: workflow?.steps[0]?.id, method: WorkflowMethods.EMAIL, scheduledDate: `2024-05-22T06:30:00.000Z`, scheduled: false, @@ -186,13 +195,15 @@ async function createWorkflowRemindersAndTasksForWorkflow(workflowName: string) { booking: { connect: { - bookingUid: "Fd9Rf8iYsOpmQUw9hB1vKd8", + uid: "Fd9Rf8iYsOpmQUw9hB1vKd8", + }, + }, + uuid: uuidv4(), + workflowStep: { + connect: { + id: workflow?.steps[0]?.id, }, }, - uuid: "uuid-3", - - bookingUid: "Fd9Rf8iYsOpmQUw9hB1vKd8", - workflowStepId: workflow?.steps[0]?.id, method: WorkflowMethods.EMAIL, scheduledDate: `2024-05-22T06:30:00.000Z`, scheduled: false, @@ -201,13 +212,15 @@ async function createWorkflowRemindersAndTasksForWorkflow(workflowName: string) { booking: { connect: { - bookingUid: "Kd8Dx9jTkQbnWEu3pR7yKdl", + uid: "Kd8Dx9jTkQbnWEu3pR7yKdl", + }, + }, + uuid: uuidv4(), + workflowStep: { + connect: { + id: workflow?.steps[0]?.id, }, }, - uuid: "uuid-4", - - bookingUid: "Kd8Dx9jTkQbnWEu3pR7yKdl", - workflowStepId: workflow?.steps[0]?.id, method: WorkflowMethods.EMAIL, scheduledDate: `2024-05-22T06:30:00.000Z`, scheduled: false, @@ -679,7 +692,8 @@ describe("scheduleBookingReminders", () => { workflow.timeUnit, workflow.trigger, organizer.id, - null //teamId + null, //teamId, + true ); // number is not verified, so sms should not send @@ -703,7 +717,8 @@ describe("scheduleBookingReminders", () => { workflow.timeUnit, workflow.trigger, organizer.id, - null //teamId + null, //teamId + true ); // two sms should be scheduled @@ -997,8 +1012,10 @@ describe("deleteWorkfowRemindersOfRemovedMember", () => { await prismock.membership.delete({ where: { - userId: 101, - teamId: 2, + userId_teamId: { + teamId: 2, // removing from team 2 + userId: 101, // organizer's userId + }, }, }); diff --git a/packages/features/tasker/repository.ts b/packages/features/tasker/repository.ts index f7e4fd4948..b07efbb09f 100644 --- a/packages/features/tasker/repository.ts +++ b/packages/features/tasker/repository.ts @@ -1,4 +1,4 @@ -import { type Prisma } from "@prisma/client"; +import { Prisma } from "@prisma/client"; import db from "@calcom/prisma"; @@ -68,10 +68,6 @@ export class Task { }); } - static async getAll() { - return db.task.findMany(); - } - static async getFailed() { return db.task.findMany({ where: whereMaxAttemptsReached, @@ -155,24 +151,28 @@ export class Task { }); } - static async cancelWithReference(referenceUid: string, type: TaskTypes) { - const task = await db.task.findFirst({ - where: { - referenceUid, - type, - }, - select: { - id: true, - }, - }); - - if (!task) return null; - - return await db.task.delete({ - where: { - id: task.id, - }, - }); + static async cancelWithReference(referenceUid: string, type: TaskTypes): Promise<{ id: string } | null> { + // db.task.delete throws an error if the task does not exist, so we catch it and return null + try { + return await db.task.delete({ + where: { + referenceUid_type: { + referenceUid, + type, + }, + }, + select: { + id: true, + }, + }); + } catch (error) { + if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === "P2025") { + // P2025 is the error code for "Record to delete does not exist" + console.warn(`Task with reference ${referenceUid} and type ${type} does not exist. No action taken.`); + return null; + } + throw error; + } } static async cleanup() { diff --git a/packages/features/tasker/tasks/triggerFormSubmittedNoEvent/triggerFormSubmittedNoEventWebhook.ts b/packages/features/tasker/tasks/triggerFormSubmittedNoEvent/triggerFormSubmittedNoEventWebhook.ts index aea05147d7..324ddc46bb 100644 --- a/packages/features/tasker/tasks/triggerFormSubmittedNoEvent/triggerFormSubmittedNoEventWebhook.ts +++ b/packages/features/tasker/tasks/triggerFormSubmittedNoEvent/triggerFormSubmittedNoEventWebhook.ts @@ -34,10 +34,6 @@ export const ZTriggerFormSubmittedNoEventWebhookPayloadSchema = z.object({ id: z.string(), name: z.string(), teamId: z.number().nullable(), - fields: z - .array(z.object({ id: z.string(), label: z.string() }).passthrough()) - .nullable() - .default([]), }), }); diff --git a/packages/prisma/migrations/20250710172011_add_indexes_and_uniq_index_to_task_table/migration.sql b/packages/prisma/migrations/20250710172011_add_indexes_and_uniq_index_to_task_table/migration.sql new file mode 100644 index 0000000000..322d693e3c --- /dev/null +++ b/packages/prisma/migrations/20250710172011_add_indexes_and_uniq_index_to_task_table/migration.sql @@ -0,0 +1,14 @@ +/* + Warnings: + + - A unique constraint covering the columns `[referenceUid,type]` on the table `Task` will be added. If there are existing duplicate values, this will fail. + +*/ +-- CreateIndex +CREATE INDEX "Task_succeededAt_idx" ON "Task"("succeededAt"); + +-- CreateIndex +CREATE INDEX "Task_scheduledAt_succeededAt_idx" ON "Task"("scheduledAt", "succeededAt"); + +-- CreateIndex +CREATE UNIQUE INDEX "Task_referenceUid_type_key" ON "Task"("referenceUid", "type"); diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma index 1e15a62f82..17fafe7e4c 100644 --- a/packages/prisma/schema.prisma +++ b/packages/prisma/schema.prisma @@ -1924,6 +1924,13 @@ model Task { lastError String? lastFailedAttemptAt DateTime? referenceUid String? + + // unique index on referenceUid,type to avoid duplicate tasks + @@unique([referenceUid, type]) + // for finding succeeded tasks + @@index([succeededAt]) + // for finding tasks that are scheduled to be executed + @@index([scheduledAt, succeededAt]) } enum SMSLockState {