bulltrackers-module 1.0.678 → 1.0.679

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -16,12 +16,53 @@ const storage = new Storage(); // Singleton GCS Client
16
16
  */
17
17
  async function processAlertForPI(db, logger, piCid, alertType, computationMetadata, computationDate, dependencies = {}) {
18
18
  try {
19
+ // [FIX] Check if computation date is earlier than today (backfill protection)
20
+ const today = new Date().toISOString().split('T')[0]; // YYYY-MM-DD format
21
+ const isHistoricalData = computationDate < today;
22
+
23
+ // If it's historical data, check if this alert already exists
24
+ if (isHistoricalData) {
25
+ // Check if we've already created alerts for this PI/date/alertType combination
26
+ const existingAlertsSnapshot = await db.collection('SignedInUsers')
27
+ .doc('_metadata') // Use metadata doc to track processed alerts
28
+ .collection('processed_alerts')
29
+ .where('piCid', '==', Number(piCid))
30
+ .where('computationDate', '==', computationDate)
31
+ .where('alertType', '==', alertType.id)
32
+ .limit(1)
33
+ .get();
34
+
35
+ if (!existingAlertsSnapshot.empty) {
36
+ logger.log('INFO', `[processAlertForPI] Skipping duplicate alert for historical data: PI ${piCid}, date ${computationDate}, alert type ${alertType.id}`);
37
+ return;
38
+ }
39
+
40
+ logger.log('WARN', `[processAlertForPI] Processing alert for historical data (backfill): PI ${piCid}, date ${computationDate}, alert type ${alertType.id}. This alert will only be sent to developers.`);
41
+ }
42
+
19
43
  // 1. Get PI username from rankings or subscriptions
20
44
  const piUsername = await getPIUsername(db, piCid);
21
45
 
22
46
  // 2. Find all users subscribed to this PI and alert type
23
47
  // Use computationName (e.g., 'RiskScoreIncrease') to map to alertConfig keys
24
- const subscriptions = await findSubscriptionsForPI(db, logger, piCid, alertType.computationName, computationDate, dependencies);
48
+ let subscriptions = await findSubscriptionsForPI(db, logger, piCid, alertType.computationName, computationDate, dependencies);
49
+
50
+ // [FIX] If it's historical data, only send to developer accounts
51
+ if (isHistoricalData) {
52
+ const { isDeveloper } = require('../../api-v2/helpers/data-fetchers/firestore.js');
53
+
54
+ // Filter subscriptions to only include developers
55
+ const devSubscriptions = [];
56
+ for (const subscription of subscriptions) {
57
+ const isDev = await isDeveloper(db, String(subscription.userCid));
58
+ if (isDev) {
59
+ devSubscriptions.push(subscription);
60
+ }
61
+ }
62
+
63
+ subscriptions = devSubscriptions;
64
+ logger.log('INFO', `[processAlertForPI] Historical data: Filtered to ${subscriptions.length} developer subscriptions for PI ${piCid}, alert type ${alertType.id}`);
65
+ }
25
66
 
26
67
  if (subscriptions.length === 0) {
27
68
  logger.log('INFO', `[processAlertForPI] No subscriptions found for PI ${piCid}, alert type ${alertType.id}`);
@@ -38,8 +79,24 @@ async function processAlertForPI(db, logger, piCid, alertType, computationMetada
38
79
  const config = dependencies.config || {};
39
80
  const notificationPromises = [];
40
81
 
82
+ // Check watchlistAlerts preference for each user
83
+ const { manageNotificationPreferences } = require('../../api-v2/helpers/data-fetchers/firestore.js');
84
+
41
85
  for (const subscription of subscriptions) {
42
86
  const userCid = subscription.userCid;
87
+
88
+ // [FIX] Check user's watchlistAlerts preference before creating alert
89
+ try {
90
+ const prefs = await manageNotificationPreferences(db, userCid, 'get');
91
+ if (!prefs.watchlistAlerts) {
92
+ logger.log('DEBUG', `[processAlertForPI] User ${userCid} has watchlistAlerts disabled, skipping alert`);
93
+ continue; // Skip this user
94
+ }
95
+ } catch (prefError) {
96
+ logger.log('WARN', `[processAlertForPI] Error checking watchlistAlerts preference for user ${userCid}: ${prefError.message}`);
97
+ // Continue anyway - fail open for important alerts
98
+ }
99
+
43
100
  const notificationId = `alert_${Date.now()}_${userCid}_${piCid}_${Math.random().toString(36).substring(2, 9)}`;
44
101
 
45
102
  const notificationData = {
@@ -129,6 +186,28 @@ async function processAlertForPI(db, logger, piCid, alertType, computationMetada
129
186
 
130
187
  logger.log('SUCCESS', `[processAlertForPI] Created ${notificationPromises.length} notifications for PI ${piCid}, alert type ${alertType.id}`);
131
188
 
189
+ // [FIX] Mark alert as processed to prevent duplicates on backfill
190
+ if (isHistoricalData) {
191
+ try {
192
+ await db.collection('SignedInUsers')
193
+ .doc('_metadata')
194
+ .collection('processed_alerts')
195
+ .doc(`${piCid}_${computationDate}_${alertType.id}`)
196
+ .set({
197
+ piCid: Number(piCid),
198
+ computationDate: computationDate,
199
+ alertType: alertType.id,
200
+ alertTypeName: alertType.name,
201
+ processedAt: FieldValue.serverTimestamp(),
202
+ notificationsSent: notificationPromises.length
203
+ });
204
+ logger.log('INFO', `[processAlertForPI] Marked historical alert as processed: PI ${piCid}, date ${computationDate}, alert type ${alertType.id}`);
205
+ } catch (markError) {
206
+ logger.log('WARN', `[processAlertForPI] Failed to mark alert as processed: ${markError.message}`);
207
+ // Don't throw - this is non-critical
208
+ }
209
+ }
210
+
132
211
  } catch (error) {
133
212
  logger.log('ERROR', `[processAlertForPI] Error processing alert for PI ${piCid}`, error);
134
213
  throw error;
@@ -285,8 +364,29 @@ async function findSubscriptionsForPI(db, logger, piCid, alertTypeId, computatio
285
364
  const isTestProbe = alertTypeId === 'TestSystemProbe';
286
365
  const isEnabled = item.alertConfig && item.alertConfig[configKey] === true;
287
366
 
288
- // If it's the probe OR the user explicitly enabled it
289
- if (isTestProbe || isEnabled) {
367
+ // [FIX] For TestSystemProbe, check user's testAlerts preference
368
+ let shouldSendAlert = false;
369
+ if (isTestProbe) {
370
+ // Check if user has testAlerts enabled in their notification preferences
371
+ try {
372
+ const { manageNotificationPreferences } = require('../../api-v2/helpers/data-fetchers/firestore.js');
373
+ const prefs = await manageNotificationPreferences(db, userCid, 'get');
374
+ shouldSendAlert = prefs.testAlerts === true;
375
+
376
+ if (!shouldSendAlert) {
377
+ logger.log('DEBUG', `[findSubscriptionsForPI] User ${userCid} has testAlerts disabled, skipping TestSystemProbe alert`);
378
+ }
379
+ } catch (prefError) {
380
+ logger.log('WARN', `[findSubscriptionsForPI] Error checking testAlerts preference for user ${userCid}: ${prefError.message}`);
381
+ // Default to not sending test alerts if we can't check preferences
382
+ shouldSendAlert = false;
383
+ }
384
+ } else {
385
+ // For non-test alerts, use normal alert config check
386
+ shouldSendAlert = isEnabled;
387
+ }
388
+
389
+ if (shouldSendAlert) {
290
390
  subscriptions.push({
291
391
  userCid: userCid,
292
392
  piCid: piCid,
@@ -18,6 +18,15 @@ const { FieldValue } = require('@google-cloud/firestore');
18
18
  */
19
19
  async function notifyTaskEngineComplete(db, logger, userCid, requestId, username, success, errorMessage, options = {}) {
20
20
  try {
21
+ // [FIX] Check user's notification preferences before sending
22
+ const { manageNotificationPreferences } = require('./data-fetchers/firestore.js');
23
+ const prefs = await manageNotificationPreferences(db, userCid, 'get');
24
+
25
+ if (!prefs.syncProcesses) {
26
+ logger?.log('DEBUG', `[notifyTaskEngineComplete] User ${userCid} has syncProcesses disabled, skipping notification`);
27
+ return;
28
+ }
29
+
21
30
  const notificationData = {
22
31
  type: 'sync',
23
32
  subType: 'complete',
@@ -42,6 +51,29 @@ async function notifyTaskEngineComplete(db, logger, userCid, requestId, username
42
51
  .doc(requestId)
43
52
  .set(notificationData);
44
53
 
54
+ // [FIX] Clean up progress notifications for this sync to stop showing loading icons
55
+ try {
56
+ const progressNotificationsQuery = await db.collection('SignedInUsers')
57
+ .doc(String(userCid))
58
+ .collection('notifications')
59
+ .where('metadata.requestId', '==', requestId)
60
+ .where('subType', '==', 'progress')
61
+ .get();
62
+
63
+ const batch = db.batch();
64
+ progressNotificationsQuery.docs.forEach(doc => {
65
+ batch.delete(doc.ref);
66
+ });
67
+
68
+ if (progressNotificationsQuery.size > 0) {
69
+ await batch.commit();
70
+ logger?.log('INFO', `[notifyTaskEngineComplete] Cleaned up ${progressNotificationsQuery.size} progress notifications for request ${requestId}`);
71
+ }
72
+ } catch (cleanupError) {
73
+ logger?.log('WARN', `[notifyTaskEngineComplete] Failed to clean up progress notifications: ${cleanupError.message}`);
74
+ // Don't throw - cleanup is non-critical
75
+ }
76
+
45
77
  logger?.log('INFO', `[notifyTaskEngineComplete] Notification sent for user ${userCid}, request ${requestId}`);
46
78
  } catch (error) {
47
79
  logger?.log('WARN', `[notifyTaskEngineComplete] Failed to send notification: ${error.message}`);
@@ -61,6 +93,15 @@ async function notifyTaskEngineComplete(db, logger, userCid, requestId, username
61
93
  */
62
94
  async function notifyTaskEngineProgress(db, logger, userCid, requestId, username, stage, dataType, options = {}) {
63
95
  try {
96
+ // [FIX] Check user's notification preferences before sending
97
+ const { manageNotificationPreferences } = require('./data-fetchers/firestore.js');
98
+ const prefs = await manageNotificationPreferences(db, userCid, 'get');
99
+
100
+ if (!prefs.syncProcesses) {
101
+ logger?.log('DEBUG', `[notifyTaskEngineProgress] User ${userCid} has syncProcesses disabled, skipping notification`);
102
+ return;
103
+ }
104
+
64
105
  const stageMessages = {
65
106
  'started': 'Data sync started',
66
107
  'portfolio_complete': 'Portfolio data fetched',
@@ -130,6 +171,15 @@ async function notifyPIDataRefreshed(db, logger, collectionRegistry, piCid, user
130
171
  */
131
172
  async function notifyComputationComplete(db, logger, userCid, requestId, computation, displayName, success, errorMessage, options = {}) {
132
173
  try {
174
+ // [FIX] Check user's notification preferences before sending
175
+ const { manageNotificationPreferences } = require('./data-fetchers/firestore.js');
176
+ const prefs = await manageNotificationPreferences(db, userCid, 'get');
177
+
178
+ if (!prefs.userActionCompletions) {
179
+ logger?.log('DEBUG', `[notifyComputationComplete] User ${userCid} has userActionCompletions disabled, skipping notification`);
180
+ return;
181
+ }
182
+
133
183
  const notificationData = {
134
184
  type: 'computation',
135
185
  subType: 'complete',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.678",
3
+ "version": "1.0.679",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [