chore: Watchlist schema update (#24246)
* watchlist schema update * changes addressed * no backfill audit * fix type * fix flow of migration * fix err * type err fix * add if exists check in migration
This commit is contained in:
@@ -1,9 +1,8 @@
|
||||
import prismock from "../../../../../../tests/libs/__mocks__/prisma";
|
||||
|
||||
import type { NextApiRequest } from "next";
|
||||
import { describe, expect, it, beforeEach } from "vitest";
|
||||
|
||||
import { WatchlistSeverity } from "@calcom/prisma/enums";
|
||||
|
||||
import { isLockedOrBlocked } from "../../../lib/utils/isLockedOrBlocked";
|
||||
|
||||
describe("isLockedOrBlocked", () => {
|
||||
@@ -18,7 +17,6 @@ describe("isLockedOrBlocked", () => {
|
||||
{
|
||||
type: "DOMAIN",
|
||||
value: "blocked.com",
|
||||
severity: WatchlistSeverity.CRITICAL,
|
||||
createdById: 1,
|
||||
},
|
||||
],
|
||||
@@ -26,13 +24,13 @@ describe("isLockedOrBlocked", () => {
|
||||
});
|
||||
|
||||
it("should return false if no user in request", async () => {
|
||||
const req = { userId: null, user: null } as any;
|
||||
const req = { userId: null, user: null } as unknown as NextApiRequest;
|
||||
const result = await isLockedOrBlocked(req);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false if user has no email", async () => {
|
||||
const req = { userId: 123, user: { email: null } } as any;
|
||||
const req = { userId: 123, user: { email: null } } as unknown as NextApiRequest;
|
||||
const result = await isLockedOrBlocked(req);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
@@ -44,7 +42,7 @@ describe("isLockedOrBlocked", () => {
|
||||
locked: true,
|
||||
email: "test@example.com",
|
||||
},
|
||||
} as any;
|
||||
} as unknown as NextApiRequest;
|
||||
|
||||
const result = await isLockedOrBlocked(req);
|
||||
expect(result).toBe(true);
|
||||
@@ -57,7 +55,7 @@ describe("isLockedOrBlocked", () => {
|
||||
locked: false,
|
||||
email: "test@blocked.com",
|
||||
},
|
||||
} as any;
|
||||
} as unknown as NextApiRequest;
|
||||
|
||||
const result = await isLockedOrBlocked(req);
|
||||
expect(result).toBe(true);
|
||||
@@ -70,7 +68,7 @@ describe("isLockedOrBlocked", () => {
|
||||
locked: false,
|
||||
email: "test@example.com",
|
||||
},
|
||||
} as any;
|
||||
} as unknown as NextApiRequest;
|
||||
|
||||
const result = await isLockedOrBlocked(req);
|
||||
expect(result).toBe(false);
|
||||
@@ -83,7 +81,7 @@ describe("isLockedOrBlocked", () => {
|
||||
locked: false,
|
||||
email: "test@BLOCKED.COM",
|
||||
},
|
||||
} as any;
|
||||
} as unknown as NextApiRequest;
|
||||
|
||||
const result = await isLockedOrBlocked(req);
|
||||
expect(result).toBe(true);
|
||||
|
||||
@@ -59,7 +59,7 @@ import { createWatchlistEntry } from "@calcom/features/watchlist/lib/testUtils";
|
||||
import { WEBSITE_URL, WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { ErrorCode } from "@calcom/lib/errorCodes";
|
||||
import { resetTestEmails } from "@calcom/lib/testEmails";
|
||||
import { CreationSource, WatchlistSeverity, WatchlistType } from "@calcom/prisma/enums";
|
||||
import { CreationSource, WatchlistType } from "@calcom/prisma/enums";
|
||||
import { BookingStatus, SchedulingType } from "@calcom/prisma/enums";
|
||||
import { test } from "@calcom/web/test/fixtures/fixtures";
|
||||
|
||||
@@ -199,7 +199,7 @@ describe("handleNewBooking", () => {
|
||||
|
||||
await expectBookingToBeInDatabase({
|
||||
description: "",
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
|
||||
uid: createdBooking.uid!,
|
||||
eventTypeId: mockBookingData.eventTypeId,
|
||||
status: BookingStatus.ACCEPTED,
|
||||
@@ -263,6 +263,7 @@ describe("handleNewBooking", () => {
|
||||
3. Should fallback to creating the booking in the first connected Calendar when neither event nor organizer has a destination calendar - This doesn't practically happen because organizer is always required to have a schedule set
|
||||
3. Should trigger BOOKING_CREATED webhook
|
||||
`,
|
||||
|
||||
async ({ emails }) => {
|
||||
const handleNewBooking = getNewBookingHandler();
|
||||
const booker = getBooker({
|
||||
@@ -363,7 +364,7 @@ describe("handleNewBooking", () => {
|
||||
|
||||
await expectBookingToBeInDatabase({
|
||||
description: "",
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
|
||||
uid: createdBooking.uid!,
|
||||
eventTypeId: mockBookingData.eventTypeId,
|
||||
status: BookingStatus.ACCEPTED,
|
||||
@@ -423,6 +424,7 @@ describe("handleNewBooking", () => {
|
||||
3. Should fallback to create a booking in the Organizer Calendar if event doesn't have destination calendar
|
||||
3. Should trigger BOOKING_CREATED webhook
|
||||
`,
|
||||
|
||||
async ({ emails }) => {
|
||||
const handleNewBooking = getNewBookingHandler();
|
||||
const booker = getBooker({
|
||||
@@ -525,7 +527,7 @@ describe("handleNewBooking", () => {
|
||||
|
||||
await expectBookingToBeInDatabase({
|
||||
description: "",
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
|
||||
uid: createdBooking.uid!,
|
||||
eventTypeId: mockBookingData.eventTypeId,
|
||||
status: BookingStatus.ACCEPTED,
|
||||
@@ -578,6 +580,7 @@ describe("handleNewBooking", () => {
|
||||
|
||||
test(
|
||||
`an error in creating a calendar event should not stop the booking creation - Current behaviour is wrong as the booking is created but no-one is notified of it`,
|
||||
|
||||
async ({ emails }) => {
|
||||
const handleNewBooking = getNewBookingHandler();
|
||||
const booker = getBooker({
|
||||
@@ -666,7 +669,7 @@ describe("handleNewBooking", () => {
|
||||
|
||||
await expectBookingToBeInDatabase({
|
||||
description: "",
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
|
||||
uid: createdBooking.uid!,
|
||||
eventTypeId: mockBookingData.eventTypeId,
|
||||
status: BookingStatus.ACCEPTED,
|
||||
@@ -699,6 +702,7 @@ describe("handleNewBooking", () => {
|
||||
|
||||
test(
|
||||
"If destination calendar has no credential ID due to some reason, it should create the event in first connected calendar instead",
|
||||
|
||||
async ({ emails }) => {
|
||||
const handleNewBooking = getNewBookingHandler();
|
||||
const booker = getBooker({
|
||||
@@ -811,7 +815,7 @@ describe("handleNewBooking", () => {
|
||||
|
||||
await expectBookingToBeInDatabase({
|
||||
description: "",
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
|
||||
uid: createdBooking.uid!,
|
||||
eventTypeId: mockBookingData.eventTypeId,
|
||||
status: BookingStatus.ACCEPTED,
|
||||
@@ -865,6 +869,7 @@ describe("handleNewBooking", () => {
|
||||
|
||||
test(
|
||||
"If destination calendar is there for Google Calendar but there are no Google Calendar credentials but there is an Apple Calendar credential connected, it should create the event in Apple Calendar",
|
||||
|
||||
async ({ emails }) => {
|
||||
const handleNewBooking = getNewBookingHandler();
|
||||
const booker = getBooker({
|
||||
@@ -970,7 +975,7 @@ describe("handleNewBooking", () => {
|
||||
|
||||
await expectBookingToBeInDatabase({
|
||||
description: "",
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
|
||||
uid: createdBooking.uid!,
|
||||
eventTypeId: mockBookingData.eventTypeId,
|
||||
status: BookingStatus.ACCEPTED,
|
||||
@@ -1022,6 +1027,7 @@ describe("handleNewBooking", () => {
|
||||
describe("Event's first location should be used when location is unspecied", () => {
|
||||
test(
|
||||
`should create a successful booking with right location app when event has location option as video client`,
|
||||
|
||||
async ({ emails }) => {
|
||||
const handleNewBooking = getNewBookingHandler();
|
||||
const booker = getBooker({
|
||||
@@ -1115,6 +1121,7 @@ describe("handleNewBooking", () => {
|
||||
`should create a successful booking with right location when event's location is not a conferencing app
|
||||
`,
|
||||
//test with inPerson event type
|
||||
|
||||
async ({ emails }) => {
|
||||
const handleNewBooking = getNewBookingHandler();
|
||||
const booker = getBooker({
|
||||
@@ -1198,6 +1205,7 @@ describe("handleNewBooking", () => {
|
||||
);
|
||||
test(
|
||||
`should create a successful booking with organizer default conferencing app when event's location is not set`,
|
||||
|
||||
async ({ emails }) => {
|
||||
const handleNewBooking = getNewBookingHandler();
|
||||
const booker = getBooker({
|
||||
@@ -1292,6 +1300,7 @@ describe("handleNewBooking", () => {
|
||||
describe("Video Meeting Creation", () => {
|
||||
test(
|
||||
`should create a successful booking with Zoom if used`,
|
||||
|
||||
async ({ emails }) => {
|
||||
const handleNewBooking = getNewBookingHandler();
|
||||
const subscriberUrl = "http://my-webhook.example.com";
|
||||
@@ -1380,6 +1389,7 @@ describe("handleNewBooking", () => {
|
||||
|
||||
test(
|
||||
`Booking should still be created using calvideo, if error occurs with zoom`,
|
||||
|
||||
async ({ emails }) => {
|
||||
const handleNewBooking = getNewBookingHandler();
|
||||
const subscriberUrl = "http://my-webhook.example.com";
|
||||
@@ -1534,6 +1544,7 @@ describe("handleNewBooking", () => {
|
||||
describe("UTM tracking tests", () => {
|
||||
test(
|
||||
"should create a booking with UTM tracking",
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
async ({ emails }) => {
|
||||
const handleNewBooking = getNewBookingHandler();
|
||||
const booker = getBooker({
|
||||
@@ -1609,7 +1620,6 @@ describe("handleNewBooking", () => {
|
||||
bookingData: mockedBookingData,
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
expectBookingTrackingToBeInDatabase(tracking, createdBooking.uid!);
|
||||
},
|
||||
timeout
|
||||
@@ -1619,6 +1629,7 @@ describe("handleNewBooking", () => {
|
||||
describe("Creation source tests", () => {
|
||||
test(
|
||||
`should create a booking with creation source as WEBAPP`,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
async ({ emails }) => {
|
||||
const handleNewBooking = getNewBookingHandler();
|
||||
const booker = getBooker({
|
||||
@@ -1700,7 +1711,7 @@ describe("handleNewBooking", () => {
|
||||
() => {
|
||||
test(
|
||||
`should fail a booking if there is already a Cal.com booking overlapping the time`,
|
||||
async ({}) => {
|
||||
async () => {
|
||||
const handleNewBooking = getNewBookingHandler();
|
||||
|
||||
const booker = getBooker({
|
||||
@@ -1929,7 +1940,7 @@ describe("handleNewBooking", () => {
|
||||
});
|
||||
expectBookingToBeInDatabase({
|
||||
description: "",
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
|
||||
uid: createdBooking.uid!,
|
||||
eventTypeId: mockBookingData.eventTypeId,
|
||||
status: BookingStatus.ACCEPTED,
|
||||
@@ -2098,7 +2109,7 @@ describe("handleNewBooking", () => {
|
||||
});
|
||||
expectBookingToBeInDatabase({
|
||||
description: "",
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
|
||||
uid: createdBooking.uid!,
|
||||
eventTypeId: mockBookingData.eventTypeId,
|
||||
status: BookingStatus.ACCEPTED,
|
||||
@@ -2119,6 +2130,7 @@ describe("handleNewBooking", () => {
|
||||
3. Should trigger BOOKING_REQUESTED webhook
|
||||
4. Should trigger BOOKING_REQUESTED workflow
|
||||
`,
|
||||
|
||||
async ({ emails }) => {
|
||||
const handleNewBooking = getNewBookingHandler();
|
||||
const subscriberUrl = "http://my-webhook.example.com";
|
||||
@@ -2220,7 +2232,7 @@ describe("handleNewBooking", () => {
|
||||
|
||||
await expectBookingToBeInDatabase({
|
||||
description: "",
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
|
||||
uid: createdBooking.uid!,
|
||||
eventTypeId: mockBookingData.eventTypeId,
|
||||
status: BookingStatus.PENDING,
|
||||
@@ -2256,6 +2268,7 @@ describe("handleNewBooking", () => {
|
||||
3. Should trigger BOOKING_REQUESTED webhook
|
||||
4. Should trigger BOOKING_REQUESTED workflow
|
||||
`,
|
||||
|
||||
async ({ emails }) => {
|
||||
const handleNewBooking = getNewBookingHandler();
|
||||
const subscriberUrl = "http://my-webhook.example.com";
|
||||
@@ -2345,7 +2358,7 @@ describe("handleNewBooking", () => {
|
||||
|
||||
await expectBookingToBeInDatabase({
|
||||
description: "",
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
|
||||
uid: createdBooking.uid!,
|
||||
eventTypeId: mockBookingData.eventTypeId,
|
||||
status: BookingStatus.PENDING,
|
||||
@@ -2382,6 +2395,7 @@ describe("handleNewBooking", () => {
|
||||
2. Should send emails to the booker as well as organizer
|
||||
3. Should trigger BOOKING_CREATED webhook
|
||||
`,
|
||||
|
||||
async ({ emails }) => {
|
||||
const handleNewBooking = getNewBookingHandler();
|
||||
const booker = getBooker({
|
||||
@@ -2479,7 +2493,7 @@ describe("handleNewBooking", () => {
|
||||
|
||||
await expectBookingToBeInDatabase({
|
||||
description: "",
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
|
||||
uid: createdBooking.uid!,
|
||||
eventTypeId: mockBookingData.eventTypeId,
|
||||
status: BookingStatus.ACCEPTED,
|
||||
@@ -2518,6 +2532,7 @@ describe("handleNewBooking", () => {
|
||||
3. Should trigger BOOKING_REQUESTED webhook
|
||||
4. Should trigger BOOKING_REQUESTED workflows
|
||||
`,
|
||||
|
||||
async ({ emails }) => {
|
||||
const handleNewBooking = getNewBookingHandler();
|
||||
const subscriberUrl = "http://my-webhook.example.com";
|
||||
@@ -2621,7 +2636,7 @@ describe("handleNewBooking", () => {
|
||||
|
||||
await expectBookingToBeInDatabase({
|
||||
description: "",
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
|
||||
uid: createdBooking.uid!,
|
||||
eventTypeId: mockBookingData.eventTypeId,
|
||||
status: BookingStatus.PENDING,
|
||||
@@ -2648,7 +2663,7 @@ describe("handleNewBooking", () => {
|
||||
// FIXME: We shouldn't throw error here, the behaviour should be fixed.
|
||||
test(
|
||||
`if booking with Cal Video(Daily Video) fails, booking creation fails with uncaught error`,
|
||||
async ({}) => {
|
||||
async () => {
|
||||
const handleNewBooking = getNewBookingHandler();
|
||||
const booker = getBooker({
|
||||
email: "booker@example.org",
|
||||
@@ -2716,6 +2731,7 @@ describe("handleNewBooking", () => {
|
||||
2. Should send emails to the booker as well as organizer
|
||||
3. Should trigger BOOKING_CREATED webhook
|
||||
`,
|
||||
|
||||
async ({ emails }) => {
|
||||
const handleNewBooking = getNewBookingHandler();
|
||||
const booker = getBooker({
|
||||
@@ -2801,7 +2817,7 @@ describe("handleNewBooking", () => {
|
||||
|
||||
await expectBookingToBeInDatabase({
|
||||
description: "",
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
|
||||
uid: createdBooking.uid!,
|
||||
eventTypeId: mockBookingData.eventTypeId,
|
||||
status: BookingStatus.ACCEPTED,
|
||||
@@ -2842,6 +2858,7 @@ describe("handleNewBooking", () => {
|
||||
6. Workflow should not trigger before payment is made
|
||||
7. Workflow triggers once payment is successful
|
||||
`,
|
||||
|
||||
async ({ emails }) => {
|
||||
const handleNewBooking = getNewBookingHandler();
|
||||
const booker = getBooker({
|
||||
@@ -2963,7 +2980,7 @@ describe("handleNewBooking", () => {
|
||||
await expectBookingToBeInDatabase({
|
||||
description: "",
|
||||
location: BookingLocations.CalVideo,
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
|
||||
uid: createdBooking.uid!,
|
||||
eventTypeId: mockBookingData.eventTypeId,
|
||||
status: BookingStatus.PENDING,
|
||||
@@ -2984,7 +3001,7 @@ describe("handleNewBooking", () => {
|
||||
organizer,
|
||||
location: BookingLocations.CalVideo,
|
||||
subscriberUrl: "http://my-webhook.example.com",
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
|
||||
paymentId: createdBooking.paymentId!,
|
||||
});
|
||||
|
||||
@@ -2993,7 +3010,7 @@ describe("handleNewBooking", () => {
|
||||
expect(webhookResponse?.statusCode).toBe(200);
|
||||
await expectBookingToBeInDatabase({
|
||||
description: "",
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
|
||||
uid: createdBooking.uid!,
|
||||
eventTypeId: mockBookingData.eventTypeId,
|
||||
status: BookingStatus.ACCEPTED,
|
||||
@@ -3024,6 +3041,7 @@ describe("handleNewBooking", () => {
|
||||
6. Should trigger BOOKING_REQUESTED workflow
|
||||
7. Booking should still stay in pending state
|
||||
`,
|
||||
|
||||
async ({ emails }) => {
|
||||
const bookingInitiatedEmail = "booking_initiated@workflow.com";
|
||||
const handleNewBooking = getNewBookingHandler();
|
||||
@@ -3145,7 +3163,7 @@ describe("handleNewBooking", () => {
|
||||
);
|
||||
await expectBookingToBeInDatabase({
|
||||
description: "",
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
|
||||
uid: createdBooking.uid!,
|
||||
eventTypeId: mockBookingData.eventTypeId,
|
||||
status: BookingStatus.PENDING,
|
||||
@@ -3165,7 +3183,7 @@ describe("handleNewBooking", () => {
|
||||
organizer,
|
||||
location: BookingLocations.CalVideo,
|
||||
subscriberUrl: "http://my-webhook.example.com",
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
|
||||
paymentId: createdBooking.paymentId!,
|
||||
});
|
||||
expectWorkflowToBeTriggered({ emailsToReceive: [bookingInitiatedEmail], emails });
|
||||
@@ -3178,7 +3196,7 @@ describe("handleNewBooking", () => {
|
||||
expect(webhookResponse?.statusCode).toBe(200);
|
||||
await expectBookingToBeInDatabase({
|
||||
description: "",
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
|
||||
uid: createdBooking.uid!,
|
||||
eventTypeId: mockBookingData.eventTypeId,
|
||||
status: BookingStatus.PENDING,
|
||||
@@ -3203,6 +3221,7 @@ describe("handleNewBooking", () => {
|
||||
);
|
||||
test(
|
||||
`cannot book same slot multiple times `,
|
||||
|
||||
async ({ emails }) => {
|
||||
const handleNewBooking = getNewBookingHandler();
|
||||
const booker = getBooker({
|
||||
@@ -3298,7 +3317,7 @@ describe("handleNewBooking", () => {
|
||||
|
||||
await expectBookingToBeInDatabase({
|
||||
description: "",
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
|
||||
uid: createdBooking.uid!,
|
||||
eventTypeId: mockBookingData.eventTypeId,
|
||||
status: BookingStatus.ACCEPTED,
|
||||
@@ -3351,6 +3370,7 @@ describe("handleNewBooking", () => {
|
||||
|
||||
test(
|
||||
`Payment retry scenario - should return existing payment UID and prevent duplicate bookings when retrying canceled payment`,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
async ({ emails }) => {
|
||||
const handleNewBooking = getNewBookingHandler();
|
||||
const booker = getBooker({
|
||||
@@ -3400,7 +3420,7 @@ describe("handleNewBooking", () => {
|
||||
mockSuccessfulVideoMeetingCreation({
|
||||
metadataLookupKey: "dailyvideo",
|
||||
});
|
||||
const { paymentUid } = mockPaymentApp({
|
||||
const { paymentUid: _paymentUid } = mockPaymentApp({
|
||||
metadataLookupKey: "stripe",
|
||||
appStoreLookupKey: "stripepayment",
|
||||
});
|
||||
@@ -3693,7 +3713,6 @@ describe("handleNewBooking", () => {
|
||||
|
||||
await createWatchlistEntry({
|
||||
type: WatchlistType.USERNAME,
|
||||
severity: WatchlistSeverity.CRITICAL,
|
||||
value: organizer.username,
|
||||
});
|
||||
|
||||
@@ -3755,7 +3774,6 @@ describe("handleNewBooking", () => {
|
||||
|
||||
await createWatchlistEntry({
|
||||
type: WatchlistType.DOMAIN,
|
||||
severity: WatchlistSeverity.CRITICAL,
|
||||
value: "spammer.com",
|
||||
});
|
||||
|
||||
@@ -3817,7 +3835,6 @@ describe("handleNewBooking", () => {
|
||||
|
||||
await createWatchlistEntry({
|
||||
type: WatchlistType.EMAIL,
|
||||
severity: WatchlistSeverity.CRITICAL,
|
||||
value: "spam@spammer.com",
|
||||
});
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import prismock from "../../../../tests/libs/__mocks__/prisma";
|
||||
|
||||
import type { WatchlistSeverity, WatchlistType } from "@calcom/prisma/enums";
|
||||
import type { WatchlistType } from "@calcom/prisma/enums";
|
||||
|
||||
interface WatchlistInput {
|
||||
severity: WatchlistSeverity;
|
||||
type: WatchlistType;
|
||||
value: string;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { captureException } from "@sentry/nextjs";
|
||||
|
||||
import db from "@calcom/prisma";
|
||||
import { WatchlistType, WatchlistSeverity } from "@calcom/prisma/enums";
|
||||
import { WatchlistType } from "@calcom/prisma/enums";
|
||||
|
||||
import type { IWatchlistRepository } from "./watchlist.repository.interface";
|
||||
|
||||
@@ -11,7 +11,6 @@ export class WatchlistRepository implements IWatchlistRepository {
|
||||
try {
|
||||
const emailInWatchlist = await db.watchlist.findFirst({
|
||||
where: {
|
||||
severity: WatchlistSeverity.CRITICAL,
|
||||
OR: [
|
||||
{ type: WatchlistType.EMAIL, value: email },
|
||||
{ type: WatchlistType.DOMAIN, value: domain },
|
||||
@@ -53,7 +52,6 @@ export class WatchlistRepository implements IWatchlistRepository {
|
||||
try {
|
||||
const blockedRecords = await db.watchlist.findMany({
|
||||
where: {
|
||||
severity: WatchlistSeverity.CRITICAL,
|
||||
OR: [
|
||||
...(usernames.length > 0
|
||||
? [
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "WatchlistSource" AS ENUM ('MANUAL', 'FREE_DOMAIN_POLICY');
|
||||
|
||||
-- AlterEnum
|
||||
ALTER TYPE "WatchlistAction" ADD VALUE 'ALERT';
|
||||
|
||||
|
||||
-- 1) Add a temporary UUID column `uid` and backfill it without extensions
|
||||
ALTER TABLE "Watchlist"
|
||||
ADD COLUMN IF NOT EXISTS "uid" UUID;
|
||||
|
||||
-- Backfill existing rows with UUIDs using gen_random_uuid()
|
||||
UPDATE "Watchlist"
|
||||
SET "uid" = COALESCE("uid", gen_random_uuid());
|
||||
|
||||
-- Enforce NOT NULL now that all rows have a value
|
||||
ALTER TABLE "Watchlist"
|
||||
ALTER COLUMN "uid" SET NOT NULL;
|
||||
|
||||
-- 2) Adjust uniques/indexes that reference old id? (none in your snippet)
|
||||
-- If you had FKs or PK references elsewhere, you'd update them here.
|
||||
|
||||
-- 3) Switch primary key from old `id` to new `uid`:
|
||||
-- Drop old PK (name might vary in your DB; adjust if needed)
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "Watchlist" DROP CONSTRAINT "Watchlist_pkey";
|
||||
EXCEPTION
|
||||
WHEN undefined_object THEN NULL;
|
||||
END $$;
|
||||
|
||||
-- 4) Drop the old `id` column and rename `uid`→`id`
|
||||
ALTER TABLE "Watchlist" DROP COLUMN "id";
|
||||
ALTER TABLE "Watchlist" RENAME COLUMN "uid" TO "id";
|
||||
|
||||
-- 5) Add the new PK on `id` (UUID)
|
||||
ALTER TABLE "Watchlist" ADD CONSTRAINT "Watchlist_pkey" PRIMARY KEY ("id");
|
||||
|
||||
-- DropForeignKey - Do this before modifying Watchlist structure
|
||||
ALTER TABLE "Watchlist" DROP CONSTRAINT "Watchlist_createdById_fkey";
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "Watchlist" DROP CONSTRAINT "Watchlist_updatedById_fkey";
|
||||
|
||||
-- Modify Watchlist table structure FIRST
|
||||
-- AlterTable
|
||||
ALTER TABLE "Watchlist" DROP COLUMN "createdAt",
|
||||
DROP COLUMN "createdById",
|
||||
DROP COLUMN "severity",
|
||||
DROP COLUMN "updatedAt",
|
||||
DROP COLUMN "updatedById",
|
||||
ADD COLUMN "isGlobal" BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN "lastUpdatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
ADD COLUMN "source" "WatchlistSource" NOT NULL DEFAULT 'MANUAL';
|
||||
|
||||
-- Create WatchlistAudit table AFTER Watchlist has UUID id
|
||||
-- CreateTable
|
||||
CREATE TABLE "WatchlistAudit" (
|
||||
"id" UUID NOT NULL,
|
||||
"type" "WatchlistType" NOT NULL,
|
||||
"value" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"action" "WatchlistAction" NOT NULL DEFAULT 'REPORT',
|
||||
"changedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"changedByUserId" INTEGER,
|
||||
"watchlistId" UUID NOT NULL,
|
||||
|
||||
CONSTRAINT "WatchlistAudit_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- Drop the BlockedBookingLog table
|
||||
-- DropTable
|
||||
DROP TABLE "BlockedBookingLog";
|
||||
|
||||
-- Drop unused enum
|
||||
-- DropEnum
|
||||
DROP TYPE "WatchlistSeverity";
|
||||
|
||||
-- Create WatchlistEventAudit table
|
||||
-- CreateTable
|
||||
CREATE TABLE "WatchlistEventAudit" (
|
||||
"id" UUID NOT NULL,
|
||||
"watchlistId" UUID NOT NULL,
|
||||
"eventTypeId" INTEGER NOT NULL,
|
||||
"actionTaken" "WatchlistAction" NOT NULL,
|
||||
"timestamp" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "WatchlistEventAudit_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- Create indexes for performance
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "WatchlistAudit_id_key" ON "WatchlistAudit"("id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX IF NOT EXISTS "WatchlistAudit_watchlistId_changedAt_idx" ON "WatchlistAudit"("watchlistId", "changedAt");
|
||||
|
||||
-- CreateIndex - Main composite index matching schema
|
||||
CREATE INDEX IF NOT EXISTS "Watchlist_type_value_organizationId_action_idx" ON "Watchlist"("type", "value", "organizationId", "action");
|
||||
|
||||
-- CreateIndex - For organization-specific and global queries
|
||||
CREATE INDEX IF NOT EXISTS "Watchlist_organizationId_isGlobal_idx" ON "Watchlist"("organizationId", "isGlobal");
|
||||
|
||||
-- CreateIndex - For source-based queries
|
||||
CREATE INDEX IF NOT EXISTS "Watchlist_source_idx" ON "Watchlist"("source");
|
||||
|
||||
-- CreateIndex - Add unique constraint matching schema
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "Watchlist_type_value_organizationId_key" ON "Watchlist"("type", "value", "organizationId");
|
||||
|
||||
-- CreateIndex - Performance index for WatchlistEventAudit queries
|
||||
CREATE INDEX IF NOT EXISTS "WatchlistEventAudit_watchlistId_timestamp_idx" ON "WatchlistEventAudit"("watchlistId", "timestamp");
|
||||
|
||||
-- CreateIndex - For eventType-based queries
|
||||
CREATE INDEX IF NOT EXISTS "WatchlistEventAudit_eventTypeId_timestamp_idx" ON "WatchlistEventAudit"("eventTypeId", "timestamp");
|
||||
@@ -451,8 +451,6 @@ model User {
|
||||
updatedAttributeToUsers AttributeToUser[] @relation("updatedBy")
|
||||
createdTranslations EventTypeTranslation[] @relation("CreatedEventTypeTranslations")
|
||||
updatedTranslations EventTypeTranslation[] @relation("UpdatedEventTypeTranslations")
|
||||
createdWatchlists Watchlist[] @relation("CreatedWatchlists")
|
||||
updatedWatchlists Watchlist[] @relation("UpdatedWatchlists")
|
||||
BookingInternalNote BookingInternalNote[]
|
||||
creationSource CreationSource?
|
||||
createdOrganizationOnboardings OrganizationOnboarding[] @relation("CreatedOrganizationOnboardings")
|
||||
@@ -2269,54 +2267,54 @@ enum WatchlistType {
|
||||
USERNAME
|
||||
}
|
||||
|
||||
enum WatchlistSeverity {
|
||||
LOW
|
||||
MEDIUM
|
||||
HIGH
|
||||
CRITICAL
|
||||
}
|
||||
|
||||
enum WatchlistAction {
|
||||
REPORT
|
||||
BLOCK
|
||||
ALERT
|
||||
}
|
||||
|
||||
enum WatchlistSource {
|
||||
MANUAL
|
||||
FREE_DOMAIN_POLICY
|
||||
}
|
||||
|
||||
model Watchlist {
|
||||
id String @id @unique @default(cuid())
|
||||
type WatchlistType
|
||||
|
||||
value String
|
||||
description String?
|
||||
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
type WatchlistType
|
||||
value String
|
||||
description String?
|
||||
isGlobal Boolean @default(false)
|
||||
organizationId Int?
|
||||
|
||||
action WatchlistAction @default(REPORT)
|
||||
severity WatchlistSeverity @default(LOW)
|
||||
createdAt DateTime @default(now())
|
||||
createdBy User? @relation("CreatedWatchlists", onDelete: SetNull, fields: [createdById], references: [id])
|
||||
createdById Int?
|
||||
updatedAt DateTime @updatedAt
|
||||
updatedBy User? @relation("UpdatedWatchlists", onDelete: SetNull, fields: [updatedById], references: [id])
|
||||
updatedById Int?
|
||||
action WatchlistAction @default(REPORT)
|
||||
source WatchlistSource @default(MANUAL)
|
||||
lastUpdatedAt DateTime @default(now())
|
||||
|
||||
@@unique([type, value, organizationId])
|
||||
@@index([type, value, organizationId, action])
|
||||
}
|
||||
|
||||
model BlockedBookingLog {
|
||||
id String @id @default(uuid())
|
||||
email String
|
||||
eventTypeId Int?
|
||||
model WatchlistAudit {
|
||||
id String @id @default(uuid(7)) @db.Uuid
|
||||
type WatchlistType
|
||||
value String
|
||||
description String?
|
||||
|
||||
organizationId Int?
|
||||
action WatchlistAction @default(REPORT)
|
||||
|
||||
bookingData Json?
|
||||
createdAt DateTime @default(now())
|
||||
watchlistId String?
|
||||
changedAt DateTime @default(now())
|
||||
changedByUserId Int?
|
||||
|
||||
@@index([organizationId, createdAt])
|
||||
@@index([email])
|
||||
@@index([watchlistId])
|
||||
watchlistId String @db.Uuid
|
||||
|
||||
@@index([watchlistId, changedAt])
|
||||
}
|
||||
|
||||
model WatchlistEventAudit {
|
||||
id String @id @default(uuid(7)) @db.Uuid
|
||||
watchlistId String @db.Uuid
|
||||
eventTypeId Int
|
||||
actionTaken WatchlistAction
|
||||
timestamp DateTime @default(now())
|
||||
}
|
||||
|
||||
enum BillingPeriod {
|
||||
|
||||
Reference in New Issue
Block a user