2
0

🗃️ Write faster prisma queries

This commit is contained in:
Baptiste Arnaud
2022-12-08 11:02:52 +01:00
parent fe8a531715
commit 7eac2c7110
23 changed files with 235 additions and 134 deletions

View File

@ -1,6 +1,6 @@
import { getLinkedTypebots } from '@/features/blocks/logic/typebotLink/api' import { getLinkedTypebots } from '@/features/blocks/logic/typebotLink/api'
import prisma from '@/lib/prisma' import prisma from '@/lib/prisma'
import { canReadTypebot } from '@/utils/api/dbRules' import { canReadTypebots } from '@/utils/api/dbRules'
import { authenticatedProcedure } from '@/utils/server/trpc' import { authenticatedProcedure } from '@/utils/server/trpc'
import { TRPCError } from '@trpc/server' import { TRPCError } from '@trpc/server'
import { Typebot, Webhook } from 'models' import { Typebot, Webhook } from 'models'
@ -40,7 +40,7 @@ export const getResultExampleProcedure = authenticatedProcedure
) )
.query(async ({ input: { typebotId, blockId }, ctx: { user } }) => { .query(async ({ input: { typebotId, blockId }, ctx: { user } }) => {
const typebot = (await prisma.typebot.findFirst({ const typebot = (await prisma.typebot.findFirst({
where: canReadTypebot(typebotId, user), where: canReadTypebots(typebotId, user),
select: { select: {
groups: true, groups: true,
edges: true, edges: true,

View File

@ -1,5 +1,5 @@
import prisma from '@/lib/prisma' import prisma from '@/lib/prisma'
import { canReadTypebot } from '@/utils/api/dbRules' import { canReadTypebots } from '@/utils/api/dbRules'
import { authenticatedProcedure } from '@/utils/server/trpc' import { authenticatedProcedure } from '@/utils/server/trpc'
import { TRPCError } from '@trpc/server' import { TRPCError } from '@trpc/server'
import { Group, Typebot, Webhook, WebhookBlock } from 'models' import { Group, Typebot, Webhook, WebhookBlock } from 'models'
@ -36,7 +36,7 @@ export const listWebhookBlocksProcedure = authenticatedProcedure
) )
.query(async ({ input: { typebotId }, ctx: { user } }) => { .query(async ({ input: { typebotId }, ctx: { user } }) => {
const typebot = (await prisma.typebot.findFirst({ const typebot = (await prisma.typebot.findFirst({
where: canReadTypebot(typebotId, user), where: canReadTypebots(typebotId, user),
select: { select: {
groups: true, groups: true,
webhooks: true, webhooks: true,

View File

@ -1,5 +1,5 @@
import prisma from '@/lib/prisma' import prisma from '@/lib/prisma'
import { canWriteTypebot } from '@/utils/api/dbRules' import { canWriteTypebots } from '@/utils/api/dbRules'
import { authenticatedProcedure } from '@/utils/server/trpc' import { authenticatedProcedure } from '@/utils/server/trpc'
import { TRPCError } from '@trpc/server' import { TRPCError } from '@trpc/server'
import { Typebot, Webhook, WebhookBlock } from 'models' import { Typebot, Webhook, WebhookBlock } from 'models'
@ -31,7 +31,7 @@ export const subscribeWebhookProcedure = authenticatedProcedure
) )
.query(async ({ input: { typebotId, blockId, url }, ctx: { user } }) => { .query(async ({ input: { typebotId, blockId, url }, ctx: { user } }) => {
const typebot = (await prisma.typebot.findFirst({ const typebot = (await prisma.typebot.findFirst({
where: canWriteTypebot(typebotId, user), where: canWriteTypebots(typebotId, user),
select: { select: {
groups: true, groups: true,
webhooks: true, webhooks: true,

View File

@ -1,5 +1,5 @@
import prisma from '@/lib/prisma' import prisma from '@/lib/prisma'
import { canWriteTypebot } from '@/utils/api/dbRules' import { canWriteTypebots } from '@/utils/api/dbRules'
import { authenticatedProcedure } from '@/utils/server/trpc' import { authenticatedProcedure } from '@/utils/server/trpc'
import { TRPCError } from '@trpc/server' import { TRPCError } from '@trpc/server'
import { Typebot, Webhook, WebhookBlock } from 'models' import { Typebot, Webhook, WebhookBlock } from 'models'
@ -30,7 +30,7 @@ export const unsubscribeWebhookProcedure = authenticatedProcedure
) )
.query(async ({ input: { typebotId, blockId }, ctx: { user } }) => { .query(async ({ input: { typebotId, blockId }, ctx: { user } }) => {
const typebot = (await prisma.typebot.findFirst({ const typebot = (await prisma.typebot.findFirst({
where: canWriteTypebot(typebotId, user), where: canWriteTypebots(typebotId, user),
select: { select: {
groups: true, groups: true,
webhooks: true, webhooks: true,

View File

@ -1,5 +1,5 @@
import prisma from '@/lib/prisma' import prisma from '@/lib/prisma'
import { canWriteTypebot } from '@/utils/api/dbRules' import { canWriteTypebots } from '@/utils/api/dbRules'
import { deleteFiles } from '@/utils/api/storage' import { deleteFiles } from '@/utils/api/storage'
import { User, Prisma } from 'db' import { User, Prisma } from 'db'
import { InputBlockType, Typebot } from 'models' import { InputBlockType, Typebot } from 'models'
@ -14,7 +14,7 @@ export const archiveResults = async ({
resultsFilter?: Prisma.ResultWhereInput resultsFilter?: Prisma.ResultWhereInput
}) => { }) => {
const typebot = await prisma.typebot.findFirst({ const typebot = await prisma.typebot.findFirst({
where: canWriteTypebot(typebotId, user), where: canWriteTypebots(typebotId, user),
select: { groups: true }, select: { groups: true },
}) })
if (!typebot) return { success: false } if (!typebot) return { success: false }

View File

@ -1,4 +1,4 @@
import { canWriteTypebot } from '@/utils/api/dbRules' import { canWriteTypebots } from '@/utils/api/dbRules'
import { authenticatedProcedure } from '@/utils/server/trpc' import { authenticatedProcedure } from '@/utils/server/trpc'
import { TRPCError } from '@trpc/server' import { TRPCError } from '@trpc/server'
import { z } from 'zod' import { z } from 'zod'
@ -34,7 +34,7 @@ export const deleteResultsProcedure = authenticatedProcedure
user, user,
resultsFilter: { resultsFilter: {
id: (idsArray?.length ?? 0) > 0 ? { in: idsArray } : undefined, id: (idsArray?.length ?? 0) > 0 ? { in: idsArray } : undefined,
typebot: canWriteTypebot(typebotId, user), typebot: canWriteTypebots(typebotId, user),
}, },
}) })

View File

@ -1,5 +1,5 @@
import prisma from '@/lib/prisma' import prisma from '@/lib/prisma'
import { canReadTypebot } from '@/utils/api/dbRules' import { canReadTypebots } from '@/utils/api/dbRules'
import { authenticatedProcedure } from '@/utils/server/trpc' import { authenticatedProcedure } from '@/utils/server/trpc'
import { logSchema } from 'models' import { logSchema } from 'models'
import { z } from 'zod' import { z } from 'zod'
@ -22,9 +22,14 @@ export const getResultLogsProcedure = authenticatedProcedure
) )
.output(z.object({ logs: z.array(logSchema) })) .output(z.object({ logs: z.array(logSchema) }))
.query(async ({ input: { typebotId, resultId }, ctx: { user } }) => { .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({ const logs = await prisma.log.findMany({
where: { where: {
result: { id: resultId, typebot: canReadTypebot(typebotId, user) }, result: { id: resultId, typebotId: typebot.id },
}, },
}) })

View File

@ -1,5 +1,5 @@
import prisma from '@/lib/prisma' import prisma from '@/lib/prisma'
import { canReadTypebot } from '@/utils/api/dbRules' import { canReadTypebots } from '@/utils/api/dbRules'
import { authenticatedProcedure } from '@/utils/server/trpc' import { authenticatedProcedure } from '@/utils/server/trpc'
import { TRPCError } from '@trpc/server' import { TRPCError } from '@trpc/server'
import { ResultWithAnswers, resultWithAnswersSchema } from 'models' import { ResultWithAnswers, resultWithAnswersSchema } from 'models'
@ -38,11 +38,17 @@ export const getResultsProcedure = authenticatedProcedure
message: 'limit must be between 1 and 200', message: 'limit must be between 1 and 200',
}) })
const { cursor } = input 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({ const results = (await prisma.result.findMany({
take: limit + 1, take: limit + 1,
cursor: cursor ? { id: cursor } : undefined, cursor: cursor ? { id: cursor } : undefined,
where: { where: {
typebot: canReadTypebot(input.typebotId, user), typebotId: typebot.id,
answers: { some: {} }, answers: { some: {} },
}, },
orderBy: { orderBy: {

View File

@ -2,7 +2,7 @@ import { withSentry } from '@sentry/nextjs'
import prisma from '@/lib/prisma' import prisma from '@/lib/prisma'
import { InputBlockType, PublicTypebot } from 'models' import { InputBlockType, PublicTypebot } from 'models'
import { NextApiRequest, NextApiResponse } from 'next' 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 { getAuthenticatedUser } from '@/features/auth/api'
import { badRequest, methodNotAllowed, notAuthenticated } from 'utils/api' import { badRequest, methodNotAllowed, notAuthenticated } from 'utils/api'
@ -39,7 +39,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
await prisma.publicTypebot.deleteMany({ await prisma.publicTypebot.deleteMany({
where: { where: {
id: publishedTypebotId, id: publishedTypebotId,
typebot: canWriteTypebot(typebotId, user), typebot: canWriteTypebots(typebotId, user),
}, },
}) })
return res.send({ success: true }) return res.send({ success: true })

View File

@ -2,7 +2,7 @@ import { withSentry } from '@sentry/nextjs'
import { CollaborationType } from 'db' import { CollaborationType } from 'db'
import prisma from '@/lib/prisma' import prisma from '@/lib/prisma'
import { NextApiRequest, NextApiResponse } from 'next' 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 { methodNotAllowed, notAuthenticated } from 'utils/api'
import { getAuthenticatedUser } from '@/features/auth/api' import { getAuthenticatedUser } from '@/features/auth/api'
import { archiveResults } from '@/features/results/api' import { archiveResults } from '@/features/results/api'
@ -15,7 +15,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method === 'GET') { if (req.method === 'GET') {
const typebot = await prisma.typebot.findFirst({ const typebot = await prisma.typebot.findFirst({
where: { where: {
...canReadTypebot(typebotId, user), ...canReadTypebots(typebotId, user),
isArchived: { not: true }, isArchived: { not: true },
}, },
include: { include: {
@ -46,10 +46,10 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
}) })
if (!success) return res.status(500).send({ success: false }) if (!success) return res.status(500).send({ success: false })
await prisma.publicTypebot.deleteMany({ await prisma.publicTypebot.deleteMany({
where: { typebot: canWriteTypebot(typebotId, user) }, where: { typebot: canWriteTypebots(typebotId, user) },
}) })
const typebots = await prisma.typebot.updateMany({ const typebots = await prisma.typebot.updateMany({
where: canWriteTypebot(typebotId, user), where: canWriteTypebots(typebotId, user),
data: { isArchived: true }, data: { isArchived: true },
}) })
return res.send({ typebots }) return res.send({ typebots })
@ -57,7 +57,8 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method === 'PUT') { if (req.method === 'PUT') {
const data = typeof req.body === 'string' ? JSON.parse(req.body) : req.body const data = typeof req.body === 'string' ? JSON.parse(req.body) : req.body
const existingTypebot = await prisma.typebot.findFirst({ const existingTypebot = await prisma.typebot.findFirst({
where: canReadTypebot(typebotId, user), where: canReadTypebots(typebotId, user),
select: { updatedAt: true },
}) })
if ( if (
existingTypebot && existingTypebot &&
@ -65,7 +66,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
) )
return res.send({ message: 'Found newer version of typebot in database' }) return res.send({ message: 'Found newer version of typebot in database' })
const typebots = await prisma.typebot.updateMany({ const typebots = await prisma.typebot.updateMany({
where: canWriteTypebot(typebotId, user), where: canWriteTypebots(typebotId, user),
data: { data: {
...data, ...data,
theme: data.theme ?? undefined, theme: data.theme ?? undefined,
@ -78,7 +79,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method === 'PATCH') { if (req.method === 'PATCH') {
const data = typeof req.body === 'string' ? JSON.parse(req.body) : req.body const data = typeof req.body === 'string' ? JSON.parse(req.body) : req.body
const typebots = await prisma.typebot.updateMany({ const typebots = await prisma.typebot.updateMany({
where: canWriteTypebot(typebotId, user), where: canWriteTypebots(typebotId, user),
data, data,
}) })
return res.send({ typebots }) return res.send({ typebots })

View File

@ -4,7 +4,7 @@ import { NextApiRequest, NextApiResponse } from 'next'
import { methodNotAllowed, notAuthenticated } from 'utils/api' import { methodNotAllowed, notAuthenticated } from 'utils/api'
import { withSentry } from '@sentry/nextjs' import { withSentry } from '@sentry/nextjs'
import { getAuthenticatedUser } from '@/features/auth/api' 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 handler = async (req: NextApiRequest, res: NextApiResponse) => {
const user = await getAuthenticatedUser(req) const user = await getAuthenticatedUser(req)
@ -12,8 +12,8 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method === 'GET') { if (req.method === 'GET') {
const typebotId = req.query.typebotId as string const typebotId = req.query.typebotId as string
const typebot = await prisma.typebot.findFirst({ const typebot = await prisma.typebot.findFirst({
where: canReadTypebot(typebotId, user), where: canReadTypebots(typebotId, user),
include: { publishedTypebot: true }, select: { publishedTypebot: true },
}) })
const publishedTypebot = const publishedTypebot =
typebot?.publishedTypebot as unknown as PublicTypebot typebot?.publishedTypebot as unknown as PublicTypebot

View File

@ -2,7 +2,7 @@ import { withSentry } from '@sentry/nextjs'
import prisma from '@/lib/prisma' import prisma from '@/lib/prisma'
import { Stats } from 'models' import { Stats } from 'models'
import { NextApiRequest, NextApiResponse } from 'next' import { NextApiRequest, NextApiResponse } from 'next'
import { canReadTypebot } from '@/utils/api/dbRules' import { canReadTypebots } from '@/utils/api/dbRules'
import { getAuthenticatedUser } from '@/features/auth/api' import { getAuthenticatedUser } from '@/features/auth/api'
import { methodNotAllowed, notAuthenticated } from 'utils/api' import { methodNotAllowed, notAuthenticated } from 'utils/api'
@ -12,23 +12,27 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method === 'GET') { if (req.method === 'GET') {
const typebotId = req.query.typebotId as string 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({ const totalViews = await prisma.result.count({
where: { where: {
typebotId, typebotId: typebot.id,
typebot: canReadTypebot(typebotId, user),
}, },
}) })
const totalStarts = await prisma.result.count({ const totalStarts = await prisma.result.count({
where: { where: {
typebotId, typebotId: typebot.id,
typebot: canReadTypebot(typebotId, user),
answers: { some: {} }, answers: { some: {} },
}, },
}) })
const totalCompleted = await prisma.result.count({ const totalCompleted = await prisma.result.count({
where: { where: {
typebotId, typebotId: typebot.id,
typebot: canReadTypebot(typebotId, user),
isCompleted: true, isCompleted: true,
}, },
}) })

View File

@ -1,7 +1,7 @@
import { withSentry } from '@sentry/nextjs' import { withSentry } from '@sentry/nextjs'
import prisma from '@/lib/prisma' import prisma from '@/lib/prisma'
import { NextApiRequest, NextApiResponse } from 'next' import { NextApiRequest, NextApiResponse } from 'next'
import { canReadTypebot } from '@/utils/api/dbRules' import { canReadTypebots } from '@/utils/api/dbRules'
import { getAuthenticatedUser } from '@/features/auth/api' import { getAuthenticatedUser } from '@/features/auth/api'
import { methodNotAllowed, notAuthenticated, notFound } from 'utils/api' import { methodNotAllowed, notAuthenticated, notFound } from 'utils/api'
@ -11,7 +11,8 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method === 'GET') { if (req.method === 'GET') {
const typebotId = req.query.typebotId as string const typebotId = req.query.typebotId as string
const typebot = await prisma.typebot.findFirst({ const typebot = await prisma.typebot.findFirst({
where: canReadTypebot(typebotId, user), where: canReadTypebots(typebotId, user),
select: { groups: true },
}) })
if (!typebot) return notFound(res) if (!typebot) return notFound(res)
return res.send({ groups: typebot.groups }) return res.send({ groups: typebot.groups })

View File

@ -1,7 +1,7 @@
import { withSentry } from '@sentry/nextjs' import { withSentry } from '@sentry/nextjs'
import prisma from '@/lib/prisma' import prisma from '@/lib/prisma'
import { NextApiRequest, NextApiResponse } from 'next' import { NextApiRequest, NextApiResponse } from 'next'
import { canReadTypebot } from '@/utils/api/dbRules' import { canReadTypebots } from '@/utils/api/dbRules'
import { getAuthenticatedUser } from '@/features/auth/api' import { getAuthenticatedUser } from '@/features/auth/api'
import { methodNotAllowed, notAuthenticated } from 'utils/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 const typebotId = req.query.typebotId as string
if (req.method === 'GET') { if (req.method === 'GET') {
const collaborators = await prisma.collaboratorsOnTypebots.findMany({ 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 } } }, include: { user: { select: { name: true, image: true, email: true } } },
}) })
return res.send({ return res.send({

View File

@ -2,7 +2,7 @@ import { withSentry } from '@sentry/nextjs'
import { CollaborationType, WorkspaceRole } from 'db' import { CollaborationType, WorkspaceRole } from 'db'
import prisma from '@/lib/prisma' import prisma from '@/lib/prisma'
import { NextApiRequest, NextApiResponse } from 'next' import { NextApiRequest, NextApiResponse } from 'next'
import { canReadTypebot, canWriteTypebot } from '@/utils/api/dbRules' import { canReadTypebots, canWriteTypebots } from '@/utils/api/dbRules'
import { import {
badRequest, badRequest,
forbidden, forbidden,
@ -19,7 +19,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const typebotId = req.query.typebotId as string const typebotId = req.query.typebotId as string
if (req.method === 'GET') { if (req.method === 'GET') {
const invitations = await prisma.invitation.findMany({ const invitations = await prisma.invitation.findMany({
where: { typebotId, typebot: canReadTypebot(typebotId, user) }, where: { typebotId, typebot: canReadTypebots(typebotId, user) },
}) })
return res.send({ return res.send({
invitations, invitations,
@ -27,7 +27,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
} }
if (req.method === 'POST') { if (req.method === 'POST') {
const typebot = await prisma.typebot.findFirst({ const typebot = await prisma.typebot.findFirst({
where: canWriteTypebot(typebotId, user), where: canWriteTypebots(typebotId, user),
include: { workspace: { select: { name: true } } }, include: { workspace: { select: { name: true } } },
}) })
if (!typebot || !typebot.workspaceId) return forbidden(res) if (!typebot || !typebot.workspaceId) return forbidden(res)

View File

@ -2,7 +2,7 @@ import { withSentry } from '@sentry/nextjs'
import prisma from '@/lib/prisma' import prisma from '@/lib/prisma'
import { defaultWebhookAttributes } from 'models' import { defaultWebhookAttributes } from 'models'
import { NextApiRequest, NextApiResponse } from 'next' import { NextApiRequest, NextApiResponse } from 'next'
import { canWriteTypebot } from '@/utils/api/dbRules' import { canWriteTypebots } from '@/utils/api/dbRules'
import { getAuthenticatedUser } from '@/features/auth/api' import { getAuthenticatedUser } from '@/features/auth/api'
import { forbidden, methodNotAllowed, notAuthenticated } from 'utils/api' import { forbidden, methodNotAllowed, notAuthenticated } from 'utils/api'
@ -12,7 +12,8 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method === 'POST') { if (req.method === 'POST') {
const typebotId = req.query.typebotId as string const typebotId = req.query.typebotId as string
const typebot = await prisma.typebot.findFirst({ const typebot = await prisma.typebot.findFirst({
where: canWriteTypebot(typebotId, user), where: canWriteTypebots(typebotId, user),
select: { id: true },
}) })
if (!typebot) return forbidden(res) if (!typebot) return forbidden(res)
const webhook = await prisma.webhook.create({ const webhook = await prisma.webhook.create({

View File

@ -11,21 +11,30 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const workspaceId = req.query.workspaceId as string const workspaceId = req.query.workspaceId as string
const now = new Date() const now = new Date()
const firstDayOfMonth = new Date(now.getFullYear(), now.getMonth(), 1) const firstDayOfMonth = new Date(now.getFullYear(), now.getMonth(), 1)
const lastDayOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 1) const firstDayOfNextMonth = new Date(
const totalChatsUsed = await prisma.result.count({ now.getFullYear(),
where: { now.getMonth() + 1,
typebot: { 1
)
const totalChatsUsed = await prisma.$transaction(async (tx) => {
const typebots = await tx.typebot.findMany({
where: {
workspace: { workspace: {
id: workspaceId, id: workspaceId,
members: { some: { userId: user.id } }, members: { some: { userId: user.id } },
}, },
}, },
hasStarted: true, })
createdAt: { return tx.result.count({
gte: firstDayOfMonth, where: {
lte: lastDayOfMonth, typebotId: { in: typebots.map((typebot) => typebot.id) },
hasStarted: true,
createdAt: {
gte: firstDayOfMonth,
lt: firstDayOfNextMonth,
},
}, },
}, })
}) })
const { const {
_sum: { storageUsed: totalStorageUsed }, _sum: { storageUsed: totalStorageUsed },

View File

@ -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 prisma from '@/lib/prisma'
import { NextApiResponse } from 'next' import { NextApiResponse } from 'next'
import { env, isNotEmpty } from 'utils' import { env, isNotEmpty } from 'utils'
import { forbidden } from 'utils/api' import { forbidden } from 'utils/api'
const parseWhereFilter = ( export const canWriteTypebots = (
typebotIds: string[] | string, typebotIds: string[] | string,
user: User, user: Pick<User, 'email' | 'id'>
type: 'read' | 'write'
): Prisma.TypebotWhereInput => ({ ): Prisma.TypebotWhereInput => ({
OR: [ id: typeof typebotIds === 'string' ? typebotIds : { in: typebotIds },
{ workspace: isNotEmpty(env('E2E_TEST'))
id: typeof typebotIds === 'string' ? typebotIds : { in: typebotIds }, ? undefined
collaborators: { : {
some: { members: {
userId: user.id, some: { userId: user.id, role: { not: WorkspaceRole.GUEST } },
type: type === 'write' ? CollaborationType.WRITE : undefined,
}, },
}, },
},
{
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) => export const canReadTypebots = (
parseWhereFilter(typebotId, user, 'read') typebotIds: string | string[],
user: Pick<User, 'email' | 'id'>
export const canWriteTypebot = (typebotId: string, user: User) => ) => ({
parseWhereFilter(typebotId, user, 'write') id: typeof typebotIds === 'string' ? typebotIds : { in: typebotIds },
workspace:
export const canReadTypebots = (typebotIds: string[], user: User) => user.email === process.env.ADMIN_EMAIL || isNotEmpty(env('E2E_TEST'))
parseWhereFilter(typebotIds, user, 'read') ? undefined
: {
export const canWriteTypebots = (typebotIds: string[], user: User) => members: {
parseWhereFilter(typebotIds, user, 'write') some: { userId: user.id },
},
},
})
export const canEditGuests = (user: User, typebotId: string) => ({ export const canEditGuests = (user: User, typebotId: string) => ({
id: typebotId, id: typebotId,

View File

@ -32,7 +32,7 @@ const handler = async (
const typebotId = req.query.typebotId as string const typebotId = req.query.typebotId as string
const blockId = req.query.blockId as string const blockId = req.query.blockId as string
if (!filePath) return badRequest(res, 'Missing filePath or fileType') 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({ const typebot = (await prisma.publicTypebot.findFirst({
where: { typebotId }, where: { typebotId },
})) as unknown as PublicTypebot })) as unknown as PublicTypebot
@ -53,14 +53,14 @@ const handler = async (
return res.status(200).send({ return res.status(200).send({
presignedUrl, presignedUrl,
hasReachedStorageLimit: false, hasReachedStorageLimit,
}) })
} }
return methodNotAllowed(res) return methodNotAllowed(res)
} }
const checkStorageLimit = async (typebotId: string): Promise<boolean> => { const checkStorageLimit = async (typebotId: string): Promise<boolean> => {
const typebot = await prisma.typebot.findFirst({ const typebot = await prisma.typebot.findUnique({
where: { id: typebotId }, where: { id: typebotId },
include: { include: {
workspace: { workspace: {
@ -84,9 +84,7 @@ const checkStorageLimit = async (typebotId: string): Promise<boolean> => {
storageUsed: { gt: 0 }, storageUsed: { gt: 0 },
result: { result: {
typebot: { typebot: {
workspace: { workspaceId: typebot.workspaceId,
id: typebot?.workspaceId,
},
}, },
}, },
}, },

View File

@ -5,7 +5,7 @@ import {
sendAlmostReachedChatsLimitEmail, sendAlmostReachedChatsLimitEmail,
sendReachedChatsLimitEmail, sendReachedChatsLimitEmail,
} from 'emails' } from 'emails'
import { ResultWithAnswers, Workspace } from 'models' import { ResultWithAnswers } from 'models'
import { NextApiRequest, NextApiResponse } from 'next' import { NextApiRequest, NextApiResponse } from 'next'
import { env, getChatsLimit, isDefined } from 'utils' import { env, getChatsLimit, isDefined } from 'utils'
import { methodNotAllowed } from 'utils/api' import { methodNotAllowed } from 'utils/api'
@ -33,58 +33,63 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
} }
if (req.method === 'POST') { if (req.method === 'POST') {
const typebotId = req.query.typebotId as string 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({ const result = await prisma.result.create({
data: { data: {
typebotId, typebotId,
isCompleted: false, 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 })
res.send({ result, hasReachedLimit: false })
return return
} }
methodNotAllowed(res) methodNotAllowed(res)
} }
const checkChatsUsage = async ( const checkChatsUsage = async (typebotId: string) => {
workspace: Pick< const typebot = await prisma.typebot.findUnique({
Workspace, where: {
| 'id' id: typebotId,
| 'plan' },
| 'additionalChatsIndex' include: {
| 'chatsLimitFirstEmailSentAt' workspace: {
| 'chatsLimitSecondEmailSentAt' select: {
| 'customChatsLimit' id: true,
> plan: true,
) => { additionalChatsIndex: true,
chatsLimitFirstEmailSentAt: true,
chatsLimitSecondEmailSentAt: true,
customChatsLimit: true,
},
},
},
})
const workspace = typebot?.workspace
if (!workspace) return false
const chatsLimit = getChatsLimit(workspace) const chatsLimit = getChatsLimit(workspace)
if (chatsLimit === -1) return if (chatsLimit === -1) return
const now = new Date() const now = new Date()
const firstDayOfMonth = new Date(now.getFullYear(), now.getMonth(), 1) 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 firstDayOfNextMonth = new Date(now.getFullYear(), now.getMonth() + 1, 1)
const chatsCount = await prisma.result.count({ const chatsCount = await prisma.$transaction(async (tx) => {
where: { const typebotIds = await tx.typebot.findMany({
typebot: { workspaceId: workspace.id }, where: {
hasStarted: true, workspaceId: workspace.id,
createdAt: { gte: firstDayOfMonth, lte: lastDayOfMonth }, },
}, select: { id: true },
})
return tx.result.count({
where: {
typebotId: { in: typebotIds.map((typebot) => typebot.id) },
hasStarted: true,
createdAt: { gte: firstDayOfMonth, lte: firstDayOfNextMonth },
},
})
}) })
const hasSentFirstEmail = const hasSentFirstEmail =
workspace.chatsLimitFirstEmailSentAt !== null && workspace.chatsLimitFirstEmailSentAt !== null &&

View File

@ -0,0 +1,2 @@
-- CreateIndex
CREATE INDEX IF NOT EXISTS "Result_typebotId_idx" ON "Result"("typebotId");

View File

@ -224,6 +224,7 @@ model Result {
answers Answer[] answers Answer[]
logs Log[] logs Log[]
@@index([typebotId])
@@index([typebotId, createdAt]) @@index([typebotId, createdAt])
@@index([createdAt, typebotId]) @@index([createdAt, typebotId])
} }

View File

@ -1,9 +1,90 @@
import { PrismaClient } from 'db' import { Prisma, PrismaClient, User, WorkspaceRole } from 'db'
import { env, isNotEmpty } from 'utils'
import { promptAndSetEnvironment } from './utils' import { promptAndSetEnvironment } from './utils'
const executePlayground = async () => { const executePlayground = async () => {
await promptAndSetEnvironment() 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<User, 'id' | 'email'>
},
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() executePlayground()