From 7eac2c71100dbe9e74afdb204eb62bcd229457d2 Mon Sep 17 00:00:00 2001 From: Baptiste Arnaud Date: Thu, 8 Dec 2022 11:02:52 +0100 Subject: [PATCH] :card_file_box: Write faster prisma queries --- .../procedures/getResultExampleProcedure.ts | 4 +- .../procedures/listWebhookBlocksProcedure.ts | 4 +- .../procedures/subscribeWebhookProcedure.ts | 4 +- .../procedures/unsubscribeWebhookProcedure.ts | 4 +- .../features/results/api/archiveResults.ts | 4 +- .../api/procedures/deleteResultsProcedure.ts | 4 +- .../api/procedures/getResultLogsProcedure.ts | 9 +- .../api/procedures/getResultsProcedure.ts | 10 ++- .../src/pages/api/publicTypebots/[id].ts | 4 +- .../src/pages/api/typebots/[typebotId].ts | 15 ++-- .../[typebotId]/analytics/answersCount.ts | 6 +- .../typebots/[typebotId]/analytics/stats.ts | 18 ++-- .../pages/api/typebots/[typebotId]/blocks.ts | 5 +- .../api/typebots/[typebotId]/collaborators.ts | 4 +- .../api/typebots/[typebotId]/invitations.ts | 6 +- .../api/typebots/[typebotId]/webhooks.ts | 5 +- .../api/workspaces/[workspaceId]/usage.ts | 27 ++++-- apps/builder/src/utils/api/dbRules.ts | 59 +++++-------- .../blocks/[blockId]/storage/upload-url.ts | 10 +-- .../pages/api/typebots/[typebotId]/results.ts | 79 +++++++++-------- .../migration.sql | 2 + packages/db/prisma/schema.prisma | 1 + packages/scripts/playground.ts | 85 ++++++++++++++++++- 23 files changed, 235 insertions(+), 134 deletions(-) create mode 100644 packages/db/prisma/migrations/20221208094821_add_typebot_id_index_in_result/migration.sql diff --git a/apps/builder/src/features/blocks/integrations/webhook/api/procedures/getResultExampleProcedure.ts b/apps/builder/src/features/blocks/integrations/webhook/api/procedures/getResultExampleProcedure.ts index a7b86d2e3d0..89b01afb088 100644 --- a/apps/builder/src/features/blocks/integrations/webhook/api/procedures/getResultExampleProcedure.ts +++ b/apps/builder/src/features/blocks/integrations/webhook/api/procedures/getResultExampleProcedure.ts @@ -1,6 +1,6 @@ import { getLinkedTypebots } from '@/features/blocks/logic/typebotLink/api' import prisma from '@/lib/prisma' -import { canReadTypebot } from '@/utils/api/dbRules' +import { canReadTypebots } from '@/utils/api/dbRules' import { authenticatedProcedure } from '@/utils/server/trpc' import { TRPCError } from '@trpc/server' import { Typebot, Webhook } from 'models' @@ -40,7 +40,7 @@ export const getResultExampleProcedure = authenticatedProcedure ) .query(async ({ input: { typebotId, blockId }, ctx: { user } }) => { const typebot = (await prisma.typebot.findFirst({ - where: canReadTypebot(typebotId, user), + where: canReadTypebots(typebotId, user), select: { groups: true, edges: true, diff --git a/apps/builder/src/features/blocks/integrations/webhook/api/procedures/listWebhookBlocksProcedure.ts b/apps/builder/src/features/blocks/integrations/webhook/api/procedures/listWebhookBlocksProcedure.ts index ce1bac7d9fb..b634f8baeaf 100644 --- a/apps/builder/src/features/blocks/integrations/webhook/api/procedures/listWebhookBlocksProcedure.ts +++ b/apps/builder/src/features/blocks/integrations/webhook/api/procedures/listWebhookBlocksProcedure.ts @@ -1,5 +1,5 @@ import prisma from '@/lib/prisma' -import { canReadTypebot } from '@/utils/api/dbRules' +import { canReadTypebots } from '@/utils/api/dbRules' import { authenticatedProcedure } from '@/utils/server/trpc' import { TRPCError } from '@trpc/server' import { Group, Typebot, Webhook, WebhookBlock } from 'models' @@ -36,7 +36,7 @@ export const listWebhookBlocksProcedure = authenticatedProcedure ) .query(async ({ input: { typebotId }, ctx: { user } }) => { const typebot = (await prisma.typebot.findFirst({ - where: canReadTypebot(typebotId, user), + where: canReadTypebots(typebotId, user), select: { groups: true, webhooks: true, diff --git a/apps/builder/src/features/blocks/integrations/webhook/api/procedures/subscribeWebhookProcedure.ts b/apps/builder/src/features/blocks/integrations/webhook/api/procedures/subscribeWebhookProcedure.ts index dbc131ea769..f9c6bb7d722 100644 --- a/apps/builder/src/features/blocks/integrations/webhook/api/procedures/subscribeWebhookProcedure.ts +++ b/apps/builder/src/features/blocks/integrations/webhook/api/procedures/subscribeWebhookProcedure.ts @@ -1,5 +1,5 @@ import prisma from '@/lib/prisma' -import { canWriteTypebot } from '@/utils/api/dbRules' +import { canWriteTypebots } from '@/utils/api/dbRules' import { authenticatedProcedure } from '@/utils/server/trpc' import { TRPCError } from '@trpc/server' import { Typebot, Webhook, WebhookBlock } from 'models' @@ -31,7 +31,7 @@ export const subscribeWebhookProcedure = authenticatedProcedure ) .query(async ({ input: { typebotId, blockId, url }, ctx: { user } }) => { const typebot = (await prisma.typebot.findFirst({ - where: canWriteTypebot(typebotId, user), + where: canWriteTypebots(typebotId, user), select: { groups: true, webhooks: true, diff --git a/apps/builder/src/features/blocks/integrations/webhook/api/procedures/unsubscribeWebhookProcedure.ts b/apps/builder/src/features/blocks/integrations/webhook/api/procedures/unsubscribeWebhookProcedure.ts index 19d1982eac4..6ba92f170d2 100644 --- a/apps/builder/src/features/blocks/integrations/webhook/api/procedures/unsubscribeWebhookProcedure.ts +++ b/apps/builder/src/features/blocks/integrations/webhook/api/procedures/unsubscribeWebhookProcedure.ts @@ -1,5 +1,5 @@ import prisma from '@/lib/prisma' -import { canWriteTypebot } from '@/utils/api/dbRules' +import { canWriteTypebots } from '@/utils/api/dbRules' import { authenticatedProcedure } from '@/utils/server/trpc' import { TRPCError } from '@trpc/server' import { Typebot, Webhook, WebhookBlock } from 'models' @@ -30,7 +30,7 @@ export const unsubscribeWebhookProcedure = authenticatedProcedure ) .query(async ({ input: { typebotId, blockId }, ctx: { user } }) => { const typebot = (await prisma.typebot.findFirst({ - where: canWriteTypebot(typebotId, user), + where: canWriteTypebots(typebotId, user), select: { groups: true, webhooks: true, diff --git a/apps/builder/src/features/results/api/archiveResults.ts b/apps/builder/src/features/results/api/archiveResults.ts index fa95a82fbf6..b9c0ad54bd5 100644 --- a/apps/builder/src/features/results/api/archiveResults.ts +++ b/apps/builder/src/features/results/api/archiveResults.ts @@ -1,5 +1,5 @@ import prisma from '@/lib/prisma' -import { canWriteTypebot } from '@/utils/api/dbRules' +import { canWriteTypebots } from '@/utils/api/dbRules' import { deleteFiles } from '@/utils/api/storage' import { User, Prisma } from 'db' import { InputBlockType, Typebot } from 'models' @@ -14,7 +14,7 @@ export const archiveResults = async ({ resultsFilter?: Prisma.ResultWhereInput }) => { const typebot = await prisma.typebot.findFirst({ - where: canWriteTypebot(typebotId, user), + where: canWriteTypebots(typebotId, user), select: { groups: true }, }) if (!typebot) return { success: false } diff --git a/apps/builder/src/features/results/api/procedures/deleteResultsProcedure.ts b/apps/builder/src/features/results/api/procedures/deleteResultsProcedure.ts index 620a8cec756..31f2554d43c 100644 --- a/apps/builder/src/features/results/api/procedures/deleteResultsProcedure.ts +++ b/apps/builder/src/features/results/api/procedures/deleteResultsProcedure.ts @@ -1,4 +1,4 @@ -import { canWriteTypebot } from '@/utils/api/dbRules' +import { canWriteTypebots } from '@/utils/api/dbRules' import { authenticatedProcedure } from '@/utils/server/trpc' import { TRPCError } from '@trpc/server' import { z } from 'zod' @@ -34,7 +34,7 @@ export const deleteResultsProcedure = authenticatedProcedure user, resultsFilter: { id: (idsArray?.length ?? 0) > 0 ? { in: idsArray } : undefined, - typebot: canWriteTypebot(typebotId, user), + typebot: canWriteTypebots(typebotId, user), }, }) diff --git a/apps/builder/src/features/results/api/procedures/getResultLogsProcedure.ts b/apps/builder/src/features/results/api/procedures/getResultLogsProcedure.ts index ae1f36638c1..c269114ddbe 100644 --- a/apps/builder/src/features/results/api/procedures/getResultLogsProcedure.ts +++ b/apps/builder/src/features/results/api/procedures/getResultLogsProcedure.ts @@ -1,5 +1,5 @@ import prisma from '@/lib/prisma' -import { canReadTypebot } from '@/utils/api/dbRules' +import { canReadTypebots } from '@/utils/api/dbRules' import { authenticatedProcedure } from '@/utils/server/trpc' import { logSchema } from 'models' import { z } from 'zod' @@ -22,9 +22,14 @@ export const getResultLogsProcedure = authenticatedProcedure ) .output(z.object({ logs: z.array(logSchema) })) .query(async ({ input: { typebotId, resultId }, ctx: { user } }) => { + const typebot = await prisma.typebot.findFirst({ + where: canReadTypebots(typebotId, user), + select: { id: true }, + }) + if (!typebot) throw new Error('Typebot not found') const logs = await prisma.log.findMany({ where: { - result: { id: resultId, typebot: canReadTypebot(typebotId, user) }, + result: { id: resultId, typebotId: typebot.id }, }, }) diff --git a/apps/builder/src/features/results/api/procedures/getResultsProcedure.ts b/apps/builder/src/features/results/api/procedures/getResultsProcedure.ts index 3fe2e3a2c19..a8bf5d1fa88 100644 --- a/apps/builder/src/features/results/api/procedures/getResultsProcedure.ts +++ b/apps/builder/src/features/results/api/procedures/getResultsProcedure.ts @@ -1,5 +1,5 @@ import prisma from '@/lib/prisma' -import { canReadTypebot } from '@/utils/api/dbRules' +import { canReadTypebots } from '@/utils/api/dbRules' import { authenticatedProcedure } from '@/utils/server/trpc' import { TRPCError } from '@trpc/server' import { ResultWithAnswers, resultWithAnswersSchema } from 'models' @@ -38,11 +38,17 @@ export const getResultsProcedure = authenticatedProcedure message: 'limit must be between 1 and 200', }) const { cursor } = input + const typebot = await prisma.typebot.findFirst({ + where: canReadTypebots(input.typebotId, user), + select: { id: true }, + }) + if (!typebot) + throw new TRPCError({ code: 'NOT_FOUND', message: 'Typebot not found' }) const results = (await prisma.result.findMany({ take: limit + 1, cursor: cursor ? { id: cursor } : undefined, where: { - typebot: canReadTypebot(input.typebotId, user), + typebotId: typebot.id, answers: { some: {} }, }, orderBy: { diff --git a/apps/builder/src/pages/api/publicTypebots/[id].ts b/apps/builder/src/pages/api/publicTypebots/[id].ts index 8c1f260a67c..5d22d95bbd6 100644 --- a/apps/builder/src/pages/api/publicTypebots/[id].ts +++ b/apps/builder/src/pages/api/publicTypebots/[id].ts @@ -2,7 +2,7 @@ import { withSentry } from '@sentry/nextjs' import prisma from '@/lib/prisma' import { InputBlockType, PublicTypebot } from 'models' import { NextApiRequest, NextApiResponse } from 'next' -import { canPublishFileInput, canWriteTypebot } from '@/utils/api/dbRules' +import { canPublishFileInput, canWriteTypebots } from '@/utils/api/dbRules' import { getAuthenticatedUser } from '@/features/auth/api' import { badRequest, methodNotAllowed, notAuthenticated } from 'utils/api' @@ -39,7 +39,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { await prisma.publicTypebot.deleteMany({ where: { id: publishedTypebotId, - typebot: canWriteTypebot(typebotId, user), + typebot: canWriteTypebots(typebotId, user), }, }) return res.send({ success: true }) diff --git a/apps/builder/src/pages/api/typebots/[typebotId].ts b/apps/builder/src/pages/api/typebots/[typebotId].ts index bba7a705223..d1b07f452b9 100644 --- a/apps/builder/src/pages/api/typebots/[typebotId].ts +++ b/apps/builder/src/pages/api/typebots/[typebotId].ts @@ -2,7 +2,7 @@ import { withSentry } from '@sentry/nextjs' import { CollaborationType } from 'db' import prisma from '@/lib/prisma' import { NextApiRequest, NextApiResponse } from 'next' -import { canReadTypebot, canWriteTypebot } from '@/utils/api/dbRules' +import { canReadTypebots, canWriteTypebots } from '@/utils/api/dbRules' import { methodNotAllowed, notAuthenticated } from 'utils/api' import { getAuthenticatedUser } from '@/features/auth/api' import { archiveResults } from '@/features/results/api' @@ -15,7 +15,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { if (req.method === 'GET') { const typebot = await prisma.typebot.findFirst({ where: { - ...canReadTypebot(typebotId, user), + ...canReadTypebots(typebotId, user), isArchived: { not: true }, }, include: { @@ -46,10 +46,10 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { }) if (!success) return res.status(500).send({ success: false }) await prisma.publicTypebot.deleteMany({ - where: { typebot: canWriteTypebot(typebotId, user) }, + where: { typebot: canWriteTypebots(typebotId, user) }, }) const typebots = await prisma.typebot.updateMany({ - where: canWriteTypebot(typebotId, user), + where: canWriteTypebots(typebotId, user), data: { isArchived: true }, }) return res.send({ typebots }) @@ -57,7 +57,8 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { if (req.method === 'PUT') { const data = typeof req.body === 'string' ? JSON.parse(req.body) : req.body const existingTypebot = await prisma.typebot.findFirst({ - where: canReadTypebot(typebotId, user), + where: canReadTypebots(typebotId, user), + select: { updatedAt: true }, }) if ( existingTypebot && @@ -65,7 +66,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { ) return res.send({ message: 'Found newer version of typebot in database' }) const typebots = await prisma.typebot.updateMany({ - where: canWriteTypebot(typebotId, user), + where: canWriteTypebots(typebotId, user), data: { ...data, theme: data.theme ?? undefined, @@ -78,7 +79,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { if (req.method === 'PATCH') { const data = typeof req.body === 'string' ? JSON.parse(req.body) : req.body const typebots = await prisma.typebot.updateMany({ - where: canWriteTypebot(typebotId, user), + where: canWriteTypebots(typebotId, user), data, }) return res.send({ typebots }) diff --git a/apps/builder/src/pages/api/typebots/[typebotId]/analytics/answersCount.ts b/apps/builder/src/pages/api/typebots/[typebotId]/analytics/answersCount.ts index 106b603b576..495b7f00701 100644 --- a/apps/builder/src/pages/api/typebots/[typebotId]/analytics/answersCount.ts +++ b/apps/builder/src/pages/api/typebots/[typebotId]/analytics/answersCount.ts @@ -4,7 +4,7 @@ import { NextApiRequest, NextApiResponse } from 'next' import { methodNotAllowed, notAuthenticated } from 'utils/api' import { withSentry } from '@sentry/nextjs' import { getAuthenticatedUser } from '@/features/auth/api' -import { canReadTypebot } from '@/utils/api/dbRules' +import { canReadTypebots } from '@/utils/api/dbRules' const handler = async (req: NextApiRequest, res: NextApiResponse) => { const user = await getAuthenticatedUser(req) @@ -12,8 +12,8 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { if (req.method === 'GET') { const typebotId = req.query.typebotId as string const typebot = await prisma.typebot.findFirst({ - where: canReadTypebot(typebotId, user), - include: { publishedTypebot: true }, + where: canReadTypebots(typebotId, user), + select: { publishedTypebot: true }, }) const publishedTypebot = typebot?.publishedTypebot as unknown as PublicTypebot diff --git a/apps/builder/src/pages/api/typebots/[typebotId]/analytics/stats.ts b/apps/builder/src/pages/api/typebots/[typebotId]/analytics/stats.ts index 028e6d295ec..b49f23cbb06 100644 --- a/apps/builder/src/pages/api/typebots/[typebotId]/analytics/stats.ts +++ b/apps/builder/src/pages/api/typebots/[typebotId]/analytics/stats.ts @@ -2,7 +2,7 @@ import { withSentry } from '@sentry/nextjs' import prisma from '@/lib/prisma' import { Stats } from 'models' import { NextApiRequest, NextApiResponse } from 'next' -import { canReadTypebot } from '@/utils/api/dbRules' +import { canReadTypebots } from '@/utils/api/dbRules' import { getAuthenticatedUser } from '@/features/auth/api' import { methodNotAllowed, notAuthenticated } from 'utils/api' @@ -12,23 +12,27 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { if (req.method === 'GET') { const typebotId = req.query.typebotId as string + const typebot = await prisma.typebot.findFirst({ + where: canReadTypebots(typebotId, user), + select: { id: true }, + }) + + if (!typebot) return res.status(404).send({ message: 'Typebot not found' }) + const totalViews = await prisma.result.count({ where: { - typebotId, - typebot: canReadTypebot(typebotId, user), + typebotId: typebot.id, }, }) const totalStarts = await prisma.result.count({ where: { - typebotId, - typebot: canReadTypebot(typebotId, user), + typebotId: typebot.id, answers: { some: {} }, }, }) const totalCompleted = await prisma.result.count({ where: { - typebotId, - typebot: canReadTypebot(typebotId, user), + typebotId: typebot.id, isCompleted: true, }, }) diff --git a/apps/builder/src/pages/api/typebots/[typebotId]/blocks.ts b/apps/builder/src/pages/api/typebots/[typebotId]/blocks.ts index 8b66e6d3002..d604eff7aa3 100644 --- a/apps/builder/src/pages/api/typebots/[typebotId]/blocks.ts +++ b/apps/builder/src/pages/api/typebots/[typebotId]/blocks.ts @@ -1,7 +1,7 @@ import { withSentry } from '@sentry/nextjs' import prisma from '@/lib/prisma' import { NextApiRequest, NextApiResponse } from 'next' -import { canReadTypebot } from '@/utils/api/dbRules' +import { canReadTypebots } from '@/utils/api/dbRules' import { getAuthenticatedUser } from '@/features/auth/api' import { methodNotAllowed, notAuthenticated, notFound } from 'utils/api' @@ -11,7 +11,8 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { if (req.method === 'GET') { const typebotId = req.query.typebotId as string const typebot = await prisma.typebot.findFirst({ - where: canReadTypebot(typebotId, user), + where: canReadTypebots(typebotId, user), + select: { groups: true }, }) if (!typebot) return notFound(res) return res.send({ groups: typebot.groups }) diff --git a/apps/builder/src/pages/api/typebots/[typebotId]/collaborators.ts b/apps/builder/src/pages/api/typebots/[typebotId]/collaborators.ts index dcedda77d95..08b764c0861 100644 --- a/apps/builder/src/pages/api/typebots/[typebotId]/collaborators.ts +++ b/apps/builder/src/pages/api/typebots/[typebotId]/collaborators.ts @@ -1,7 +1,7 @@ import { withSentry } from '@sentry/nextjs' import prisma from '@/lib/prisma' import { NextApiRequest, NextApiResponse } from 'next' -import { canReadTypebot } from '@/utils/api/dbRules' +import { canReadTypebots } from '@/utils/api/dbRules' import { getAuthenticatedUser } from '@/features/auth/api' import { methodNotAllowed, notAuthenticated } from 'utils/api' @@ -11,7 +11,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { const typebotId = req.query.typebotId as string if (req.method === 'GET') { const collaborators = await prisma.collaboratorsOnTypebots.findMany({ - where: { typebot: canReadTypebot(typebotId, user) }, + where: { typebot: canReadTypebots(typebotId, user) }, include: { user: { select: { name: true, image: true, email: true } } }, }) return res.send({ diff --git a/apps/builder/src/pages/api/typebots/[typebotId]/invitations.ts b/apps/builder/src/pages/api/typebots/[typebotId]/invitations.ts index 1e4f0744335..a74699500f6 100644 --- a/apps/builder/src/pages/api/typebots/[typebotId]/invitations.ts +++ b/apps/builder/src/pages/api/typebots/[typebotId]/invitations.ts @@ -2,7 +2,7 @@ import { withSentry } from '@sentry/nextjs' import { CollaborationType, WorkspaceRole } from 'db' import prisma from '@/lib/prisma' import { NextApiRequest, NextApiResponse } from 'next' -import { canReadTypebot, canWriteTypebot } from '@/utils/api/dbRules' +import { canReadTypebots, canWriteTypebots } from '@/utils/api/dbRules' import { badRequest, forbidden, @@ -19,7 +19,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { const typebotId = req.query.typebotId as string if (req.method === 'GET') { const invitations = await prisma.invitation.findMany({ - where: { typebotId, typebot: canReadTypebot(typebotId, user) }, + where: { typebotId, typebot: canReadTypebots(typebotId, user) }, }) return res.send({ invitations, @@ -27,7 +27,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { } if (req.method === 'POST') { const typebot = await prisma.typebot.findFirst({ - where: canWriteTypebot(typebotId, user), + where: canWriteTypebots(typebotId, user), include: { workspace: { select: { name: true } } }, }) if (!typebot || !typebot.workspaceId) return forbidden(res) diff --git a/apps/builder/src/pages/api/typebots/[typebotId]/webhooks.ts b/apps/builder/src/pages/api/typebots/[typebotId]/webhooks.ts index f96794c570d..147b5aea9ae 100644 --- a/apps/builder/src/pages/api/typebots/[typebotId]/webhooks.ts +++ b/apps/builder/src/pages/api/typebots/[typebotId]/webhooks.ts @@ -2,7 +2,7 @@ import { withSentry } from '@sentry/nextjs' import prisma from '@/lib/prisma' import { defaultWebhookAttributes } from 'models' import { NextApiRequest, NextApiResponse } from 'next' -import { canWriteTypebot } from '@/utils/api/dbRules' +import { canWriteTypebots } from '@/utils/api/dbRules' import { getAuthenticatedUser } from '@/features/auth/api' import { forbidden, methodNotAllowed, notAuthenticated } from 'utils/api' @@ -12,7 +12,8 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { if (req.method === 'POST') { const typebotId = req.query.typebotId as string const typebot = await prisma.typebot.findFirst({ - where: canWriteTypebot(typebotId, user), + where: canWriteTypebots(typebotId, user), + select: { id: true }, }) if (!typebot) return forbidden(res) const webhook = await prisma.webhook.create({ diff --git a/apps/builder/src/pages/api/workspaces/[workspaceId]/usage.ts b/apps/builder/src/pages/api/workspaces/[workspaceId]/usage.ts index 6261af7d6c3..a7079137954 100644 --- a/apps/builder/src/pages/api/workspaces/[workspaceId]/usage.ts +++ b/apps/builder/src/pages/api/workspaces/[workspaceId]/usage.ts @@ -11,21 +11,30 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { const workspaceId = req.query.workspaceId as string const now = new Date() const firstDayOfMonth = new Date(now.getFullYear(), now.getMonth(), 1) - const lastDayOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 1) - const totalChatsUsed = await prisma.result.count({ - where: { - typebot: { + const firstDayOfNextMonth = new Date( + now.getFullYear(), + now.getMonth() + 1, + 1 + ) + const totalChatsUsed = await prisma.$transaction(async (tx) => { + const typebots = await tx.typebot.findMany({ + where: { workspace: { id: workspaceId, members: { some: { userId: user.id } }, }, }, - hasStarted: true, - createdAt: { - gte: firstDayOfMonth, - lte: lastDayOfMonth, + }) + return tx.result.count({ + where: { + typebotId: { in: typebots.map((typebot) => typebot.id) }, + hasStarted: true, + createdAt: { + gte: firstDayOfMonth, + lt: firstDayOfNextMonth, + }, }, - }, + }) }) const { _sum: { storageUsed: totalStorageUsed }, diff --git a/apps/builder/src/utils/api/dbRules.ts b/apps/builder/src/utils/api/dbRules.ts index e66800ae9fe..90e162fa20d 100644 --- a/apps/builder/src/utils/api/dbRules.ts +++ b/apps/builder/src/utils/api/dbRules.ts @@ -1,50 +1,37 @@ -import { CollaborationType, Plan, Prisma, User, WorkspaceRole } from 'db' +import { Plan, Prisma, User, WorkspaceRole } from 'db' import prisma from '@/lib/prisma' import { NextApiResponse } from 'next' import { env, isNotEmpty } from 'utils' import { forbidden } from 'utils/api' -const parseWhereFilter = ( +export const canWriteTypebots = ( typebotIds: string[] | string, - user: User, - type: 'read' | 'write' + user: Pick ): Prisma.TypebotWhereInput => ({ - OR: [ - { - id: typeof typebotIds === 'string' ? typebotIds : { in: typebotIds }, - collaborators: { - some: { - userId: user.id, - type: type === 'write' ? CollaborationType.WRITE : undefined, + id: typeof typebotIds === 'string' ? typebotIds : { in: typebotIds }, + workspace: isNotEmpty(env('E2E_TEST')) + ? undefined + : { + members: { + some: { userId: user.id, role: { not: WorkspaceRole.GUEST } }, }, }, - }, - { - id: typeof typebotIds === 'string' ? typebotIds : { in: typebotIds }, - workspace: - (type === 'read' && user.email === process.env.ADMIN_EMAIL) || - isNotEmpty(env('E2E_TEST')) - ? undefined - : { - members: { - some: { userId: user.id, role: { not: WorkspaceRole.GUEST } }, - }, - }, - }, - ], }) -export const canReadTypebot = (typebotId: string, user: User) => - parseWhereFilter(typebotId, user, 'read') - -export const canWriteTypebot = (typebotId: string, user: User) => - parseWhereFilter(typebotId, user, 'write') - -export const canReadTypebots = (typebotIds: string[], user: User) => - parseWhereFilter(typebotIds, user, 'read') - -export const canWriteTypebots = (typebotIds: string[], user: User) => - parseWhereFilter(typebotIds, user, 'write') +export const canReadTypebots = ( + typebotIds: string | string[], + user: Pick +) => ({ + id: typeof typebotIds === 'string' ? typebotIds : { in: typebotIds }, + workspace: + user.email === process.env.ADMIN_EMAIL || isNotEmpty(env('E2E_TEST')) + ? undefined + : { + members: { + some: { userId: user.id }, + }, + }, +}) export const canEditGuests = (user: User, typebotId: string) => ({ id: typebotId, 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 8a65853c93e..bf793ea3118 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 @@ -32,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 @@ -53,14 +53,14 @@ const handler = async ( return res.status(200).send({ presignedUrl, - hasReachedStorageLimit: false, + hasReachedStorageLimit, }) } return methodNotAllowed(res) } const checkStorageLimit = async (typebotId: string): Promise => { - const typebot = await prisma.typebot.findFirst({ + const typebot = await prisma.typebot.findUnique({ where: { id: typebotId }, include: { workspace: { @@ -84,9 +84,7 @@ const checkStorageLimit = async (typebotId: string): Promise => { storageUsed: { gt: 0 }, result: { typebot: { - workspace: { - id: typebot?.workspaceId, - }, + workspaceId: typebot.workspaceId, }, }, }, diff --git a/apps/viewer/src/pages/api/typebots/[typebotId]/results.ts b/apps/viewer/src/pages/api/typebots/[typebotId]/results.ts index d037dd1c5a7..3632c9a702a 100644 --- a/apps/viewer/src/pages/api/typebots/[typebotId]/results.ts +++ b/apps/viewer/src/pages/api/typebots/[typebotId]/results.ts @@ -5,7 +5,7 @@ import { sendAlmostReachedChatsLimitEmail, sendReachedChatsLimitEmail, } from 'emails' -import { ResultWithAnswers, Workspace } from 'models' +import { ResultWithAnswers } from 'models' import { NextApiRequest, NextApiResponse } from 'next' import { env, getChatsLimit, isDefined } from 'utils' import { methodNotAllowed } from 'utils/api' @@ -33,58 +33,63 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { } if (req.method === 'POST') { const typebotId = req.query.typebotId as string + const hasReachedLimit = await checkChatsUsage(typebotId) + if (hasReachedLimit) return res.send({ result: null, hasReachedLimit }) const result = await prisma.result.create({ data: { typebotId, isCompleted: false, }, - include: { - typebot: { - include: { - workspace: { - select: { - id: true, - plan: true, - additionalChatsIndex: true, - chatsLimitFirstEmailSentAt: true, - chatsLimitSecondEmailSentAt: true, - customChatsLimit: true, - }, - }, - }, - }, - }, }) - // const hasReachedLimit = await checkChatsUsage(result.typebot.workspace) - res.send({ result, hasReachedLimit: false }) + res.send({ result }) return } methodNotAllowed(res) } -const checkChatsUsage = async ( - workspace: Pick< - Workspace, - | 'id' - | 'plan' - | 'additionalChatsIndex' - | 'chatsLimitFirstEmailSentAt' - | 'chatsLimitSecondEmailSentAt' - | 'customChatsLimit' - > -) => { +const checkChatsUsage = async (typebotId: string) => { + const typebot = await prisma.typebot.findUnique({ + where: { + id: typebotId, + }, + include: { + workspace: { + select: { + id: true, + plan: true, + additionalChatsIndex: true, + chatsLimitFirstEmailSentAt: true, + chatsLimitSecondEmailSentAt: true, + customChatsLimit: true, + }, + }, + }, + }) + + const workspace = typebot?.workspace + + if (!workspace) return false + 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 chatsCount = await prisma.$transaction(async (tx) => { + const typebotIds = await tx.typebot.findMany({ + where: { + workspaceId: workspace.id, + }, + select: { id: true }, + }) + + return tx.result.count({ + where: { + typebotId: { in: typebotIds.map((typebot) => typebot.id) }, + hasStarted: true, + createdAt: { gte: firstDayOfMonth, lte: firstDayOfNextMonth }, + }, + }) }) const hasSentFirstEmail = workspace.chatsLimitFirstEmailSentAt !== null && diff --git a/packages/db/prisma/migrations/20221208094821_add_typebot_id_index_in_result/migration.sql b/packages/db/prisma/migrations/20221208094821_add_typebot_id_index_in_result/migration.sql new file mode 100644 index 00000000000..c81194d84c5 --- /dev/null +++ b/packages/db/prisma/migrations/20221208094821_add_typebot_id_index_in_result/migration.sql @@ -0,0 +1,2 @@ +-- CreateIndex +CREATE INDEX IF NOT EXISTS "Result_typebotId_idx" ON "Result"("typebotId"); \ No newline at end of file diff --git a/packages/db/prisma/schema.prisma b/packages/db/prisma/schema.prisma index 497d439f7d4..b86339f9cde 100644 --- a/packages/db/prisma/schema.prisma +++ b/packages/db/prisma/schema.prisma @@ -224,6 +224,7 @@ model Result { answers Answer[] logs Log[] + @@index([typebotId]) @@index([typebotId, createdAt]) @@index([createdAt, typebotId]) } diff --git a/packages/scripts/playground.ts b/packages/scripts/playground.ts index e06171ff8d9..47857288fc0 100644 --- a/packages/scripts/playground.ts +++ b/packages/scripts/playground.ts @@ -1,9 +1,90 @@ -import { PrismaClient } from 'db' +import { Prisma, PrismaClient, User, WorkspaceRole } from 'db' +import { env, isNotEmpty } from 'utils' import { promptAndSetEnvironment } from './utils' const executePlayground = async () => { await promptAndSetEnvironment() - const prisma = new PrismaClient({ log: ['query', 'info', 'warn', 'error'] }) + const prisma = new PrismaClient({ + log: [{ emit: 'event', level: 'query' }, 'info', 'warn', 'error'], + }) + + prisma.$on('query', (e) => { + console.log(e.query) + console.log(e.params) + console.log(e.duration, 'ms') + }) + const now = new Date() + + const typebot = await prisma.typebot.findFirst({ + where: parseReadFilter('6rw8KvRZe7UbHcJrs8Ui4S', { + email: '', + id: 'ckzmhmiey001009mnzt5nkxu8', + }), + select: { id: true }, + }) + + if (!typebot) return + + const totalViews = await prisma.result.count({ + where: { + typebotId: { + in: await filterAuthorizedTypebotIds( + prisma, + { + typebotIds: '6rw8KvRZe7UbHcJrs8Ui4S', + user: { email: '', id: 'ckzmhmiey001009mnzt5nkxu8' }, + }, + 'read' + ), + }, + }, + }) + const totalStarts = await prisma.result.count({ + where: { + typebot: { id: typebot.id }, + answers: { some: {} }, + }, + }) + const totalCompleted = await prisma.result.count({ + where: { + typebot: { id: typebot.id }, + isCompleted: true, + }, + }) +} + +const filterAuthorizedTypebotIds = async ( + prisma: PrismaClient, + { + typebotIds, + user, + }: { + typebotIds: string | string[] + user: Pick + }, + role: 'read' | 'write' +) => { + const typebots = await prisma.typebot.findMany({ + where: { + id: { in: typebotIds }, + workspace: + (role === 'read' && user.email === process.env.ADMIN_EMAIL) || + isNotEmpty(env('E2E_TEST')) + ? undefined + : { + members: { + some: { + userId: user.id, + role: + role === 'write' ? { not: WorkspaceRole.GUEST } : undefined, + }, + }, + }, + }, + select: { id: true }, + }) + + return typebots.map((typebot) => typebot.id) } executePlayground()