bulltrackers-module 1.0.514 → 1.0.516
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.
|
@@ -256,52 +256,85 @@ async function findLatestRankingsDate(db, rankingsCollection, maxDaysBack = 30)
|
|
|
256
256
|
|
|
257
257
|
/**
|
|
258
258
|
* [NEW] Fetches Popular Investors from the daily rankings document.
|
|
259
|
-
* UPDATED:
|
|
260
|
-
* This
|
|
259
|
+
* UPDATED: Checks last 7 days of rankings data and builds a de-duplicated map.
|
|
260
|
+
* This solves the problem of a single day where a given popular investor is not in the rankings.
|
|
261
|
+
* UPDATED: Uses collection registry for path resolution.
|
|
261
262
|
*/
|
|
262
263
|
async function getPopularInvestorsToUpdate(dependencies, config) {
|
|
263
|
-
const { db, logger } = dependencies;
|
|
264
|
-
|
|
264
|
+
const { db, logger, collectionRegistry } = dependencies;
|
|
265
|
+
|
|
266
|
+
// Get collection name from registry if available
|
|
267
|
+
let collectionName = config.popularInvestorRankingsCollection || process.env.FIRESTORE_COLLECTION_PI_RANKINGS || 'popular_investor_rankings';
|
|
268
|
+
|
|
269
|
+
if (collectionRegistry && collectionRegistry.getCollectionPath) {
|
|
270
|
+
try {
|
|
271
|
+
// Extract collection name from registry path: popular_investor_rankings/{date}
|
|
272
|
+
const samplePath = collectionRegistry.getCollectionPath('rootData', 'popularInvestorRankings', { date: '2025-01-01' });
|
|
273
|
+
collectionName = samplePath.split('/')[0];
|
|
274
|
+
} catch (err) {
|
|
275
|
+
logger.log('WARN', `[Core Utils] Failed to get collection from registry, using config: ${err.message}`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
265
278
|
|
|
266
|
-
logger.log('INFO', `[Core Utils] Getting Popular Investors to update from ${collectionName}...`);
|
|
279
|
+
logger.log('INFO', `[Core Utils] Getting Popular Investors to update from ${collectionName} (checking last 7 days)...`);
|
|
267
280
|
|
|
268
|
-
//
|
|
269
|
-
const today = new Date()
|
|
270
|
-
|
|
281
|
+
// Check last 7 days of rankings data
|
|
282
|
+
const today = new Date();
|
|
283
|
+
const piMap = new Map(); // De-duplicated map: cid -> { cid, username, latestDate }
|
|
271
284
|
|
|
272
285
|
try {
|
|
273
|
-
|
|
274
|
-
let
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
logger.log('WARN', `[Core Utils] No Popular Investor rankings found for date: ${today}. Looking for latest available date...`);
|
|
279
|
-
rankingsDate = await findLatestRankingsDate(db, collectionName, 30);
|
|
280
|
-
|
|
281
|
-
if (!rankingsDate) {
|
|
282
|
-
logger.log('WARN', `[Core Utils] No Popular Investor rankings found in last 30 days. Returning empty array.`);
|
|
283
|
-
return [];
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
logger.log('INFO', `[Core Utils] Using fallback rankings date: ${rankingsDate} (today: ${today})`);
|
|
287
|
-
docRef = db.collection(collectionName).doc(rankingsDate);
|
|
288
|
-
docSnap = await docRef.get();
|
|
286
|
+
// Check each of the last 7 days
|
|
287
|
+
for (let i = 0; i < 7; i++) {
|
|
288
|
+
const checkDate = new Date(today);
|
|
289
|
+
checkDate.setDate(checkDate.getDate() - i);
|
|
290
|
+
const dateStr = checkDate.toISOString().split('T')[0];
|
|
289
291
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
292
|
+
try {
|
|
293
|
+
const docRef = db.collection(collectionName).doc(dateStr);
|
|
294
|
+
const docSnap = await docRef.get();
|
|
295
|
+
|
|
296
|
+
if (docSnap.exists) {
|
|
297
|
+
const data = docSnap.data();
|
|
298
|
+
const items = data.Items || [];
|
|
299
|
+
|
|
300
|
+
// Add all PIs from this date to the map (de-duplicated by CID)
|
|
301
|
+
for (const item of items) {
|
|
302
|
+
const cid = String(item.CustomerId);
|
|
303
|
+
const username = item.UserName;
|
|
304
|
+
|
|
305
|
+
// Only add if not already in map, or if this date is more recent
|
|
306
|
+
if (!piMap.has(cid)) {
|
|
307
|
+
piMap.set(cid, {
|
|
308
|
+
cid: cid,
|
|
309
|
+
username: username,
|
|
310
|
+
latestDate: dateStr
|
|
311
|
+
});
|
|
312
|
+
} else {
|
|
313
|
+
// Update if this date is more recent
|
|
314
|
+
const existing = piMap.get(cid);
|
|
315
|
+
if (dateStr > existing.latestDate) {
|
|
316
|
+
existing.latestDate = dateStr;
|
|
317
|
+
if (username) existing.username = username;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
} catch (dateErr) {
|
|
323
|
+
logger.log('WARN', `[Core Utils] Error checking rankings for date ${dateStr}: ${dateErr.message}`);
|
|
324
|
+
continue; // Continue to next date
|
|
293
325
|
}
|
|
294
326
|
}
|
|
327
|
+
|
|
328
|
+
if (piMap.size === 0) {
|
|
329
|
+
logger.log('WARN', `[Core Utils] No Popular Investor rankings found in last 7 days. Returning empty array.`);
|
|
330
|
+
return [];
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
logger.log('INFO', `[Core Utils] Found ${piMap.size} unique Popular Investors across last 7 days of rankings.`);
|
|
295
334
|
|
|
296
|
-
|
|
297
|
-
const
|
|
298
|
-
|
|
299
|
-
// Map to { cid, username }
|
|
300
|
-
const allTargets = items.map(item => ({
|
|
301
|
-
cid: String(item.CustomerId),
|
|
302
|
-
username: item.UserName
|
|
303
|
-
}));
|
|
304
|
-
|
|
335
|
+
// Convert map to array and filter out PIs updated in the last 18 hours
|
|
336
|
+
const allTargets = Array.from(piMap.values());
|
|
337
|
+
|
|
305
338
|
// Filter out PIs updated in the last 18 hours
|
|
306
339
|
const HOURS_THRESHOLD = 18;
|
|
307
340
|
const thresholdMs = HOURS_THRESHOLD * 60 * 60 * 1000;
|
|
@@ -310,12 +343,29 @@ async function getPopularInvestorsToUpdate(dependencies, config) {
|
|
|
310
343
|
const filteredTargets = [];
|
|
311
344
|
let skippedCount = 0;
|
|
312
345
|
|
|
313
|
-
// Check each PI's last sync time
|
|
346
|
+
// Check each PI's last sync time using user-centric path
|
|
314
347
|
for (const target of allTargets) {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
348
|
+
// Use collection registry for sync status path if available
|
|
349
|
+
let syncRef;
|
|
350
|
+
if (collectionRegistry && collectionRegistry.getCollectionPath) {
|
|
351
|
+
try {
|
|
352
|
+
// syncStatus path is: SignedInUsers/{cid}/syncStatus/latest (document path)
|
|
353
|
+
const syncStatusPath = collectionRegistry.getCollectionPath('signedInUsers', 'syncStatus', { cid: target.cid });
|
|
354
|
+
syncRef = db.doc(syncStatusPath);
|
|
355
|
+
} catch (err) {
|
|
356
|
+
// Fallback to legacy path
|
|
357
|
+
syncRef = db.collection('user_sync_requests')
|
|
358
|
+
.doc(target.cid)
|
|
359
|
+
.collection('global')
|
|
360
|
+
.doc('latest');
|
|
361
|
+
}
|
|
362
|
+
} else {
|
|
363
|
+
// Fallback to legacy path
|
|
364
|
+
syncRef = db.collection('user_sync_requests')
|
|
365
|
+
.doc(target.cid)
|
|
366
|
+
.collection('global')
|
|
367
|
+
.doc('latest');
|
|
368
|
+
}
|
|
319
369
|
|
|
320
370
|
const syncDoc = await syncRef.get();
|
|
321
371
|
if (syncDoc.exists) {
|
|
@@ -336,7 +386,7 @@ async function getPopularInvestorsToUpdate(dependencies, config) {
|
|
|
336
386
|
logger.log('INFO', `[Core Utils Debug] First PI Target Mapped: ${JSON.stringify(filteredTargets[0])}`);
|
|
337
387
|
}
|
|
338
388
|
|
|
339
|
-
logger.log('INFO', `[Core Utils] Found ${allTargets.length} Popular Investors from
|
|
389
|
+
logger.log('INFO', `[Core Utils] Found ${allTargets.length} Popular Investors from last 7 days of rankings. Skipped ${skippedCount} recently updated. ${filteredTargets.length} will be updated.`);
|
|
340
390
|
return filteredTargets;
|
|
341
391
|
|
|
342
392
|
} catch (error) {
|
|
@@ -347,21 +397,37 @@ async function getPopularInvestorsToUpdate(dependencies, config) {
|
|
|
347
397
|
|
|
348
398
|
/**
|
|
349
399
|
* [NEW] Fetches Signed-In Users for daily update.
|
|
400
|
+
* UPDATED: Uses user-centric collection model and collection registry.
|
|
350
401
|
* Skips users updated in the last 18 hours to avoid duplicate refreshes.
|
|
351
402
|
*/
|
|
352
403
|
async function getSignedInUsersToUpdate(dependencies, config) {
|
|
353
|
-
const { db, logger } = dependencies;
|
|
354
|
-
|
|
404
|
+
const { db, logger, collectionRegistry } = dependencies;
|
|
405
|
+
|
|
406
|
+
// Get collection name from registry if available
|
|
407
|
+
let signedInUsersCollection = config.signedInUsersCollection || 'signedInUsers';
|
|
408
|
+
|
|
409
|
+
if (collectionRegistry && collectionRegistry.getCollectionPath) {
|
|
410
|
+
try {
|
|
411
|
+
// Extract collection name from registry path: signedInUsers/{firebaseUid}
|
|
412
|
+
const samplePath = collectionRegistry.getCollectionPath('signedInUsers', 'main', {});
|
|
413
|
+
signedInUsersCollection = samplePath.split('/')[0];
|
|
414
|
+
} catch (err) {
|
|
415
|
+
logger.log('WARN', `[Core Utils] Failed to get collection from registry, using config: ${err.message}`);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
355
418
|
|
|
356
419
|
// 18 hours threshold - skip users updated more recently than this
|
|
357
420
|
const HOURS_THRESHOLD = 18;
|
|
358
421
|
const thresholdMs = HOURS_THRESHOLD * 60 * 60 * 1000;
|
|
359
422
|
const now = Date.now();
|
|
423
|
+
const today = new Date().toISOString().split('T')[0];
|
|
360
424
|
|
|
361
|
-
logger.log('INFO', `[Core Utils] Getting Signed-In Users to update from ${
|
|
425
|
+
logger.log('INFO', `[Core Utils] Getting Signed-In Users to update from ${signedInUsersCollection} (skipping users updated in last ${HOURS_THRESHOLD} hours)...`);
|
|
362
426
|
|
|
363
427
|
try {
|
|
364
|
-
|
|
428
|
+
// Get all signed-in users from the main collection
|
|
429
|
+
// This collection maps Firebase UID to eToro CID
|
|
430
|
+
const snapshot = await db.collection(signedInUsersCollection).get();
|
|
365
431
|
|
|
366
432
|
if (snapshot.empty) {
|
|
367
433
|
logger.log('INFO', '[Core Utils] No Signed-In Users found.');
|
|
@@ -369,74 +435,67 @@ async function getSignedInUsersToUpdate(dependencies, config) {
|
|
|
369
435
|
}
|
|
370
436
|
|
|
371
437
|
const targets = [];
|
|
372
|
-
let skippedCount = 0;
|
|
373
438
|
|
|
439
|
+
// Extract CIDs from user documents
|
|
374
440
|
snapshot.forEach(doc => {
|
|
375
441
|
const data = doc.data();
|
|
376
|
-
const cid = data.cid ||
|
|
377
|
-
const username = data.username || 'Unknown';
|
|
442
|
+
const cid = data.etoroCID || data.cid || null;
|
|
443
|
+
const username = data.etoroUsername || data.username || 'Unknown';
|
|
378
444
|
|
|
379
445
|
if (!cid) return;
|
|
380
446
|
|
|
381
|
-
// Check if user was recently updated (check sync requests and portfolio timestamps)
|
|
382
|
-
// First check sync requests for last update time
|
|
383
|
-
let shouldSkip = false;
|
|
384
|
-
|
|
385
|
-
// Check user_sync_requests for last completed sync
|
|
386
|
-
// We'll check this asynchronously, but for now we'll check portfolio data timestamps
|
|
387
|
-
// The portfolio data is stored in snapshots with dates, so we can check the latest snapshot date
|
|
388
|
-
const portfolioCollection = collectionName; // Same collection
|
|
389
|
-
const blockId = '19M';
|
|
390
|
-
const today = new Date().toISOString().split('T')[0];
|
|
391
|
-
|
|
392
|
-
// For efficiency, we'll check the sync requests collection for the last update time
|
|
393
|
-
// This is a synchronous check we can do here
|
|
394
|
-
// Actually, we need to make this async, so we'll do a batch check after
|
|
395
|
-
|
|
396
|
-
// For now, include all users - we'll filter in the dispatcher
|
|
397
447
|
targets.push({ cid: String(cid), username });
|
|
398
448
|
});
|
|
399
449
|
|
|
400
|
-
|
|
401
|
-
|
|
450
|
+
logger.log('INFO', `[Core Utils] Found ${targets.length} Signed-In Users. Checking update status...`);
|
|
451
|
+
|
|
452
|
+
// Filter out users who were updated recently
|
|
402
453
|
const filteredTargets = [];
|
|
403
|
-
|
|
454
|
+
let skippedCount = 0;
|
|
455
|
+
|
|
404
456
|
const checkPromises = targets.map(async (target) => {
|
|
405
|
-
// Check if user has portfolio
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
// Check portfolio snapshot for today
|
|
409
|
-
const portfolioSnapshotRef = db.collection(collectionName)
|
|
410
|
-
.doc(blockId)
|
|
411
|
-
.collection('snapshots')
|
|
412
|
-
.doc(today)
|
|
413
|
-
.collection('parts')
|
|
414
|
-
.limit(1);
|
|
415
|
-
|
|
416
|
-
const portfolioSnapshot = await portfolioSnapshotRef.get();
|
|
457
|
+
// Check if user has portfolio data for today (using root data format)
|
|
458
|
+
let hasTodayData = false;
|
|
417
459
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
460
|
+
if (collectionRegistry && collectionRegistry.getCollectionPath) {
|
|
461
|
+
try {
|
|
462
|
+
// Check root data portfolio for today
|
|
463
|
+
const portfolioPath = collectionRegistry.getCollectionPath('rootData', 'signedInUserPortfolio', {
|
|
464
|
+
date: today,
|
|
465
|
+
cid: target.cid
|
|
466
|
+
});
|
|
467
|
+
const portfolioRef = db.doc(portfolioPath);
|
|
468
|
+
const portfolioDoc = await portfolioRef.get();
|
|
469
|
+
|
|
470
|
+
if (portfolioDoc.exists) {
|
|
471
|
+
hasTodayData = true;
|
|
472
|
+
}
|
|
473
|
+
} catch (err) {
|
|
474
|
+
logger.log('WARN', `[Core Utils] Error checking portfolio for ${target.cid}: ${err.message}`);
|
|
475
|
+
}
|
|
433
476
|
}
|
|
434
477
|
|
|
435
|
-
// Also check sync
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
478
|
+
// Also check sync status using user-centric path
|
|
479
|
+
let syncRef;
|
|
480
|
+
if (collectionRegistry && collectionRegistry.getCollectionPath) {
|
|
481
|
+
try {
|
|
482
|
+
// syncStatus path is: SignedInUsers/{cid}/syncStatus/latest (document path)
|
|
483
|
+
const syncStatusPath = collectionRegistry.getCollectionPath('signedInUsers', 'syncStatus', { cid: target.cid });
|
|
484
|
+
syncRef = db.doc(syncStatusPath);
|
|
485
|
+
} catch (err) {
|
|
486
|
+
// Fallback to legacy path
|
|
487
|
+
syncRef = db.collection('user_sync_requests')
|
|
488
|
+
.doc(target.cid)
|
|
489
|
+
.collection('global')
|
|
490
|
+
.doc('latest');
|
|
491
|
+
}
|
|
492
|
+
} else {
|
|
493
|
+
// Fallback to legacy path
|
|
494
|
+
syncRef = db.collection('user_sync_requests')
|
|
495
|
+
.doc(target.cid)
|
|
496
|
+
.collection('global')
|
|
497
|
+
.doc('latest');
|
|
498
|
+
}
|
|
440
499
|
|
|
441
500
|
const syncDoc = await syncRef.get();
|
|
442
501
|
if (syncDoc.exists) {
|
|
@@ -446,10 +505,16 @@ async function getSignedInUsersToUpdate(dependencies, config) {
|
|
|
446
505
|
|
|
447
506
|
if (lastRequestedAt && (now - lastRequestedAt) < thresholdMs) {
|
|
448
507
|
skippedCount++;
|
|
449
|
-
return null; // Skip this user
|
|
508
|
+
return null; // Skip this user - recently updated
|
|
450
509
|
}
|
|
451
510
|
}
|
|
452
511
|
|
|
512
|
+
// If user has data for today, skip them (they're already up-to-date)
|
|
513
|
+
if (hasTodayData) {
|
|
514
|
+
skippedCount++;
|
|
515
|
+
return null; // Skip this user - already updated today
|
|
516
|
+
}
|
|
517
|
+
|
|
453
518
|
return target; // Include this user - needs update
|
|
454
519
|
});
|
|
455
520
|
|
|
@@ -30,6 +30,15 @@ async function requestUserSync(req, res, dependencies, config) {
|
|
|
30
30
|
});
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
// Check the referrer or source header to determine if this is from PI page or profile page
|
|
34
|
+
const referrer = req.headers.referer || req.headers.referrer || '';
|
|
35
|
+
const sourcePage = req.query?.sourcePage || req.body?.sourcePage || '';
|
|
36
|
+
|
|
37
|
+
// Determine user type based on where the request came from
|
|
38
|
+
// If from /popular-investors/{cid}, it's a PI
|
|
39
|
+
// If from /profile, it's a signed-in user
|
|
40
|
+
const isPI = referrer.includes('/popular-investors/') || sourcePage === 'popular-investor';
|
|
41
|
+
|
|
33
42
|
// Check for dev override impersonation
|
|
34
43
|
const { getEffectiveCid, getDevOverride } = require('../dev/dev_helpers');
|
|
35
44
|
const effectiveCid = await getEffectiveCid(db, requestingUserCid, config, logger);
|
|
@@ -71,103 +80,17 @@ async function requestUserSync(req, res, dependencies, config) {
|
|
|
71
80
|
logger.log('INFO', `[requestUserSync] Developer account ${requestingUserCid} bypassing rate limits${isImpersonating ? ` (impersonating ${effectiveCid})` : ''}`);
|
|
72
81
|
}
|
|
73
82
|
|
|
74
|
-
//
|
|
75
|
-
// Check if user is in rankings (PI) or has signed-in user data
|
|
76
|
-
const { checkIfUserIsPI } = require('../data_helpers');
|
|
77
|
-
let isPI = await checkIfUserIsPI(db, targetCidNum, config, logger);
|
|
78
|
-
|
|
79
|
-
// Fallback: If not found in rankings, check if they have PI data (portfolio, history, or social)
|
|
80
|
-
// This handles cases where a PI might not be in the latest rankings but has historical data
|
|
81
|
-
if (!isPI) {
|
|
82
|
-
try {
|
|
83
|
-
const today = new Date().toISOString().split('T')[0];
|
|
84
|
-
let hasPiData = false;
|
|
85
|
-
|
|
86
|
-
// Check if they have PI portfolio data (any date in last 7 days)
|
|
87
|
-
for (let i = 0; i <= 7; i++) {
|
|
88
|
-
const checkDate = new Date();
|
|
89
|
-
checkDate.setDate(checkDate.getDate() - i);
|
|
90
|
-
const dateStr = checkDate.toISOString().split('T')[0];
|
|
91
|
-
|
|
92
|
-
// Check portfolio data
|
|
93
|
-
const portfolioRef = db.collection('PopularInvestorPortfolioData')
|
|
94
|
-
.doc(dateStr)
|
|
95
|
-
.collection(String(targetCidNum))
|
|
96
|
-
.doc(String(targetCidNum));
|
|
97
|
-
const portfolioDoc = await portfolioRef.get();
|
|
98
|
-
|
|
99
|
-
if (portfolioDoc.exists) {
|
|
100
|
-
logger.log('INFO', `[requestUserSync] User ${targetCidNum} has PI portfolio data from ${dateStr}, treating as PI`);
|
|
101
|
-
hasPiData = true;
|
|
102
|
-
break;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Check trade history data
|
|
106
|
-
const historyRef = db.collection('PopularInvestorTradeHistoryData')
|
|
107
|
-
.doc(dateStr)
|
|
108
|
-
.collection(String(targetCidNum))
|
|
109
|
-
.doc(String(targetCidNum));
|
|
110
|
-
const historyDoc = await historyRef.get();
|
|
111
|
-
|
|
112
|
-
if (historyDoc.exists) {
|
|
113
|
-
logger.log('INFO', `[requestUserSync] User ${targetCidNum} has PI trade history data from ${dateStr}, treating as PI`);
|
|
114
|
-
hasPiData = true;
|
|
115
|
-
break;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Check social posts data
|
|
119
|
-
const socialRef = db.collection('PopularInvestorSocialPostData')
|
|
120
|
-
.doc(dateStr)
|
|
121
|
-
.collection(String(targetCidNum))
|
|
122
|
-
.doc(String(targetCidNum));
|
|
123
|
-
const socialDoc = await socialRef.get();
|
|
124
|
-
|
|
125
|
-
if (socialDoc.exists) {
|
|
126
|
-
logger.log('INFO', `[requestUserSync] User ${targetCidNum} has PI social posts data from ${dateStr}, treating as PI`);
|
|
127
|
-
hasPiData = true;
|
|
128
|
-
break;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (hasPiData) {
|
|
133
|
-
isPI = { CustomerId: targetCidNum, UserName: null }; // Fake ranking entry
|
|
134
|
-
} else {
|
|
135
|
-
logger.log('INFO', `[requestUserSync] User ${targetCidNum} not found in rankings and no PI data found, treating as signed-in user`);
|
|
136
|
-
}
|
|
137
|
-
} catch (fallbackErr) {
|
|
138
|
-
logger.log('WARN', `[requestUserSync] Error checking PI data fallback for ${targetCidNum}:`, fallbackErr.message);
|
|
139
|
-
}
|
|
140
|
-
} else {
|
|
141
|
-
logger.log('INFO', `[requestUserSync] User ${targetCidNum} found in rankings as PI`);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Get username - for PIs, get from rankings; for signed-in users, we'll need to fetch from their profile
|
|
83
|
+
// Get username based on user type
|
|
145
84
|
let username = null;
|
|
146
85
|
if (isPI) {
|
|
147
|
-
//
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
const { findLatestRankingsDate } = require('../data_helpers');
|
|
153
|
-
const rankingsCollection = config.popularInvestorRankingsCollection || 'popular_investor_rankings';
|
|
154
|
-
const rankingsDate = await findLatestRankingsDate(db, rankingsCollection, 30);
|
|
155
|
-
|
|
156
|
-
if (rankingsDate) {
|
|
157
|
-
const rankingsRef = db.collection(rankingsCollection).doc(rankingsDate);
|
|
158
|
-
const rankingsDoc = await rankingsRef.get();
|
|
159
|
-
if (rankingsDoc.exists) {
|
|
160
|
-
const rankingsData = rankingsDoc.data();
|
|
161
|
-
const rankingsItems = rankingsData.Items || [];
|
|
162
|
-
const rankingEntry = rankingsItems.find(item => Number(item.CustomerId) === targetCidNum);
|
|
163
|
-
if (rankingEntry) {
|
|
164
|
-
username = rankingEntry.UserName || rankingEntry.username || null;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
86
|
+
// For PIs, get username from rankings
|
|
87
|
+
const { checkIfUserIsPI } = require('../core/user_status_helpers');
|
|
88
|
+
const rankEntry = await checkIfUserIsPI(db, targetCidNum, config, logger);
|
|
89
|
+
if (rankEntry && (rankEntry.UserName || rankEntry.username)) {
|
|
90
|
+
username = rankEntry.UserName || rankEntry.username;
|
|
168
91
|
}
|
|
169
92
|
} else {
|
|
170
|
-
// For signed-in users, try
|
|
93
|
+
// For signed-in users, try verification data
|
|
171
94
|
const signedInUsersCollection = config.signedInUsersCollection || 'signed_in_users';
|
|
172
95
|
const userDoc = await db.collection(signedInUsersCollection).doc(String(targetCidNum)).get();
|
|
173
96
|
if (userDoc.exists) {
|
|
@@ -177,8 +100,7 @@ async function requestUserSync(req, res, dependencies, config) {
|
|
|
177
100
|
}
|
|
178
101
|
|
|
179
102
|
if (!username) {
|
|
180
|
-
|
|
181
|
-
username = String(targetCidNum);
|
|
103
|
+
username = String(targetCidNum); // Fallback to CID
|
|
182
104
|
}
|
|
183
105
|
|
|
184
106
|
// Create request document
|
|
@@ -832,9 +832,9 @@ async function handleOnDemandUserUpdate(taskData, config, dependencies) {
|
|
|
832
832
|
logger.log('INFO', `[On-Demand Update] Skipping history fetch (portfolioOnly=false) for ${username}`);
|
|
833
833
|
}
|
|
834
834
|
|
|
835
|
-
// Fetch social data
|
|
835
|
+
// Fetch social data - always for on-demand syncs
|
|
836
836
|
let socialFetched = false;
|
|
837
|
-
if (
|
|
837
|
+
if (username) {
|
|
838
838
|
try {
|
|
839
839
|
logger.log('INFO', `[On-Demand Update] Fetching social data for ${username} (${cid})...`);
|
|
840
840
|
|