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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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', `[
|
|
195
|
-
|
|
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
|
|