bulltrackers-module 1.0.518 → 1.0.519

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.
@@ -41,6 +41,7 @@ async function trackProfileView(req, res, dependencies, config) {
41
41
  // Add unique viewers if viewer CID is provided
42
42
  if (effectiveViewerCid) {
43
43
  // Read existing to merge unique viewers
44
+ // Note: date is already in path params, so don't provide documentId
44
45
  const existingResult = await readWithMigration(
45
46
  db,
46
47
  'popularInvestors',
@@ -50,7 +51,7 @@ async function trackProfileView(req, res, dependencies, config) {
50
51
  isCollection: false,
51
52
  dataType: 'piProfileViews',
52
53
  config,
53
- documentId: today,
54
+ // documentId not needed - date is already in path params
54
55
  collectionRegistry: dependencies.collectionRegistry
55
56
  }
56
57
  );
@@ -66,6 +67,7 @@ async function trackProfileView(req, res, dependencies, config) {
66
67
  }
67
68
 
68
69
  // Write to new path (with dual write to legacy during migration)
70
+ // Note: date is already in path params, so don't provide documentId
69
71
  await writeWithMigration(
70
72
  db,
71
73
  'popularInvestors',
@@ -77,7 +79,7 @@ async function trackProfileView(req, res, dependencies, config) {
77
79
  merge: true,
78
80
  dataType: 'piProfileViews',
79
81
  config,
80
- documentId: today,
82
+ // documentId not needed - date is already in path params
81
83
  dualWrite: true,
82
84
  collectionRegistry: dependencies.collectionRegistry
83
85
  }
@@ -95,6 +97,7 @@ async function trackProfileView(req, res, dependencies, config) {
95
97
  };
96
98
 
97
99
  // Write individual view (new path)
100
+ // Note: viewId is already in path params, so don't provide documentId
98
101
  await writeWithMigration(
99
102
  db,
100
103
  'popularInvestors',
@@ -106,7 +109,7 @@ async function trackProfileView(req, res, dependencies, config) {
106
109
  merge: true,
107
110
  dataType: 'piIndividualViews',
108
111
  config,
109
- documentId: viewId,
112
+ // documentId not needed - viewId is already in path params
110
113
  dualWrite: true,
111
114
  collectionRegistry: dependencies.collectionRegistry
112
115
  }
@@ -87,10 +87,11 @@ async function requestPiAddition(req, res, dependencies, config) {
87
87
 
88
88
  /**
89
89
  * GET /user/me/watchlists/:id/rankings-check
90
- * Check which PIs in a watchlist are in the latest available rankings
90
+ * Check which PIs in a watchlist are in the master list (single source of truth)
91
+ * UPDATED: Now checks against the master list instead of latest rankings date
91
92
  */
92
93
  async function checkPisInRankings(req, res, dependencies, config) {
93
- const { db, logger } = dependencies;
94
+ const { db, logger, collectionRegistry } = dependencies;
94
95
  const { userCid } = req.query;
95
96
  const { id } = req.params;
96
97
 
@@ -137,17 +138,94 @@ async function checkPisInRankings(req, res, dependencies, config) {
137
138
  items = watchlistData.items || [];
138
139
  }
139
140
 
141
+ // Get the master list path from collection registry
142
+ let masterListPath = 'system_state/popular_investor_master_list';
143
+
144
+ if (collectionRegistry && collectionRegistry.getCollectionPath) {
145
+ try {
146
+ masterListPath = collectionRegistry.getCollectionPath('system', 'popularInvestorMasterList', {});
147
+ } catch (err) {
148
+ logger.log('WARN', `[checkPisInRankings] Failed to get master list path from registry, using default: ${err.message}`);
149
+ }
150
+ }
151
+
152
+ // Fetch master list document
153
+ const masterListRef = db.doc(masterListPath);
154
+ const masterListDoc = await masterListRef.get();
155
+
156
+ if (!masterListDoc.exists) {
157
+ // Master list doesn't exist yet - fallback to legacy rankings check
158
+ logger.log('WARN', `[checkPisInRankings] Master list not found, falling back to legacy rankings check`);
159
+ const legacyResult = await checkPisInRankingsLegacy(db, items, config, logger);
160
+ return res.status(200).json(legacyResult);
161
+ }
162
+
163
+ const masterListData = masterListDoc.data();
164
+ const investors = masterListData.investors || {};
165
+
166
+ // Create sets for quick lookup: by CID and by username (case-insensitive)
167
+ const investorsByCid = new Set();
168
+ const investorsByUsername = new Set();
169
+
170
+ for (const [cid, piData] of Object.entries(investors)) {
171
+ if (cid) {
172
+ investorsByCid.add(String(cid));
173
+ }
174
+ if (piData.username) {
175
+ investorsByUsername.add(piData.username.toLowerCase().trim());
176
+ }
177
+ }
178
+
179
+ // Check which watchlist PIs are NOT in the master list
180
+ // Check by both CID and username to be thorough
181
+ const notInRankings = [];
182
+ for (const item of items) {
183
+ const cidStr = String(item.cid);
184
+ const username = item.username ? item.username.toLowerCase().trim() : null;
185
+
186
+ // Check if PI exists in master list by CID or username
187
+ const existsByCid = investorsByCid.has(cidStr);
188
+ const existsByUsername = username && investorsByUsername.has(username);
189
+
190
+ if (!existsByCid && !existsByUsername) {
191
+ notInRankings.push(item.cid);
192
+ }
193
+ }
194
+
195
+ return res.status(200).json({
196
+ notInRankings,
197
+ rankingsDate: null, // No longer using rankings date
198
+ totalChecked: items.length,
199
+ inRankings: items.length - notInRankings.length,
200
+ usingMasterList: true
201
+ });
202
+
203
+ } catch (error) {
204
+ logger.log('ERROR', `[checkPisInRankings] Error checking PIs in master list`, error);
205
+ return res.status(500).json({ error: error.message });
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Legacy method: Check against latest rankings date
211
+ * Used as fallback when master list is not available
212
+ */
213
+ async function checkPisInRankingsLegacy(db, items, config, logger) {
214
+ try {
140
215
  // Find latest available rankings date
141
216
  const rankingsCollection = config.popularInvestorRankingsCollection || process.env.FIRESTORE_COLLECTION_PI_RANKINGS || 'popular_investor_rankings';
142
217
  const rankingsDate = await findLatestRankingsDate(db, rankingsCollection, 30);
143
218
 
144
219
  if (!rankingsDate) {
145
220
  const notInRankings = items.map(item => item.cid);
146
- return res.status(200).json({
221
+ return {
147
222
  notInRankings,
148
223
  rankingsDate: null,
224
+ totalChecked: items.length,
225
+ inRankings: 0,
226
+ usingMasterList: false,
149
227
  message: "No rankings data available"
150
- });
228
+ };
151
229
  }
152
230
 
153
231
  // Fetch rankings data
@@ -156,11 +234,14 @@ async function checkPisInRankings(req, res, dependencies, config) {
156
234
 
157
235
  if (!rankingsDoc.exists) {
158
236
  const notInRankings = items.map(item => item.cid);
159
- return res.status(200).json({
237
+ return {
160
238
  notInRankings,
161
239
  rankingsDate: null,
240
+ totalChecked: items.length,
241
+ inRankings: 0,
242
+ usingMasterList: false,
162
243
  message: "Rankings document not found"
163
- });
244
+ };
164
245
  }
165
246
 
166
247
  const rankingsData = rankingsDoc.data();
@@ -183,16 +264,24 @@ async function checkPisInRankings(req, res, dependencies, config) {
183
264
  }
184
265
  }
185
266
 
186
- return res.status(200).json({
267
+ return {
187
268
  notInRankings,
188
269
  rankingsDate: rankingsDate,
189
270
  totalChecked: items.length,
190
- inRankings: items.length - notInRankings.length
191
- });
192
-
271
+ inRankings: items.length - notInRankings.length,
272
+ usingMasterList: false
273
+ };
193
274
  } catch (error) {
194
- logger.log('ERROR', `[checkPisInRankings] Error checking PIs in rankings`, error);
195
- return res.status(500).json({ error: error.message });
275
+ logger.log('ERROR', `[checkPisInRankingsLegacy] Error in legacy rankings check`, error);
276
+ // Return all as not in rankings on error
277
+ return {
278
+ notInRankings: items.map(item => item.cid),
279
+ rankingsDate: null,
280
+ totalChecked: items.length,
281
+ inRankings: 0,
282
+ usingMasterList: false,
283
+ message: error.message
284
+ };
196
285
  }
197
286
  }
198
287
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.518",
3
+ "version": "1.0.519",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [