@@ -6,23 +6,27 @@ import {
6
6
} from '@typebot.io/prisma'
7
7
import { isDefined , isEmpty } from '@typebot.io/lib'
8
8
import { getChatsLimit } from '@typebot.io/lib/billing/getChatsLimit'
9
+ import { getUsage } from '@typebot.io/lib/api/getUsage'
9
10
import { promptAndSetEnvironment } from './utils'
11
+ import { Workspace } from '@typebot.io/schemas'
12
+ import { sendAlmostReachedChatsLimitEmail } from '@typebot.io/emails/src/emails/AlmostReachedChatsLimitEmail'
10
13
import { TelemetryEvent } from '@typebot.io/schemas/features/telemetry'
11
14
import { sendTelemetryEvents } from '@typebot.io/lib/telemetry/sendTelemetryEvent'
12
- import { Workspace } from '@typebot.io/schemas'
13
- import { Stripe } from 'stripe'
15
+ import Stripe from 'stripe'
14
16
import { createId } from '@paralleldrive/cuid2'
15
17
16
18
const prisma = new PrismaClient ( )
19
+ const LIMIT_EMAIL_TRIGGER_PERCENT = 0.75
17
20
18
21
type WorkspaceForDigest = Pick <
19
22
Workspace ,
20
23
| 'id'
21
24
| 'plan'
25
+ | 'name'
22
26
| 'customChatsLimit'
23
- | 'customStorageLimit'
24
- | 'additionalStorageIndex'
25
27
| 'isQuarantined'
28
+ | 'chatsLimitFirstEmailSentAt'
29
+ | 'chatsLimitSecondEmailSentAt'
26
30
> & {
27
31
members : ( Pick < MemberInWorkspace , 'role' > & {
28
32
user : { id : string ; email : string | null }
@@ -55,14 +59,14 @@ type ResultWithWorkspace = {
55
59
isFirstOfKind : true | undefined
56
60
}
57
61
58
- export const sendTotalResultsDigest = async ( ) => {
62
+ export const checkAndReportChatsUsage = async ( ) => {
59
63
await promptAndSetEnvironment ( 'production' )
60
64
61
- console . log ( "Generating total results yesterday's digest ..." )
62
- const todayMidnight = new Date ( )
63
- todayMidnight . setUTCHours ( 0 , 0 , 0 , 0 )
64
- const yesterday = new Date ( todayMidnight )
65
- yesterday . setDate ( yesterday . getDate ( ) - 1 )
65
+ console . log ( 'Get collected results from the last hour ...' )
66
+
67
+ const zeroedMinutesHour = new Date ( )
68
+ zeroedMinutesHour . setUTCMinutes ( 0 , 0 , 0 )
69
+ const hourAgo = new Date ( zeroedMinutesHour . getTime ( ) - 1000 * 60 * 60 )
66
70
67
71
const results = await prisma . result . groupBy ( {
68
72
by : [ 'typebotId' ] ,
@@ -72,8 +76,8 @@ export const sendTotalResultsDigest = async () => {
72
76
where : {
73
77
hasStarted : true ,
74
78
createdAt : {
75
- gte : yesterday ,
76
- lt : todayMidnight ,
79
+ lt : zeroedMinutesHour ,
80
+ gte : hourAgo ,
77
81
} ,
78
82
} ,
79
83
} )
@@ -82,7 +86,7 @@ export const sendTotalResultsDigest = async () => {
82
86
`Found ${ results . reduce (
83
87
( total , result ) => total + result . _count . _all ,
84
88
0
85
- ) } results collected yesterday .`
89
+ ) } results collected for the last hour .`
86
90
)
87
91
88
92
const workspaces = await prisma . workspace . findMany ( {
@@ -95,6 +99,7 @@ export const sendTotalResultsDigest = async () => {
95
99
} ,
96
100
select : {
97
101
id : true ,
102
+ name : true ,
98
103
typebots : { select : { id : true } } ,
99
104
members : {
100
105
select : { user : { select : { id : true , email : true } } , role : true } ,
@@ -104,6 +109,8 @@ export const sendTotalResultsDigest = async () => {
104
109
customStorageLimit : true ,
105
110
plan : true ,
106
111
isQuarantined : true ,
112
+ chatsLimitFirstEmailSentAt : true ,
113
+ chatsLimitSecondEmailSentAt : true ,
107
114
stripeId : true ,
108
115
} ,
109
116
} )
@@ -124,20 +131,18 @@ export const sendTotalResultsDigest = async () => {
124
131
isFirstOfKind : memberIndex === 0 ? ( true as const ) : undefined ,
125
132
} ) )
126
133
} )
127
- . filter ( isDefined ) satisfies ResultWithWorkspace [ ]
128
-
129
- console . log ( 'Reporting usage to Stripe...' )
134
+ . filter ( isDefined )
130
135
131
- await reportUsageToStripe ( resultsWithWorkspaces )
132
-
133
- console . log ( 'Computing workspaces limits...' )
136
+ console . log ( 'Check limits...' )
134
137
135
- const workspaceLimitReachedEvents = await sendAlertIfLimitReached (
138
+ const events = await sendAlertIfLimitReached (
136
139
resultsWithWorkspaces
137
140
. filter ( ( result ) => result . isFirstOfKind )
138
141
. map ( ( result ) => result . workspace )
139
142
)
140
143
144
+ await reportUsageToStripe ( resultsWithWorkspaces )
145
+
141
146
const newResultsCollectedEvents = resultsWithWorkspaces . map (
142
147
( result ) =>
143
148
( {
@@ -152,16 +157,11 @@ export const sendTotalResultsDigest = async () => {
152
157
} satisfies TelemetryEvent )
153
158
)
154
159
155
- await sendTelemetryEvents (
156
- workspaceLimitReachedEvents . concat ( newResultsCollectedEvents )
157
- )
158
-
159
- console . log (
160
- `Sent ${ workspaceLimitReachedEvents . length } workspace limit reached events.`
161
- )
162
160
console . log (
163
- `Sent ${ newResultsCollectedEvents . length } new results collected events.`
161
+ `Send ${ newResultsCollectedEvents . length } new results events and ${ events . length } auto quarantine events.. .`
164
162
)
163
+
164
+ await sendTelemetryEvents ( events . concat ( newResultsCollectedEvents ) )
165
165
}
166
166
167
167
const sendAlertIfLimitReached = async (
@@ -173,16 +173,50 @@ const sendAlertIfLimitReached = async (
173
173
if ( taggedWorkspaces . includes ( workspace . id ) || workspace . isQuarantined )
174
174
continue
175
175
taggedWorkspaces . push ( workspace . id )
176
- const { totalChatsUsed } = await getUsage ( workspace . id )
176
+ const { totalChatsUsed } = await getUsage ( prisma ) ( workspace . id )
177
177
const chatsLimit = getChatsLimit ( workspace )
178
- if ( chatsLimit > 0 && totalChatsUsed >= chatsLimit ) {
178
+ if (
179
+ chatsLimit > 0 &&
180
+ totalChatsUsed >= chatsLimit * LIMIT_EMAIL_TRIGGER_PERCENT &&
181
+ totalChatsUsed < chatsLimit &&
182
+ ! workspace . chatsLimitFirstEmailSentAt
183
+ ) {
184
+ const to = workspace . members
185
+ . filter ( ( member ) => member . role === WorkspaceRole . ADMIN )
186
+ . map ( ( member ) => member . user . email )
187
+ . filter ( isDefined )
188
+ console . log (
189
+ `Send almost reached chats limit email to ${ to . join ( ', ' ) } ...`
190
+ )
191
+ try {
192
+ await sendAlmostReachedChatsLimitEmail ( {
193
+ to,
194
+ usagePercent : Math . round ( ( totalChatsUsed / chatsLimit ) * 100 ) ,
195
+ chatsLimit,
196
+ workspaceName : workspace . name ,
197
+ } )
198
+ await prisma . workspace . updateMany ( {
199
+ where : { id : workspace . id } ,
200
+ data : { chatsLimitFirstEmailSentAt : new Date ( ) } ,
201
+ } )
202
+ } catch ( err ) {
203
+ console . error ( err )
204
+ }
205
+ }
206
+
207
+ if ( totalChatsUsed > chatsLimit * 1.5 && workspace . plan === Plan . FREE ) {
208
+ console . log ( `Automatically quarantine workspace ${ workspace . id } ...` )
209
+ await prisma . workspace . updateMany ( {
210
+ where : { id : workspace . id } ,
211
+ data : { isQuarantined : true } ,
212
+ } )
179
213
events . push (
180
214
...workspace . members
181
215
. filter ( ( member ) => member . role === WorkspaceRole . ADMIN )
182
216
. map (
183
217
( member ) =>
184
218
( {
185
- name : 'Workspace limit reached ' ,
219
+ name : 'Workspace automatically quarantined ' ,
186
220
userId : member . user . id ,
187
221
workspaceId : workspace . id ,
188
222
data : {
@@ -192,7 +226,6 @@ const sendAlertIfLimitReached = async (
192
226
} satisfies TelemetryEvent )
193
227
)
194
228
)
195
- continue
196
229
}
197
230
}
198
231
return events
@@ -261,35 +294,4 @@ const reportUsageToStripe = async (
261
294
}
262
295
}
263
296
264
- const getUsage = async ( workspaceId : string ) => {
265
- const now = new Date ( )
266
- const firstDayOfMonth = new Date ( now . getFullYear ( ) , now . getMonth ( ) , 1 )
267
- const firstDayOfNextMonth = new Date ( now . getFullYear ( ) , now . getMonth ( ) + 1 , 1 )
268
- const typebots = await prisma . typebot . findMany ( {
269
- where : {
270
- workspace : {
271
- id : workspaceId ,
272
- } ,
273
- } ,
274
- select : { id : true } ,
275
- } )
276
-
277
- const [ totalChatsUsed ] = await Promise . all ( [
278
- prisma . result . count ( {
279
- where : {
280
- typebotId : { in : typebots . map ( ( typebot ) => typebot . id ) } ,
281
- hasStarted : true ,
282
- createdAt : {
283
- gte : firstDayOfMonth ,
284
- lt : firstDayOfNextMonth ,
285
- } ,
286
- } ,
287
- } ) ,
288
- ] )
289
-
290
- return {
291
- totalChatsUsed,
292
- }
293
- }
294
-
295
- sendTotalResultsDigest ( ) . then ( )
297
+ checkAndReportChatsUsage ( ) . then ( )
0 commit comments