bulltrackers-module 1.0.540 → 1.0.541
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.
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const { getPIMasterList } = require('../core/user_status_helpers');
|
|
7
|
+
const { tryDecompress } = require('../core/compression_helpers');
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* GET /user/search/pis
|
|
@@ -29,16 +30,111 @@ async function searchPopularInvestors(req, res, dependencies, config) {
|
|
|
29
30
|
// Search by username (case-insensitive, partial match)
|
|
30
31
|
const searchQuery = query.toLowerCase().trim();
|
|
31
32
|
const matches = [];
|
|
33
|
+
const rankingsCollection = config.popularInvestorRankingsCollection || process.env.FIRESTORE_COLLECTION_PI_RANKINGS || 'popular_investor_rankings';
|
|
34
|
+
|
|
35
|
+
// Cache rankings data by date to avoid fetching the same date multiple times
|
|
36
|
+
const rankingsCache = new Map();
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Helper to get rankings data for a specific date
|
|
40
|
+
*/
|
|
41
|
+
const getRankingsForDate = async (dateStr) => {
|
|
42
|
+
if (rankingsCache.has(dateStr)) {
|
|
43
|
+
return rankingsCache.get(dateStr);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const rankingsRef = db.collection(rankingsCollection).doc(dateStr);
|
|
48
|
+
const rankingsDoc = await rankingsRef.get();
|
|
49
|
+
|
|
50
|
+
if (!rankingsDoc.exists) {
|
|
51
|
+
rankingsCache.set(dateStr, null);
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const rawRankingsData = rankingsDoc.data();
|
|
56
|
+
const rankingsData = tryDecompress(rawRankingsData);
|
|
57
|
+
const rankingsItems = rankingsData.Items || [];
|
|
58
|
+
|
|
59
|
+
// Create a map for quick lookup by CID
|
|
60
|
+
const rankingsMap = new Map();
|
|
61
|
+
for (const item of rankingsItems) {
|
|
62
|
+
if (item.CustomerId) {
|
|
63
|
+
rankingsMap.set(String(item.CustomerId), item);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
rankingsCache.set(dateStr, rankingsMap);
|
|
68
|
+
return rankingsMap;
|
|
69
|
+
} catch (error) {
|
|
70
|
+
if (logger) {
|
|
71
|
+
logger.log('WARN', `[searchPopularInvestors] Failed to fetch rankings for date ${dateStr}: ${error.message}`);
|
|
72
|
+
}
|
|
73
|
+
rankingsCache.set(dateStr, null);
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Convert timestamp to date string (YYYY-MM-DD)
|
|
80
|
+
*/
|
|
81
|
+
const timestampToDateStr = (timestamp) => {
|
|
82
|
+
if (!timestamp) return null;
|
|
83
|
+
|
|
84
|
+
// Handle Firestore Timestamp
|
|
85
|
+
if (timestamp.toDate && typeof timestamp.toDate === 'function') {
|
|
86
|
+
return timestamp.toDate().toISOString().split('T')[0];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Handle Date object
|
|
90
|
+
if (timestamp instanceof Date) {
|
|
91
|
+
return timestamp.toISOString().split('T')[0];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Handle number (milliseconds since epoch)
|
|
95
|
+
if (typeof timestamp === 'number') {
|
|
96
|
+
return new Date(timestamp).toISOString().split('T')[0];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return null;
|
|
100
|
+
};
|
|
32
101
|
|
|
33
102
|
for (const [cid, investor] of Object.entries(investors)) {
|
|
34
103
|
if (investor.username) {
|
|
35
104
|
const username = investor.username.toLowerCase();
|
|
36
105
|
if (username.includes(searchQuery)) {
|
|
106
|
+
// Get ranking metrics from rankings collection using lastSeenAt
|
|
107
|
+
let aum = null;
|
|
108
|
+
let riskScore = null;
|
|
109
|
+
let gain = null;
|
|
110
|
+
let copiers = null;
|
|
111
|
+
|
|
112
|
+
if (investor.lastSeenAt) {
|
|
113
|
+
const rankingsDate = timestampToDateStr(investor.lastSeenAt);
|
|
114
|
+
|
|
115
|
+
if (rankingsDate) {
|
|
116
|
+
const rankingsMap = await getRankingsForDate(rankingsDate);
|
|
117
|
+
|
|
118
|
+
if (rankingsMap) {
|
|
119
|
+
const rankingEntry = rankingsMap.get(cid);
|
|
120
|
+
|
|
121
|
+
if (rankingEntry) {
|
|
122
|
+
aum = rankingEntry.AUMValue || null;
|
|
123
|
+
riskScore = rankingEntry.RiskScore || null;
|
|
124
|
+
gain = rankingEntry.Gain || null;
|
|
125
|
+
copiers = rankingEntry.Copiers || null;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
37
131
|
matches.push({
|
|
38
132
|
cid: Number(cid),
|
|
39
|
-
username: investor.username
|
|
40
|
-
|
|
41
|
-
|
|
133
|
+
username: investor.username,
|
|
134
|
+
aum: aum,
|
|
135
|
+
riskScore: riskScore,
|
|
136
|
+
gain: gain,
|
|
137
|
+
copiers: copiers
|
|
42
138
|
});
|
|
43
139
|
|
|
44
140
|
if (matches.length >= parseInt(limit)) {
|