@@ -84,12 +84,22 @@ export async function POST(request: NextRequest) {
8484 ) ;
8585 const queue = getDMQueue ( ) ;
8686
87- for ( const event of commentEvents ) {
88- const account = await prisma . instagramAccount . findUnique ( {
89- where : { instagramId : event . instagramAccountId } ,
90- select : { workspaceId : true } ,
91- } ) ;
87+ const commentAccountIds = [
88+ ...new Set ( commentEvents . map ( ( e ) => e . instagramAccountId ) ) ,
89+ ] ;
90+ const commentAccounts =
91+ commentAccountIds . length > 0
92+ ? await prisma . instagramAccount . findMany ( {
93+ where : { instagramId : { in : commentAccountIds } } ,
94+ select : { instagramId : true , workspaceId : true } ,
95+ } )
96+ : [ ] ;
97+ const commentAccountMap = new Map (
98+ commentAccounts . map ( ( a ) => [ a . instagramId , a . workspaceId ] )
99+ ) ;
92100
101+ let firstCommentWorkspaceId : string | null = null ;
102+ for ( const event of commentEvents ) {
93103 await queue . add (
94104 "process-comment" ,
95105 {
@@ -106,14 +116,19 @@ export async function POST(request: NextRequest) {
106116 }
107117 ) ;
108118
109- if ( account ) {
110- await prisma . webhookEvent . update ( {
111- where : { id : webhookEvent . id } ,
112- data : { workspaceId : account . workspaceId } ,
113- } ) ;
119+ const wsId = commentAccountMap . get ( event . instagramAccountId ) ;
120+ if ( wsId && ! firstCommentWorkspaceId ) {
121+ firstCommentWorkspaceId = wsId ;
114122 }
115123 }
116124
125+ if ( firstCommentWorkspaceId ) {
126+ await prisma . webhookEvent . update ( {
127+ where : { id : webhookEvent . id } ,
128+ data : { workspaceId : firstCommentWorkspaceId } ,
129+ } ) ;
130+ }
131+
117132 // Button taps from opening DMs → deliver the reveal message.
118133 const postbackEvents = parsePostbackEvents (
119134 payload as Parameters < typeof parsePostbackEvents > [ 0 ]
@@ -143,12 +158,22 @@ export async function POST(request: NextRequest) {
143158 payload as Parameters < typeof parseMessageEvents > [ 0 ]
144159 ) ;
145160
146- for ( const event of messageEvents ) {
147- const account = await prisma . instagramAccount . findUnique ( {
148- where : { instagramId : event . instagramAccountId } ,
149- select : { workspaceId : true } ,
150- } ) ;
161+ const messageAccountIds = [
162+ ...new Set ( messageEvents . map ( ( e ) => e . instagramAccountId ) ) ,
163+ ] ;
164+ const messageAccounts =
165+ messageAccountIds . length > 0
166+ ? await prisma . instagramAccount . findMany ( {
167+ where : { instagramId : { in : messageAccountIds } } ,
168+ select : { instagramId : true , workspaceId : true } ,
169+ } )
170+ : [ ] ;
171+ const messageAccountMap = new Map (
172+ messageAccounts . map ( ( a ) => [ a . instagramId , a . workspaceId ] )
173+ ) ;
151174
175+ let firstMessageWorkspaceId : string | null = null ;
176+ for ( const event of messageEvents ) {
152177 await queue . add (
153178 MESSAGE_JOB_NAME ,
154179 {
@@ -168,35 +193,59 @@ export async function POST(request: NextRequest) {
168193 }
169194 ) ;
170195
171- if ( account ) {
172- await prisma . webhookEvent . update ( {
173- where : { id : webhookEvent . id } ,
174- data : { workspaceId : account . workspaceId } ,
175- } ) ;
196+ const wsId = messageAccountMap . get ( event . instagramAccountId ) ;
197+ if ( wsId && ! firstMessageWorkspaceId ) {
198+ firstMessageWorkspaceId = wsId ;
176199 }
177200 }
178201
202+ if ( firstMessageWorkspaceId ) {
203+ await prisma . webhookEvent . update ( {
204+ where : { id : webhookEvent . id } ,
205+ data : { workspaceId : firstMessageWorkspaceId } ,
206+ } ) ;
207+ }
208+
179209 // If a user reads the opening DM and never taps the button, deliver the
180210 // same next-step DM after five minutes. The worker no-ops this delayed job
181211 // if a real button tap has already delivered the reveal.
182212 const readEvents = parseReadEvents (
183213 payload as Parameters < typeof parseReadEvents > [ 0 ]
184214 ) ;
185215
216+ // Batch: group read events by instagramAccountId to avoid N+1 queries.
217+ const readEventsByAccount = new Map <
218+ string ,
219+ { userId : string ; dedupeKey : string } [ ]
220+ > ( ) ;
186221 for ( const event of readEvents ) {
222+ const key = event . instagramAccountId ;
223+ const list = readEventsByAccount . get ( key ) ?? [ ] ;
224+ list . push ( {
225+ userId : event . userId ,
226+ dedupeKey : `${ event . userId } :${ event . instagramAccountId } ` ,
227+ } ) ;
228+ readEventsByAccount . set ( key , list ) ;
229+ }
230+
231+ // One query per distinct instagramAccountId (typically 1) instead of one
232+ // per read event.
233+ for ( const [ igAccountId , events ] of readEventsByAccount ) {
234+ const userIds = [ ...new Set ( events . map ( ( e ) => e . userId ) ) ] ;
187235 const openingLogs = await prisma . dmLog . findMany ( {
188236 where : {
189- commenterId : event . userId ,
237+ commenterId : { in : userIds } ,
190238 status : "SENT" ,
191239 automation : {
192240 isActive : true ,
193241 openingDmEnabled : true ,
194242 instagramAccount : {
195- instagramId : event . instagramAccountId ,
243+ instagramId : igAccountId ,
196244 } ,
197245 } ,
198246 } ,
199247 select : {
248+ commenterId : true ,
200249 automation : {
201250 select : {
202251 id : true ,
@@ -205,25 +254,34 @@ export async function POST(request: NextRequest) {
205254 } ,
206255 } ) ;
207256
208- const scheduledAutomationIds = new Set < string > ( ) ;
257+ // Build a map of userId → Set<automationId> for dedup.
258+ const scheduledByUser = new Map < string , Set < string > > ( ) ;
209259 for ( const log of openingLogs ) {
210- const automation = log . automation ;
211- if ( scheduledAutomationIds . has ( automation . id ) ) continue ;
212- scheduledAutomationIds . add ( automation . id ) ;
213-
214- await queue . add (
215- POSTBACK_JOB_NAME ,
216- {
217- instagramAccountId : event . instagramAccountId ,
218- userId : event . userId ,
219- payload : `reveal:${ automation . id } ` ,
220- fallback : true ,
221- } ,
222- {
223- delay : OPENING_DM_READ_FALLBACK_DELAY_MS ,
224- jobId : `read_fallback_${ event . instagramAccountId } _${ event . userId } _${ automation . id } ` ,
225- }
226- ) ;
260+ const userId = log . commenterId ;
261+ const automationId = log . automation . id ;
262+ const set = scheduledByUser . get ( userId ) ?? new Set ( ) ;
263+ set . add ( automationId ) ;
264+ scheduledByUser . set ( userId , set ) ;
265+ }
266+
267+ // Schedule fallback jobs only for automations not already scheduled.
268+ for ( const event of events ) {
269+ const scheduled = scheduledByUser . get ( event . userId ) ?? new Set ( ) ;
270+ for ( const automationId of scheduled ) {
271+ await queue . add (
272+ POSTBACK_JOB_NAME ,
273+ {
274+ instagramAccountId : igAccountId ,
275+ userId : event . userId ,
276+ payload : `reveal:${ automationId } ` ,
277+ fallback : true ,
278+ } ,
279+ {
280+ delay : OPENING_DM_READ_FALLBACK_DELAY_MS ,
281+ jobId : `read_fallback_${ igAccountId } _${ event . userId } _${ automationId } ` ,
282+ }
283+ ) ;
284+ }
227285 }
228286 }
229287
0 commit comments