From cfcecaaa1713780d1d95712315d80dbfcf13efb5 Mon Sep 17 00:00:00 2001 From: Baptiste Arnaud Date: Mon, 5 Dec 2022 13:53:20 +0100 Subject: [PATCH] :wrench: Enable usage limits on viewer --- .../inputs/fileUpload/fileUpload.spec.ts | 91 ++++---- apps/viewer/src/features/usage/usage.spec.ts | 86 +++++-- .../blocks/[blockId]/storage/upload-url.ts | 217 +++++++++--------- .../pages/api/typebots/[typebotId]/results.ts | 203 ++++++++-------- 4 files changed, 334 insertions(+), 263 deletions(-) diff --git a/apps/viewer/src/features/blocks/inputs/fileUpload/fileUpload.spec.ts b/apps/viewer/src/features/blocks/inputs/fileUpload/fileUpload.spec.ts index 8df4e31f5..c8e2b59e8 100644 --- a/apps/viewer/src/features/blocks/inputs/fileUpload/fileUpload.spec.ts +++ b/apps/viewer/src/features/blocks/inputs/fileUpload/fileUpload.spec.ts @@ -3,11 +3,16 @@ import cuid from 'cuid' import { parse } from 'papaparse' import { readFileSync } from 'fs' import { isDefined } from 'utils' -import { importTypebotInDatabase } from 'utils/playwright/databaseActions' +import { + createWorkspaces, + importTypebotInDatabase, + injectFakeResults, +} from 'utils/playwright/databaseActions' import { typebotViewer } from 'utils/playwright/testHelpers' import { getTestAsset } from '@/test/utils/playwright' +import { Plan } from 'db' -// const THREE_GIGABYTES = 3 * 1024 * 1024 * 1024 +const THREE_GIGABYTES = 3 * 1024 * 1024 * 1024 test('should work as expected', async ({ page, browser }) => { const typebotId = cuid() @@ -73,47 +78,45 @@ test('should work as expected', async ({ page, browser }) => { await expect(page2.locator('pre')).toBeHidden() }) -// TODO: uncomment on 1st of November +test.describe('Storage limit is reached', () => { + const typebotId = cuid() + const workspaceId = cuid() -// test.describe('Storage limit is reached', () => { -// const typebotId = cuid() + test.beforeAll(async () => { + await createWorkspaces([{ id: workspaceId, plan: Plan.STARTER }]) + await importTypebotInDatabase(getTestAsset('typebots/fileUpload.json'), { + id: typebotId, + publicId: `${typebotId}-public`, + workspaceId, + }) + await injectFakeResults({ + typebotId, + count: 20, + fakeStorage: THREE_GIGABYTES, + }) + }) -// test.beforeAll(async () => { -// await importTypebotInDatabase( -// path.join(__dirname, '../fixtures/typebots/fileUpload.json'), -// { -// id: typebotId, -// publicId: `${typebotId}-public`, -// } -// ) -// await createResults({ -// typebotId, -// count: 20, -// fakeStorage: THREE_GIGABYTES, -// }) -// }) - -// test("shouldn't upload anything if limit has been reached", async ({ -// page, -// }) => { -// await page.goto(`/${typebotId}-public`) -// await typebotViewer(page) -// .locator(`input[type="file"]`) -// .setInputFiles([ -// path.join(__dirname, '../fixtures/typebots/api.json'), -// path.join(__dirname, '../fixtures/typebots/fileUpload.json'), -// path.join(__dirname, '../fixtures/typebots/hugeGroup.json'), -// ]) -// await expect(typebotViewer(page).locator(`text="3"`)).toBeVisible() -// await typebotViewer(page).locator('text="Upload 3 files"').click() -// await expect( -// typebotViewer(page).locator(`text="3 files uploaded"`) -// ).toBeVisible() -// await page.evaluate(() => -// window.localStorage.setItem('workspaceId', 'starterWorkspace') -// ) -// await page.goto(`${process.env.NEXTAUTH_URL}/typebots/${typebotId}/results`) -// await expect(page.locator('text="150%"')).toBeVisible() -// await expect(page.locator('text="api.json"')).toBeHidden() -// }) -// }) + test("shouldn't upload anything if limit has been reached", async ({ + page, + }) => { + await page.goto(`/${typebotId}-public`) + await typebotViewer(page) + .locator(`input[type="file"]`) + .setInputFiles([ + getTestAsset('typebots/api.json'), + getTestAsset('typebots/fileUpload.json'), + getTestAsset('typebots/hugeGroup.json'), + ]) + await expect(typebotViewer(page).locator(`text="3"`)).toBeVisible() + await typebotViewer(page).locator('text="Upload 3 files"').click() + await expect( + typebotViewer(page).locator(`text="3 files uploaded"`) + ).toBeVisible() + await page.evaluate(() => + window.localStorage.setItem('workspaceId', 'starterWorkspace') + ) + await page.goto(`${process.env.NEXTAUTH_URL}/typebots/${typebotId}/results`) + await expect(page.locator('text="150%"')).toBeVisible() + await expect(page.locator('text="api.json"')).toBeHidden() + }) +}) diff --git a/apps/viewer/src/features/usage/usage.spec.ts b/apps/viewer/src/features/usage/usage.spec.ts index a461bbe19..72133f4a9 100644 --- a/apps/viewer/src/features/usage/usage.spec.ts +++ b/apps/viewer/src/features/usage/usage.spec.ts @@ -1,19 +1,75 @@ +import { getTestAsset } from '@/test/utils/playwright' import test, { expect } from '@playwright/test' import cuid from 'cuid' +import { Plan } from 'db' +import { defaultSettings } from 'models' +import { + createWorkspaces, + importTypebotInDatabase, + injectFakeResults, +} from 'utils/playwright/databaseActions' +import { typebotViewer } from 'utils/playwright/testHelpers' -// TODO: uncomment on 1st of November +test('should not start if chat limit is reached', async ({ page, context }) => { + await test.step('Free plan', async () => { + const workspaceId = cuid() + const typebotId = cuid() + await createWorkspaces([{ id: workspaceId, plan: Plan.FREE }]) + await importTypebotInDatabase(getTestAsset('typebots/fileUpload.json'), { + id: typebotId, + publicId: `${typebotId}-public`, + workspaceId, + }) + await injectFakeResults({ typebotId, count: 400 }) + await page.goto(`/${typebotId}-public`) + await expect(page.locator('text="This bot is now closed."')).toBeVisible() + await page.goto(`${process.env.NEXTAUTH_URL}/typebots/${typebotId}/results`) + await expect(page.locator('text="133%"')).toBeVisible() + }) -// test('should not start if chat limit is reached', async ({ page }) => { -// const typebotId = cuid() -// await importTypebotInDatabase( -// path.join(__dirname, '../fixtures/typebots/fileUpload.json'), -// { -// id: typebotId, -// publicId: `${typebotId}-public`, -// workspaceId: limitTestWorkspaceId, -// } -// ) -// await createResults({ typebotId, count: 320 }) -// await page.goto(`/${typebotId}-public`) -// await expect(page.locator('text="This bot is now closed."')).toBeVisible() -// }) + await test.step('Lifetime plan', async () => { + const workspaceId = cuid() + const typebotId = cuid() + await createWorkspaces([{ id: workspaceId, plan: Plan.LIFETIME }]) + await importTypebotInDatabase(getTestAsset('typebots/fileUpload.json'), { + id: typebotId, + publicId: `${typebotId}-public`, + workspaceId, + }) + await injectFakeResults({ typebotId, count: 3000 }) + await page.goto(`/${typebotId}-public`) + await expect( + typebotViewer(page).locator('text="Hey there, upload please"') + ).toBeVisible() + }) + + await test.step('Custom plan', async () => { + const workspaceId = cuid() + const typebotId = cuid() + await createWorkspaces([ + { id: workspaceId, plan: Plan.CUSTOM, customChatsLimit: 1000 }, + ]) + await importTypebotInDatabase(getTestAsset('typebots/fileUpload.json'), { + id: typebotId, + publicId: `${typebotId}-public`, + workspaceId, + settings: { + ...defaultSettings, + general: { + ...defaultSettings.general, + isNewResultOnRefreshEnabled: true, + }, + }, + }) + const page = await context.newPage() + await page.goto(`/${typebotId}-public`) + await expect( + typebotViewer(page).locator('text="Hey there, upload please"') + ).toBeVisible() + await injectFakeResults({ typebotId, count: 2000 }) + await page.goto(`/${typebotId}-public`) + await expect(page.locator('text="This bot is now closed."')).toBeVisible() + await page.goto(`${process.env.NEXTAUTH_URL}/typebots/${typebotId}/results`) + await expect(page.locator('text="200%"')).toBeVisible() + }) +}) diff --git a/apps/viewer/src/pages/api/typebots/[typebotId]/blocks/[blockId]/storage/upload-url.ts b/apps/viewer/src/pages/api/typebots/[typebotId]/blocks/[blockId]/storage/upload-url.ts index fe7cf2728..17731339f 100644 --- a/apps/viewer/src/pages/api/typebots/[typebotId]/blocks/[blockId]/storage/upload-url.ts +++ b/apps/viewer/src/pages/api/typebots/[typebotId]/blocks/[blockId]/storage/upload-url.ts @@ -3,9 +3,14 @@ import prisma from '@/lib/prisma' import { InputBlockType, PublicTypebot } from 'models' import { NextApiRequest, NextApiResponse } from 'next' import { badRequest, generatePresignedUrl, methodNotAllowed } from 'utils/api' -import { byId } from 'utils' +import { byId, env, getStorageLimit, isDefined } from 'utils' +import { + sendAlmostReachedStorageLimitEmail, + sendReachedStorageLimitEmail, +} from 'emails' +import { WorkspaceRole } from 'db' -// const LIMIT_EMAIL_TRIGGER_PERCENT = 0.8 +const LIMIT_EMAIL_TRIGGER_PERCENT = 0.8 const handler = async ( req: NextApiRequest, @@ -27,7 +32,7 @@ const handler = async ( const typebotId = req.query.typebotId as string const blockId = req.query.blockId as string if (!filePath) return badRequest(res, 'Missing filePath or fileType') - // const hasReachedStorageLimit = await checkStorageLimit(typebotId) + const hasReachedStorageLimit = await checkStorageLimit(typebotId) const typebot = (await prisma.publicTypebot.findFirst({ where: { typebotId }, })) as unknown as PublicTypebot @@ -46,117 +51,119 @@ const handler = async ( sizeLimit: sizeLimit * 1024 * 1024, }) - // TODO: enable storage limit on 1st of November 2022 - return res.status(200).send({ presignedUrl, hasReachedStorageLimit: false }) + return res.status(200).send({ + presignedUrl, + hasReachedStorageLimit, + }) } return methodNotAllowed(res) } -// const checkStorageLimit = async (typebotId: string) => { -// const typebot = await prisma.typebot.findFirst({ -// where: { id: typebotId }, -// include: { -// workspace: { -// select: { -// id: true, -// additionalStorageIndex: true, -// plan: true, -// storageLimitFirstEmailSentAt: true, -// storageLimitSecondEmailSentAt: true, -// customStorageLimit: true, -// }, -// }, -// }, -// }) -// if (!typebot?.workspace) throw new Error('Workspace not found') -// const { workspace } = typebot -// const { -// _sum: { storageUsed: totalStorageUsed }, -// } = await prisma.answer.aggregate({ -// where: { -// storageUsed: { gt: 0 }, -// result: { -// typebot: { -// workspace: { -// id: typebot?.workspaceId, -// }, -// }, -// }, -// }, -// _sum: { storageUsed: true }, -// }) -// if (!totalStorageUsed) return false -// const hasSentFirstEmail = workspace.storageLimitFirstEmailSentAt !== null -// const hasSentSecondEmail = workspace.storageLimitSecondEmailSentAt !== null -// const storageLimit = getStorageLimit(typebot.workspace) -// const storageLimitBytes = storageLimit * 1024 * 1024 * 1024 -// if ( -// totalStorageUsed >= storageLimitBytes * LIMIT_EMAIL_TRIGGER_PERCENT && -// !hasSentFirstEmail && -// env('E2E_TEST') !== 'true' -// ) -// await sendAlmostReachStorageLimitNotification({ -// workspaceId: workspace.id, -// storageLimit, -// }) -// if ( -// totalStorageUsed >= storageLimitBytes && -// !hasSentSecondEmail && -// env('E2E_TEST') !== 'true' -// ) -// await sendReachStorageLimitNotification({ -// workspaceId: workspace.id, -// storageLimit, -// }) -// return totalStorageUsed >= storageLimitBytes -// } +const checkStorageLimit = async (typebotId: string): Promise => { + const typebot = await prisma.typebot.findFirst({ + where: { id: typebotId }, + include: { + workspace: { + select: { + id: true, + additionalStorageIndex: true, + plan: true, + storageLimitFirstEmailSentAt: true, + storageLimitSecondEmailSentAt: true, + customStorageLimit: true, + }, + }, + }, + }) + if (!typebot?.workspace) throw new Error('Workspace not found') + const { workspace } = typebot + const { + _sum: { storageUsed: totalStorageUsed }, + } = await prisma.answer.aggregate({ + where: { + storageUsed: { gt: 0 }, + result: { + typebot: { + workspace: { + id: typebot?.workspaceId, + }, + }, + }, + }, + _sum: { storageUsed: true }, + }) + if (!totalStorageUsed) return false + const hasSentFirstEmail = workspace.storageLimitFirstEmailSentAt !== null + const hasSentSecondEmail = workspace.storageLimitSecondEmailSentAt !== null + const storageLimit = getStorageLimit(typebot.workspace) + const storageLimitBytes = storageLimit * 1024 * 1024 * 1024 + if ( + totalStorageUsed >= storageLimitBytes * LIMIT_EMAIL_TRIGGER_PERCENT && + !hasSentFirstEmail && + env('E2E_TEST') !== 'true' + ) + await sendAlmostReachStorageLimitNotification({ + workspaceId: workspace.id, + storageLimit, + }) + if ( + totalStorageUsed >= storageLimitBytes && + !hasSentSecondEmail && + env('E2E_TEST') !== 'true' + ) + await sendReachStorageLimitNotification({ + workspaceId: workspace.id, + storageLimit, + }) + return totalStorageUsed >= storageLimitBytes +} -// const sendAlmostReachStorageLimitNotification = async ({ -// workspaceId, -// storageLimit, -// }: { -// workspaceId: string -// storageLimit: number -// }) => { -// const members = await prisma.memberInWorkspace.findMany({ -// where: { role: WorkspaceRole.ADMIN, workspaceId }, -// include: { user: { select: { email: true } } }, -// }) +const sendAlmostReachStorageLimitNotification = async ({ + workspaceId, + storageLimit, +}: { + workspaceId: string + storageLimit: number +}) => { + const members = await prisma.memberInWorkspace.findMany({ + where: { role: WorkspaceRole.ADMIN, workspaceId }, + include: { user: { select: { email: true } } }, + }) -// await sendAlmostReachedStorageLimitEmail({ -// to: members.map((member) => member.user.email).filter(isDefined), -// storageLimit, -// url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspaceId}`, -// }) + await sendAlmostReachedStorageLimitEmail({ + to: members.map((member) => member.user.email).filter(isDefined), + storageLimit, + url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspaceId}`, + }) -// await prisma.workspace.update({ -// where: { id: workspaceId }, -// data: { storageLimitFirstEmailSentAt: new Date() }, -// }) -// } + await prisma.workspace.update({ + where: { id: workspaceId }, + data: { storageLimitFirstEmailSentAt: new Date() }, + }) +} -// const sendReachStorageLimitNotification = async ({ -// workspaceId, -// storageLimit, -// }: { -// workspaceId: string -// storageLimit: number -// }) => { -// const members = await prisma.memberInWorkspace.findMany({ -// where: { role: WorkspaceRole.ADMIN, workspaceId }, -// include: { user: { select: { email: true } } }, -// }) +const sendReachStorageLimitNotification = async ({ + workspaceId, + storageLimit, +}: { + workspaceId: string + storageLimit: number +}) => { + const members = await prisma.memberInWorkspace.findMany({ + where: { role: WorkspaceRole.ADMIN, workspaceId }, + include: { user: { select: { email: true } } }, + }) -// await sendReachedStorageLimitEmail({ -// to: members.map((member) => member.user.email).filter(isDefined), -// storageLimit, -// url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspaceId}`, -// }) + await sendReachedStorageLimitEmail({ + to: members.map((member) => member.user.email).filter(isDefined), + storageLimit, + url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspaceId}`, + }) -// await prisma.workspace.update({ -// where: { id: workspaceId }, -// data: { storageLimitSecondEmailSentAt: new Date() }, -// }) -// } + await prisma.workspace.update({ + where: { id: workspaceId }, + data: { storageLimitSecondEmailSentAt: new Date() }, + }) +} export default withSentry(handler) diff --git a/apps/viewer/src/pages/api/typebots/[typebotId]/results.ts b/apps/viewer/src/pages/api/typebots/[typebotId]/results.ts index 194c7720f..4ccc98c4b 100644 --- a/apps/viewer/src/pages/api/typebots/[typebotId]/results.ts +++ b/apps/viewer/src/pages/api/typebots/[typebotId]/results.ts @@ -1,10 +1,16 @@ import { authenticateUser } from '@/features/auth/api' import prisma from '@/lib/prisma' -import { ResultWithAnswers } from 'models' +import { WorkspaceRole } from 'db' +import { + sendAlmostReachedChatsLimitEmail, + sendReachedChatsLimitEmail, +} from 'emails' +import { ResultWithAnswers, Workspace } from 'models' import { NextApiRequest, NextApiResponse } from 'next' +import { env, getChatsLimit, isDefined } from 'utils' import { methodNotAllowed } from 'utils/api' -// const LIMIT_EMAIL_TRIGGER_PERCENT = 0.8 +const LIMIT_EMAIL_TRIGGER_PERCENT = 0.8 const handler = async (req: NextApiRequest, res: NextApiResponse) => { if (req.method === 'GET') { @@ -49,113 +55,112 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { }, }, }) - // TODO: enable storage limit on 1st of November 2022 - // const hasReachedLimit = await checkChatsUsage(result.typebot.workspace) - res.send({ result, hasReachedLimit: false }) + const hasReachedLimit = await checkChatsUsage(result.typebot.workspace) + res.send({ result, hasReachedLimit }) return } methodNotAllowed(res) } -// const checkChatsUsage = async ( -// workspace: Pick< -// Workspace, -// | 'id' -// | 'plan' -// | 'additionalChatsIndex' -// | 'chatsLimitFirstEmailSentAt' -// | 'chatsLimitSecondEmailSentAt' -// | 'customChatsLimit' -// > -// ) => { -// const chatsLimit = getChatsLimit(workspace) -// if (chatsLimit === -1) return -// const now = new Date() -// const firstDayOfMonth = new Date(now.getFullYear(), now.getMonth(), 1) -// const lastDayOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0) -// const firstDayOfNextMonth = new Date(now.getFullYear(), now.getMonth() + 1, 1) -// const chatsCount = await prisma.result.count({ -// where: { -// typebot: { workspaceId: workspace.id }, -// hasStarted: true, -// createdAt: { gte: firstDayOfMonth, lte: lastDayOfMonth }, -// }, -// }) -// const hasSentFirstEmail = -// workspace.chatsLimitFirstEmailSentAt !== null && -// workspace.chatsLimitFirstEmailSentAt < firstDayOfNextMonth && -// workspace.chatsLimitFirstEmailSentAt > firstDayOfMonth -// const hasSentSecondEmail = -// workspace.chatsLimitSecondEmailSentAt !== null && -// workspace.chatsLimitSecondEmailSentAt < firstDayOfNextMonth && -// workspace.chatsLimitSecondEmailSentAt > firstDayOfMonth -// if ( -// chatsCount >= chatsLimit * LIMIT_EMAIL_TRIGGER_PERCENT && -// !hasSentFirstEmail && -// env('E2E_TEST') !== 'true' -// ) -// await sendAlmostReachChatsLimitNotification({ -// workspaceId: workspace.id, -// chatsLimit, -// }) -// if ( -// chatsCount >= chatsLimit && -// !hasSentSecondEmail && -// env('E2E_TEST') !== 'true' -// ) -// await sendReachedAlertNotification({ -// workspaceId: workspace.id, -// chatsLimit, -// }) -// return chatsCount >= chatsLimit -// } +const checkChatsUsage = async ( + workspace: Pick< + Workspace, + | 'id' + | 'plan' + | 'additionalChatsIndex' + | 'chatsLimitFirstEmailSentAt' + | 'chatsLimitSecondEmailSentAt' + | 'customChatsLimit' + > +) => { + const chatsLimit = getChatsLimit(workspace) + if (chatsLimit === -1) return + const now = new Date() + const firstDayOfMonth = new Date(now.getFullYear(), now.getMonth(), 1) + const lastDayOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0) + const firstDayOfNextMonth = new Date(now.getFullYear(), now.getMonth() + 1, 1) + const chatsCount = await prisma.result.count({ + where: { + typebot: { workspaceId: workspace.id }, + hasStarted: true, + createdAt: { gte: firstDayOfMonth, lte: lastDayOfMonth }, + }, + }) + const hasSentFirstEmail = + workspace.chatsLimitFirstEmailSentAt !== null && + workspace.chatsLimitFirstEmailSentAt < firstDayOfNextMonth && + workspace.chatsLimitFirstEmailSentAt > firstDayOfMonth + const hasSentSecondEmail = + workspace.chatsLimitSecondEmailSentAt !== null && + workspace.chatsLimitSecondEmailSentAt < firstDayOfNextMonth && + workspace.chatsLimitSecondEmailSentAt > firstDayOfMonth + if ( + chatsCount >= chatsLimit * LIMIT_EMAIL_TRIGGER_PERCENT && + !hasSentFirstEmail && + env('E2E_TEST') !== 'true' + ) + await sendAlmostReachChatsLimitNotification({ + workspaceId: workspace.id, + chatsLimit, + }) + if ( + chatsCount >= chatsLimit && + !hasSentSecondEmail && + env('E2E_TEST') !== 'true' + ) + await sendReachedAlertNotification({ + workspaceId: workspace.id, + chatsLimit, + }) + return chatsCount >= chatsLimit +} -// const sendAlmostReachChatsLimitNotification = async ({ -// workspaceId, -// chatsLimit, -// }: { -// workspaceId: string -// chatsLimit: number -// }) => { -// const members = await prisma.memberInWorkspace.findMany({ -// where: { role: WorkspaceRole.ADMIN, workspaceId }, -// include: { user: { select: { email: true } } }, -// }) +const sendAlmostReachChatsLimitNotification = async ({ + workspaceId, + chatsLimit, +}: { + workspaceId: string + chatsLimit: number +}) => { + const members = await prisma.memberInWorkspace.findMany({ + where: { role: WorkspaceRole.ADMIN, workspaceId }, + include: { user: { select: { email: true } } }, + }) -// await sendAlmostReachedChatsLimitEmail({ -// to: members.map((member) => member.user.email).filter(isDefined), -// chatsLimit, -// url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspaceId}`, -// }) + await sendAlmostReachedChatsLimitEmail({ + to: members.map((member) => member.user.email).filter(isDefined), + chatsLimit, + url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspaceId}`, + }) -// await prisma.workspace.update({ -// where: { id: workspaceId }, -// data: { chatsLimitFirstEmailSentAt: new Date() }, -// }) -// } + await prisma.workspace.update({ + where: { id: workspaceId }, + data: { chatsLimitFirstEmailSentAt: new Date() }, + }) +} -// const sendReachedAlertNotification = async ({ -// workspaceId, -// chatsLimit, -// }: { -// workspaceId: string -// chatsLimit: number -// }) => { -// const members = await prisma.memberInWorkspace.findMany({ -// where: { role: WorkspaceRole.ADMIN, workspaceId }, -// include: { user: { select: { email: true } } }, -// }) +const sendReachedAlertNotification = async ({ + workspaceId, + chatsLimit, +}: { + workspaceId: string + chatsLimit: number +}) => { + const members = await prisma.memberInWorkspace.findMany({ + where: { role: WorkspaceRole.ADMIN, workspaceId }, + include: { user: { select: { email: true } } }, + }) -// await sendReachedChatsLimitEmail({ -// to: members.map((member) => member.user.email).filter(isDefined), -// chatsLimit, -// url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspaceId}`, -// }) + await sendReachedChatsLimitEmail({ + to: members.map((member) => member.user.email).filter(isDefined), + chatsLimit, + url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspaceId}`, + }) -// await prisma.workspace.update({ -// where: { id: workspaceId }, -// data: { chatsLimitSecondEmailSentAt: new Date() }, -// }) -// } + await prisma.workspace.update({ + where: { id: workspaceId }, + data: { chatsLimitSecondEmailSentAt: new Date() }, + }) +} export default handler