@@ -149,6 +149,10 @@ const getNewHitsFromElasticsearch = async (
149149
150150 const matchField = siteConfig . matchField ;
151151
152+ console . info (
153+ `Matched ${ elasticResponse ?. hits ?. total ?. value ?? 0 } hits for ${ subscription . _id } from ${ siteConfig . name } ` ,
154+ ) ;
155+
152156 // Filter out new hits:
153157 return ( elasticResponse ?. hits ?. hits ?? [ ] )
154158 . filter ( ( hit : { _source ?: Record < string , unknown > } ) => {
@@ -176,7 +180,7 @@ const getNewHitsFromElasticsearch = async (
176180 * @param siteConfig - The site configuration to process
177181 * @param stats - Statistics object to track processing
178182 * @param isDryRun - Do not write changes
179- * @return { Promise<void> } A Promise that resolves when processing is complete
183+ * @return A Promise that resolves when processing is complete
180184 */
181185const processSiteSubscriptions = async (
182186 server : Server ,
@@ -205,8 +209,12 @@ const processSiteSubscriptions = async (
205209
206210 // Process subscriptions sequentially to avoid overwhelming the system
207211 await result . reduce ( async ( previousPromise , subscription ) => {
212+ // @todo move this to a for loop, using
213+ // reduce() here is quite hard to reason.
208214 await previousPromise ;
209215
216+ console . info ( `Processing subscription ${ subscription . _id } for site ${ siteConfig . id } ` ) ;
217+
210218 // Resolve user data from ATV if stored there
211219 let resolvedQuery : string = subscription . query ;
212220 let resolvedSearchDescription : string = subscription . search_description ?? '' ;
@@ -218,8 +226,11 @@ const processSiteSubscriptions = async (
218226 resolvedQuery = atvData . query ?? '' ;
219227 resolvedSearchDescription = atvData . search_description ?? '' ;
220228 resolvedElasticQuery = atvData . elastic_query ;
229+
230+ console . info ( `Subscription details loaded from ATV for ${ subscription . _id } (site: ${ siteConfig . id } )` ) ;
221231 } catch ( e ) {
222232 console . error ( `Failed to load user data from ATV for ${ subscription . _id } ` , e ) ;
233+ server . Sentry ?. captureException ( e ) ;
223234 return Promise . resolve ( ) ;
224235 }
225236 }
@@ -230,15 +241,16 @@ const processSiteSubscriptions = async (
230241 const subscriptionValidForDays = siteConfig . subscription . maxAge ;
231242
232243 // Sync ATV delete_after if needed (handles config changes and legacy subscriptions)
244+ // @todo : why do we need this?
233245 const expectedDeleteAfter = calculateExpectedDeleteAfter ( new Date ( subscription . created ) , subscriptionValidForDays ) ;
234246 if ( needsDeleteAfterSync ( subscription . delete_after , expectedDeleteAfter ) ) {
235- if ( isDryRun ) {
236- console . log (
237- `[DRY RUN] Would sync ATV delete_after for ${ subscription . _id } ` +
238- `(stored : ${ subscription . delete_after ?. toISOString ( ) . substring ( 0 , 10 ) ?? 'none' } , ` +
239- `expected: ${ expectedDeleteAfter . toISOString ( ) . substring ( 0 , 10 ) } )` ,
240- ) ;
241- } else {
247+ console . info (
248+ `Sync ATV delete_after for ${ subscription . _id } ` +
249+ `(stored: ${ subscription . delete_after ?. toISOString ( ) . substring ( 0 , 10 ) ?? 'none' } , ` +
250+ `expected : ${ expectedDeleteAfter . toISOString ( ) . substring ( 0 , 10 ) } )` ,
251+ ) ;
252+
253+ if ( ! isDryRun ) {
242254 try {
243255 await server . atv . updateDocumentDeleteAfter (
244256 ATV . getAtvId ( subscription ) ,
@@ -262,41 +274,50 @@ const processSiteSubscriptions = async (
262274
263275 // If subscription should expire soon, send an expiration email
264276 if ( checkShouldSendExpiryNotification ( subscription as Partial < SubscriptionCollectionType > , siteConfig ) ) {
265- if ( isDryRun ) {
266- console . log ( `[DRY RUN] Would send expiry email to ${ ATV . getAtvId ( subscription ) } (site: ${ siteConfig . id } )` ) ;
267- } else {
277+ console . info ( `Sending expiry email to ${ ATV . getAtvId ( subscription ) } (site: ${ siteConfig . id } )` ) ;
278+
279+ // @fixme : dry run keeps spamming the messages and should
280+ // newer be used in production. Why do we need dry-run feature?
281+ if ( ! isDryRun ) {
268282 await collection . updateOne ( { _id : subscription . _id } , { $set : { expiry_notification_sent : 1 } } ) ;
269283 }
270284
271- const expiryEmailContent = await expiryEmail (
272- subscription . lang ,
273- {
274- search_description : resolvedSearchDescription ,
275- link : siteConfig . urls . base + resolvedQuery ,
276- removal_date : formattedExpiryDate ,
277- remove_link : `${ localizedBaseUrl } /hakuvahti/unsubscribe?subscription=${ subscription . _id } &hash=${ subscription . hash } ` ,
278- renewal_link : `${ localizedBaseUrl } /hakuvahti/renew?subscription=${ subscription . _id } &hash=${ subscription . hash } ` ,
279- search_link : resolvedQuery ,
280- } ,
281- siteConfig ,
282- ) ;
283-
284285 // Queue expiry email if email is active
285286 if ( isEmailActive ( subscription as Partial < SubscriptionCollectionType > ) ) {
286- const expiryEmailToQueue : QueueInsertDocument = {
287- type : 'email' ,
288- atv_id : ATV . getAtvId ( subscription ) ,
289- content : expiryEmailContent ,
290- } ;
287+ try {
288+ const expiryEmailContent = await expiryEmail (
289+ subscription . lang ,
290+ {
291+ search_description : resolvedSearchDescription ,
292+ link : siteConfig . urls . base + resolvedQuery ,
293+ removal_date : formattedExpiryDate ,
294+ remove_link : `${ localizedBaseUrl } /hakuvahti/unsubscribe?subscription=${ subscription . _id } &hash=${ subscription . hash } ` ,
295+ renewal_link : `${ localizedBaseUrl } /hakuvahti/renew?subscription=${ subscription . _id } &hash=${ subscription . hash } ` ,
296+ search_link : resolvedQuery ,
297+ } ,
298+ siteConfig ,
299+ ) ;
291300
292- if ( ! isDryRun ) {
293- await queueCollection . insertOne ( expiryEmailToQueue ) ;
301+ const expiryEmailToQueue : QueueInsertDocument = {
302+ type : 'email' ,
303+ atv_id : ATV . getAtvId ( subscription ) ,
304+ content : expiryEmailContent ,
305+ } ;
306+
307+ if ( ! isDryRun ) {
308+ await queueCollection . insertOne ( expiryEmailToQueue ) ;
309+ }
310+ stats . expiryEmailsQueued ++ ;
311+ } catch ( error ) {
312+ console . error ( `Error queueing expiry email for subscription ${ subscription . _id } :` , error ) ;
313+ server . Sentry ?. captureException ( error ) ;
294314 }
295- stats . expiryEmailsQueued ++ ;
296315 }
297316
298317 // Queue renewal SMS if subscription has SMS and site supports it
299318 if ( isSmsActive ( subscription as Partial < SubscriptionCollectionType > ) && siteConfig . subscription . enableSms ) {
319+ console . info ( `Sending expiry SMS for ${ subscription . _id } (site: ${ siteConfig . id } )` ) ;
320+
300321 try {
301322 const smsContent = await renewalSms (
302323 subscription . lang ,
@@ -314,14 +335,14 @@ const processSiteSubscriptions = async (
314335 content : smsContent ,
315336 } ;
316337
317- if ( isDryRun ) {
318- console . log ( `[DRY RUN] Would queue renewal SMS for ${ subscription . _id } ` ) ;
338+ if ( ! isDryRun ) {
319339 } else {
320340 await queueCollection . insertOne ( smsToQueue ) ;
321341 }
322342 stats . smsQueued ++ ;
323343 } catch ( error ) {
324344 console . error ( `Error queueing renewal SMS for subscription ${ subscription . _id } :` , error ) ;
345+ server . Sentry ?. captureException ( error ) ;
325346 }
326347 }
327348 }
@@ -335,6 +356,7 @@ const processSiteSubscriptions = async (
335356
336357 // No new hits
337358 if ( newHits . length === 0 ) {
359+ console . info ( `No hits for ${ subscription . _id } from ${ siteConfig . name } ` ) ;
338360 return Promise . resolve ( ) ;
339361 }
340362
@@ -348,41 +370,46 @@ const processSiteSubscriptions = async (
348370 const pad = ( n : number ) => n . toString ( ) . padStart ( 2 , '0' ) ;
349371 const formattedCreatedDate = `${ pad ( date . getDate ( ) ) } .${ pad ( date . getMonth ( ) + 1 ) } .${ date . getFullYear ( ) } ` ;
350372
351- const emailContent = await newHitsEmail (
352- subscription . lang ,
353- {
354- created_date : formattedCreatedDate ,
355- expiry_date : formattedExpiryDate ,
356- search_description : resolvedSearchDescription ,
357- search_link : resolvedQuery ,
358- remove_link : `${ localizedBaseUrl } /hakuvahti/unsubscribe?subscription=${ subscription . _id } &hash=${ subscription . hash } ` ,
359- hits : hitsForEmail ,
360- } ,
361- siteConfig ,
362- ) ;
373+ // Update last_checked regardless of channel
374+ if ( ! isDryRun ) {
375+ const dateUnixtime : number = Math . floor ( Date . now ( ) / 1000 ) ;
376+ await collection . updateOne ( { _id : subscription . _id } , { $set : { last_checked : dateUnixtime } } ) ;
377+ }
363378
364379 // Queue new hits email if email is active
365380 if ( isEmailActive ( subscription as Partial < SubscriptionCollectionType > ) ) {
366- const email : QueueInsertDocument = {
367- type : 'email' ,
368- atv_id : ATV . getAtvId ( subscription ) ,
369- content : emailContent ,
370- } ;
371-
372- if ( isDryRun ) {
373- console . log (
374- `[DRY RUN] Would queue email for ${ ATV . getAtvId ( subscription ) } : ${ newHits . length } new result(s) (site: ${ siteConfig . id } )` ,
381+ try {
382+ const emailContent = await newHitsEmail (
383+ subscription . lang ,
384+ {
385+ created_date : formattedCreatedDate ,
386+ expiry_date : formattedExpiryDate ,
387+ search_description : resolvedSearchDescription ,
388+ search_link : resolvedQuery ,
389+ remove_link : `${ localizedBaseUrl } /hakuvahti/unsubscribe?subscription=${ subscription . _id } &hash=${ subscription . hash } ` ,
390+ hits : hitsForEmail ,
391+ } ,
392+ siteConfig ,
375393 ) ;
376- } else {
377- await queueCollection . insertOne ( email ) ;
378- }
379- stats . newResultsEmailsQueued ++ ;
380- }
381394
382- // Update last_checked regardless of channel
383- if ( ! isDryRun ) {
384- const dateUnixtime : number = Math . floor ( Date . now ( ) / 1000 ) ;
385- await collection . updateOne ( { _id : subscription . _id } , { $set : { last_checked : dateUnixtime } } ) ;
395+ const email : QueueInsertDocument = {
396+ type : 'email' ,
397+ atv_id : ATV . getAtvId ( subscription ) ,
398+ content : emailContent ,
399+ } ;
400+
401+ console . info (
402+ `New email for ${ ATV . getAtvId ( subscription ) } : ${ newHits . length } new result(s) (site: ${ siteConfig . id } )` ,
403+ ) ;
404+
405+ if ( ! isDryRun ) {
406+ await queueCollection . insertOne ( email ) ;
407+ }
408+ stats . newResultsEmailsQueued ++ ;
409+ } catch ( error ) {
410+ // Log error but don't break email sending
411+ console . error ( `Error queueing SMS for subscription ${ subscription . _id } :` , error ) ;
412+ }
386413 }
387414
388415 // Queue SMS if subscription has SMS confirmed and SMS is enabled for site
@@ -404,9 +431,9 @@ const processSiteSubscriptions = async (
404431 content : smsContent ,
405432 } ;
406433
407- if ( isDryRun ) {
408- console . log ( `[DRY RUN] Would queue SMS for ${ subscription . _id } ` ) ;
409- } else {
434+ console . log ( `New SMS for ${ subscription . _id } : ${ newHits . length } new result(s) (site: ${ siteConfig . id } )` ) ;
435+
436+ if ( ! isDryRun ) {
410437 await queueCollection . insertOne ( smsToQueue ) ;
411438 }
412439 stats . smsQueued ++ ;
0 commit comments