2
0

Add usage-based new pricing plans

This commit is contained in:
Baptiste Arnaud
2022-09-17 16:37:33 +02:00
committed by Baptiste Arnaud
parent 6a1eaea700
commit 898367a33b
144 changed files with 4631 additions and 1624 deletions

View File

@ -1,8 +1,22 @@
import { withSentry } from '@sentry/nextjs'
import { almostReachedStorageLimitEmail } from 'assets/emails/almostReachedStorageLimitEmail'
import { reachedStorageLimitEmail } from 'assets/emails/reachedStorageLimitEmail'
import { WorkspaceRole } from 'db'
import prisma from 'libs/prisma'
import { InputBlockType, PublicTypebot } from 'models'
import { NextApiRequest, NextApiResponse } from 'next'
import { badRequest, generatePresignedUrl, methodNotAllowed, byId } from 'utils'
import {
badRequest,
generatePresignedUrl,
methodNotAllowed,
byId,
getStorageLimit,
sendEmailNotification,
isDefined,
env,
} from 'utils'
const LIMIT_EMAIL_TRIGGER_PERCENT = 0.8
const handler = async (
req: NextApiRequest,
@ -24,6 +38,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 typebot = (await prisma.publicTypebot.findFirst({
where: { typebotId },
})) as unknown as PublicTypebot
@ -42,9 +57,118 @@ const handler = async (
sizeLimit: sizeLimit * 1024 * 1024,
})
return res.status(200).send({ presignedUrl })
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,
},
},
},
})
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)
if (
totalStorageUsed >= storageLimit * LIMIT_EMAIL_TRIGGER_PERCENT &&
!hasSentFirstEmail &&
env('E2E_TEST') !== 'true'
)
sendAlmostReachStorageLimitEmail({
workspaceId: workspace.id,
storageLimit,
})
if (
totalStorageUsed >= storageLimit &&
!hasSentSecondEmail &&
env('E2E_TEST') !== 'true'
)
sendReachStorageLimitEmail({
workspaceId: workspace.id,
storageLimit,
})
return (totalStorageUsed ?? 0) >= getStorageLimit(typebot?.workspace)
}
const sendAlmostReachStorageLimitEmail = async ({
workspaceId,
storageLimit,
}: {
workspaceId: string
storageLimit: number
}) => {
const members = await prisma.memberInWorkspace.findMany({
where: { role: WorkspaceRole.ADMIN, workspaceId },
include: { user: { select: { email: true } } },
})
const readableStorageLimit = `${storageLimit}GB`
await sendEmailNotification({
to: members.map((member) => member.user.email).filter(isDefined),
subject: "You're close to your storage limit",
html: almostReachedStorageLimitEmail({
readableStorageLimit,
url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspaceId}`,
}),
})
await prisma.workspace.update({
where: { id: workspaceId },
data: { storageLimitFirstEmailSentAt: new Date() },
})
}
const sendReachStorageLimitEmail = async ({
workspaceId,
storageLimit,
}: {
workspaceId: string
storageLimit: number
}) => {
const members = await prisma.memberInWorkspace.findMany({
where: { role: WorkspaceRole.ADMIN, workspaceId },
include: { user: { select: { email: true } } },
})
const readableStorageLimit = `${storageLimit}GB`
await sendEmailNotification({
to: members.map((member) => member.user.email).filter(isDefined),
subject: "You've hit your storage limit",
html: reachedStorageLimitEmail({
readableStorageLimit,
url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspaceId}`,
}),
})
await prisma.workspace.update({
where: { id: workspaceId },
data: { storageLimitSecondEmailSentAt: new Date() },
})
}
export default withSentry(handler)

View File

@ -25,7 +25,7 @@ import {
saveSuccessLog,
} from 'services/api/utils'
import Mail from 'nodemailer/lib/mailer'
import { newLeadEmailContent } from 'assets/newLeadEmailContent'
import { newLeadEmailContent } from 'assets/emails/newLeadEmailContent'
const cors = initMiddleware(Cors())

View File

@ -1,8 +1,20 @@
import { almostReachedChatsLimitEmail } from 'assets/emails/almostReachedChatsLimitEmail'
import { reachedSChatsLimitEmail } from 'assets/emails/reachedChatsLimitEmail'
import { Workspace, WorkspaceRole } from 'db'
import prisma from 'libs/prisma'
import { ResultWithAnswers } from 'models'
import { NextApiRequest, NextApiResponse } from 'next'
import { authenticateUser } from 'services/api/utils'
import { methodNotAllowed } from 'utils'
import {
env,
getChatsLimit,
isDefined,
methodNotAllowed,
parseNumberWithCommas,
sendEmailNotification,
} from 'utils'
const LIMIT_EMAIL_TRIGGER_PERCENT = 0.8
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method === 'GET') {
@ -30,10 +42,150 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
typebotId,
isCompleted: false,
},
include: {
typebot: {
include: {
workspace: {
select: {
id: true,
plan: true,
additionalChatsIndex: true,
chatsLimitFirstEmailSentAt: true,
chatsLimitSecondEmailSentAt: true,
},
},
},
},
},
})
return res.send(result)
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'
>
) => {
const chatLimit = getChatsLimit(workspace)
if (chatLimit === -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 >= chatLimit * LIMIT_EMAIL_TRIGGER_PERCENT &&
!hasSentFirstEmail &&
env('E2E_TEST') !== 'true'
)
await sendAlmostReachChatsLimitEmail({
workspaceId: workspace.id,
chatLimit,
firstDayOfNextMonth,
})
if (
chatsCount >= chatLimit &&
!hasSentSecondEmail &&
env('E2E_TEST') !== 'true'
)
await sendReachedAlertEmail({
workspaceId: workspace.id,
chatLimit,
firstDayOfNextMonth,
})
return chatsCount >= chatLimit
}
const sendAlmostReachChatsLimitEmail = async ({
workspaceId,
chatLimit,
firstDayOfNextMonth,
}: {
workspaceId: string
chatLimit: number
firstDayOfNextMonth: Date
}) => {
const members = await prisma.memberInWorkspace.findMany({
where: { role: WorkspaceRole.ADMIN, workspaceId },
include: { user: { select: { email: true } } },
})
const readableChatsLimit = parseNumberWithCommas(chatLimit)
const readableResetDate = firstDayOfNextMonth
.toDateString()
.split(' ')
.slice(1, 4)
.join(' ')
await sendEmailNotification({
to: members.map((member) => member.user.email).filter(isDefined),
subject: "You've been invited to collaborate 🤝",
html: almostReachedChatsLimitEmail({
readableChatsLimit,
readableResetDate,
url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspaceId}`,
}),
})
await prisma.workspace.update({
where: { id: workspaceId },
data: { chatsLimitFirstEmailSentAt: new Date() },
})
}
const sendReachedAlertEmail = async ({
workspaceId,
chatLimit,
firstDayOfNextMonth,
}: {
workspaceId: string
chatLimit: number
firstDayOfNextMonth: Date
}) => {
const members = await prisma.memberInWorkspace.findMany({
where: { role: WorkspaceRole.ADMIN, workspaceId },
include: { user: { select: { email: true } } },
})
const readableChatsLimit = parseNumberWithCommas(chatLimit)
const readableResetDate = firstDayOfNextMonth
.toDateString()
.split(' ')
.slice(1, 4)
.join(' ')
await sendEmailNotification({
to: members.map((member) => member.user.email).filter(isDefined),
subject: "You've been invited to collaborate 🤝",
html: reachedSChatsLimitEmail({
readableChatsLimit,
readableResetDate,
url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspaceId}`,
}),
})
await prisma.workspace.update({
where: { id: workspaceId },
data: { chatsLimitSecondEmailSentAt: new Date() },
})
}
export default handler

View File

@ -13,11 +13,14 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
let storageUsed = 0
if (uploadedFiles && answer.content.includes('http')) {
const fileUrls = answer.content.split(', ')
for (const url of fileUrls) {
const { headers } = await got(url)
const size = headers['content-length']
if (isNotDefined(size)) return
storageUsed += parseInt(size, 10)
const hasReachedStorageLimit = fileUrls[0] === null
if (!hasReachedStorageLimit) {
for (const url of fileUrls) {
const { headers } = await got(url)
const size = headers['content-length']
if (isNotDefined(size)) return
storageUsed += parseInt(size, 10)
}
}
}
const result = await prisma.answer.upsert({