bulltrackers-module 1.0.609 → 1.0.610

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.
@@ -64,24 +64,41 @@ async function processAlertForPI(db, logger, piCid, alertType, computationMetada
64
64
  }
65
65
  };
66
66
 
67
+ // Write to alerts collection (not notifications) - alerts are separate from system notifications
67
68
  // Use writeWithMigration to write to new path (with legacy fallback)
68
69
  const writePromise = writeWithMigration(
69
70
  db,
70
71
  'signedInUsers',
71
- 'notifications',
72
+ 'alerts', // Write to alerts collection, not notifications
72
73
  { cid: String(userCid) },
73
- notificationData,
74
+ {
75
+ // Alert-specific format (simplified from notification format)
76
+ alertId: notificationId,
77
+ piCid: Number(piCid),
78
+ piUsername: piUsername,
79
+ alertType: alertType.id,
80
+ alertTypeName: alertType.name,
81
+ message: alertMessage,
82
+ severity: alertType.severity,
83
+ watchlistId: subscription.watchlistId,
84
+ watchlistName: subscription.watchlistName,
85
+ read: false,
86
+ createdAt: FieldValue.serverTimestamp(),
87
+ computationDate: computationDate,
88
+ computationName: alertType.computationName,
89
+ ...(computationMetadata || {})
90
+ },
74
91
  {
75
92
  isCollection: true,
76
93
  merge: false,
77
- dataType: 'notifications',
94
+ dataType: 'alerts',
78
95
  documentId: notificationId,
79
96
  dualWrite: false, // Disable dual write - we're fully migrated to new path
80
97
  config,
81
98
  collectionRegistry
82
99
  }
83
100
  ).catch(err => {
84
- logger.log('ERROR', `[processAlertForPI] Failed to write notification for CID ${userCid}: ${err.message}`, err);
101
+ logger.log('ERROR', `[processAlertForPI] Failed to write alert for CID ${userCid}: ${err.message}`, err);
85
102
  throw err; // Re-throw so we know if writes are failing
86
103
  });
87
104
 
@@ -343,11 +343,22 @@ async function checkIfPIHasAlertsToday(db, logger, userCid, piCid, date, depende
343
343
 
344
344
  const notificationsRef = db.collection(notificationsPath);
345
345
 
346
- // Check if there are any alert notifications for this PI today
347
- const snapshot = await notificationsRef
348
- .where('type', '==', 'watchlistAlerts')
349
- .where('metadata.piCid', '==', Number(piCid))
350
- .where('metadata.computationDate', '==', date)
346
+ // Check alerts collection instead of notifications
347
+ // Use collection registry to get alerts path
348
+ let alertsPath;
349
+ if (collectionRegistry && collectionRegistry.getCollectionPath) {
350
+ alertsPath = collectionRegistry.getCollectionPath('signedInUsers', 'alerts', { cid: String(userCid) });
351
+ } else {
352
+ // Fallback to legacy path
353
+ alertsPath = `user_alerts/${userCid}/alerts`;
354
+ }
355
+
356
+ const alertsRef = db.collection(alertsPath);
357
+
358
+ // Check if there are any alerts for this PI today
359
+ const snapshot = await alertsRef
360
+ .where('piCid', '==', Number(piCid))
361
+ .where('computationDate', '==', date)
351
362
  .limit(1)
352
363
  .get();
353
364
 
@@ -415,19 +426,30 @@ async function sendAllClearNotification(db, logger, userCid, piCid, piUsername,
415
426
  }
416
427
  };
417
428
 
418
- // Use writeWithMigration to write to new path (with legacy fallback)
419
- // notifications is a subcollection, so we need isCollection: true and documentId
429
+ // Write to alerts collection (not notifications) - alerts are separate from system notifications
420
430
  const { writeWithMigration } = require('../old-generic-api/user-api/helpers/core/path_resolution_helpers');
421
431
  await writeWithMigration(
422
432
  db,
423
433
  'signedInUsers',
424
- 'notifications',
434
+ 'alerts', // Write to alerts collection, not notifications
425
435
  { cid: String(userCid) },
426
- notificationData,
436
+ {
437
+ // Alert-specific format
438
+ alertId: notificationId,
439
+ piCid: Number(piCid),
440
+ piUsername: piUsername,
441
+ alertType: 'all_clear',
442
+ alertTypeName: 'All Clear',
443
+ message: `${piUsername} was processed, all clear today!`,
444
+ severity: 'info',
445
+ read: false,
446
+ createdAt: FieldValue.serverTimestamp(),
447
+ computationDate: date
448
+ },
427
449
  {
428
450
  isCollection: true,
429
451
  merge: false,
430
- dataType: 'notifications',
452
+ dataType: 'alerts',
431
453
  documentId: notificationId,
432
454
  dualWrite: false, // Disable dual write - we're fully migrated to new path
433
455
  config,
@@ -1112,13 +1112,26 @@ const getComputationResults = async (db, computationName, dateStr, userId = null
1112
1112
 
1113
1113
  // 11. Fetch User Notifications
1114
1114
  const fetchNotifications = async (firestore, userId, options = {}) => {
1115
- const { limit = 20, unreadOnly = false } = options;
1115
+ const { limit = 20, unreadOnly = false, excludeTypes = ['watchlistAlerts'] } = options;
1116
1116
  try {
1117
1117
  let query = firestore.collection('SignedInUsers').doc(userId).collection('notifications');
1118
1118
  if (unreadOnly) query = query.where('read', '==', false);
1119
1119
 
1120
- const snapshot = await query.orderBy('createdAt', 'desc').limit(limit).get();
1121
- return snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
1120
+ const snapshot = await query.orderBy('createdAt', 'desc').limit(limit * 2).get(); // Fetch more to filter
1121
+
1122
+ // Filter out excluded types (like watchlistAlerts which go to alerts bell)
1123
+ let notifications = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
1124
+
1125
+ if (excludeTypes && excludeTypes.length > 0) {
1126
+ notifications = notifications.filter(notif => {
1127
+ // Exclude by type field or metadata.notificationType
1128
+ const notifType = notif.type || notif.metadata?.notificationType;
1129
+ return !excludeTypes.includes(notifType);
1130
+ });
1131
+ }
1132
+
1133
+ // Limit after filtering
1134
+ return notifications.slice(0, limit);
1122
1135
  } catch (error) {
1123
1136
  console.error(`Error fetching notifications: ${error}`);
1124
1137
  throw error;
@@ -1225,14 +1238,29 @@ const trackPopularInvestorView = async (db, piId, viewerId = null, viewerType =
1225
1238
  // ==========================================
1226
1239
 
1227
1240
  const fetchUserAlerts = async (db, userId, options = {}) => {
1228
- const { limit = 50, unreadOnly = false, type } = options;
1229
- let query = db.collection('user_alerts').doc(userId).collection('alerts').orderBy('createdAt', 'desc');
1241
+ const { limit = 50, unreadOnly = false, type, piCid } = options;
1242
+ // Use new path: SignedInUsers/{cid}/alerts
1243
+ let query = db.collection('SignedInUsers').doc(String(userId)).collection('alerts').orderBy('createdAt', 'desc');
1230
1244
 
1231
1245
  if (unreadOnly) query = query.where('read', '==', false);
1232
1246
  if (type) query = query.where('alertType', '==', type);
1247
+ if (piCid) query = query.where('piCid', '==', Number(piCid));
1233
1248
 
1234
1249
  const snapshot = await query.limit(limit).get();
1235
- return snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
1250
+ // Convert Firestore timestamps to ISO strings for frontend
1251
+ return snapshot.docs.map(doc => {
1252
+ const data = doc.data();
1253
+ // Convert Firestore timestamps to ISO strings
1254
+ if (data.createdAt) {
1255
+ data.createdAt = data.createdAt.toDate ? data.createdAt.toDate().toISOString() :
1256
+ (data.createdAt.toISOString ? data.createdAt.toISOString() : data.createdAt);
1257
+ }
1258
+ if (data.readAt) {
1259
+ data.readAt = data.readAt.toDate ? data.readAt.toDate().toISOString() :
1260
+ (data.readAt.toISOString ? data.readAt.toISOString() : data.readAt);
1261
+ }
1262
+ return { id: doc.id, ...data };
1263
+ });
1236
1264
  };
1237
1265
 
1238
1266
  const subscribeToWatchlistAlerts = async (db, userId, watchlistId, piId, alertConfig) => {
@@ -14,11 +14,12 @@ const router = express.Router();
14
14
  router.get('/history', async (req, res) => {
15
15
  try {
16
16
  const { db } = req.dependencies;
17
- const { limit, unreadOnly, type } = req.query;
17
+ const { limit, unreadOnly, type, piCid } = req.query;
18
18
  const alerts = await fetchUserAlerts(db, req.targetUserId, {
19
19
  limit: parseInt(limit || 50),
20
20
  unreadOnly: unreadOnly === 'true',
21
- type
21
+ type: type || undefined,
22
+ piCid: piCid ? Number(piCid) : undefined
22
23
  });
23
24
  res.json({ success: true, count: alerts.length, data: alerts });
24
25
  } catch (error) {
@@ -41,7 +42,7 @@ router.get('/count', async (req, res) => {
41
42
  router.put('/:id/read', async (req, res) => {
42
43
  try {
43
44
  const { db } = req.dependencies;
44
- await db.collection('user_alerts').doc(req.targetUserId).collection('alerts').doc(req.params.id)
45
+ await db.collection('SignedInUsers').doc(String(req.targetUserId)).collection('alerts').doc(req.params.id)
45
46
  .update({ read: true, readAt: new Date() });
46
47
  res.json({ success: true });
47
48
  } catch (e) { res.status(500).json({ error: e.message }); }
@@ -52,7 +53,7 @@ router.put('/read-all', async (req, res) => {
52
53
  try {
53
54
  const { db } = req.dependencies;
54
55
  const batch = db.batch();
55
- const snaps = await db.collection('user_alerts').doc(req.targetUserId).collection('alerts').where('read', '==', false).get();
56
+ const snaps = await db.collection('SignedInUsers').doc(String(req.targetUserId)).collection('alerts').where('read', '==', false).get();
56
57
  snaps.docs.forEach(doc => batch.update(doc.ref, { read: true, readAt: new Date() }));
57
58
  await batch.commit();
58
59
  res.json({ success: true, updated: snaps.size });
@@ -63,7 +64,7 @@ router.put('/read-all', async (req, res) => {
63
64
  router.delete('/:id', async (req, res) => {
64
65
  try {
65
66
  const { db } = req.dependencies;
66
- await db.collection('user_alerts').doc(req.targetUserId).collection('alerts').doc(req.params.id).delete();
67
+ await db.collection('SignedInUsers').doc(String(req.targetUserId)).collection('alerts').doc(req.params.id).delete();
67
68
  res.json({ success: true });
68
69
  } catch (e) { res.status(500).json({ error: e.message }); }
69
70
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.609",
3
+ "version": "1.0.610",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [