bulltrackers-module 1.0.592 → 1.0.593
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.
- package/functions/old-generic-api/admin-api/index.js +895 -0
- package/functions/old-generic-api/helpers/api_helpers.js +457 -0
- package/functions/old-generic-api/index.js +204 -0
- package/functions/old-generic-api/user-api/helpers/alerts/alert_helpers.js +355 -0
- package/functions/old-generic-api/user-api/helpers/alerts/subscription_helpers.js +327 -0
- package/functions/old-generic-api/user-api/helpers/alerts/test_alert_helpers.js +212 -0
- package/functions/old-generic-api/user-api/helpers/collection_helpers.js +193 -0
- package/functions/old-generic-api/user-api/helpers/core/compression_helpers.js +68 -0
- package/functions/old-generic-api/user-api/helpers/core/data_lookup_helpers.js +256 -0
- package/functions/old-generic-api/user-api/helpers/core/path_resolution_helpers.js +640 -0
- package/functions/old-generic-api/user-api/helpers/core/user_status_helpers.js +195 -0
- package/functions/old-generic-api/user-api/helpers/data/computation_helpers.js +503 -0
- package/functions/old-generic-api/user-api/helpers/data/instrument_helpers.js +55 -0
- package/functions/old-generic-api/user-api/helpers/data/portfolio_helpers.js +245 -0
- package/functions/old-generic-api/user-api/helpers/data/social_helpers.js +174 -0
- package/functions/old-generic-api/user-api/helpers/data_helpers.js +87 -0
- package/functions/old-generic-api/user-api/helpers/dev/dev_helpers.js +336 -0
- package/functions/old-generic-api/user-api/helpers/fetch/on_demand_fetch_helpers.js +615 -0
- package/functions/old-generic-api/user-api/helpers/metrics/personalized_metrics_helpers.js +231 -0
- package/functions/old-generic-api/user-api/helpers/notifications/notification_helpers.js +641 -0
- package/functions/old-generic-api/user-api/helpers/profile/pi_profile_helpers.js +182 -0
- package/functions/old-generic-api/user-api/helpers/profile/profile_view_helpers.js +137 -0
- package/functions/old-generic-api/user-api/helpers/profile/user_profile_helpers.js +190 -0
- package/functions/old-generic-api/user-api/helpers/recommendations/recommendation_helpers.js +66 -0
- package/functions/old-generic-api/user-api/helpers/reviews/review_helpers.js +550 -0
- package/functions/old-generic-api/user-api/helpers/rootdata/rootdata_aggregation_helpers.js +378 -0
- package/functions/old-generic-api/user-api/helpers/search/pi_request_helpers.js +295 -0
- package/functions/old-generic-api/user-api/helpers/search/pi_search_helpers.js +162 -0
- package/functions/old-generic-api/user-api/helpers/sync/user_sync_helpers.js +677 -0
- package/functions/old-generic-api/user-api/helpers/verification/verification_helpers.js +323 -0
- package/functions/old-generic-api/user-api/helpers/watchlist/watchlist_analytics_helpers.js +96 -0
- package/functions/old-generic-api/user-api/helpers/watchlist/watchlist_data_helpers.js +141 -0
- package/functions/old-generic-api/user-api/helpers/watchlist/watchlist_generation_helpers.js +310 -0
- package/functions/old-generic-api/user-api/helpers/watchlist/watchlist_management_helpers.js +829 -0
- package/functions/old-generic-api/user-api/index.js +109 -0
- package/package.json +2 -2
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Popular Investor Search Helpers
|
|
3
|
+
* Handles PI search endpoints
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { getPIMasterList } = require('../core/user_status_helpers');
|
|
7
|
+
const { tryDecompress } = require('../core/compression_helpers');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* GET /user/search/pis
|
|
11
|
+
* Search for Popular Investors by username
|
|
12
|
+
*/
|
|
13
|
+
async function searchPopularInvestors(req, res, dependencies, config) {
|
|
14
|
+
const { db, logger } = dependencies;
|
|
15
|
+
const { query, limit = 20 } = req.query;
|
|
16
|
+
|
|
17
|
+
if (!query || query.trim().length < 2) {
|
|
18
|
+
return res.status(400).json({ error: "Query must be at least 2 characters" });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
// Use master list instead of rankings (single source of truth)
|
|
23
|
+
const collectionRegistry = dependencies.collectionRegistry || null;
|
|
24
|
+
const investors = await getPIMasterList(db, collectionRegistry, logger);
|
|
25
|
+
|
|
26
|
+
if (Object.keys(investors).length === 0) {
|
|
27
|
+
return res.status(404).json({ error: "Popular investor data not available" });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Search by username (case-insensitive, partial match)
|
|
31
|
+
const searchQuery = query.toLowerCase().trim();
|
|
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
|
+
};
|
|
101
|
+
|
|
102
|
+
for (const [cid, investor] of Object.entries(investors)) {
|
|
103
|
+
if (investor.username) {
|
|
104
|
+
const username = investor.username.toLowerCase();
|
|
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
|
+
|
|
131
|
+
matches.push({
|
|
132
|
+
cid: Number(cid),
|
|
133
|
+
username: investor.username,
|
|
134
|
+
aum: aum,
|
|
135
|
+
riskScore: riskScore,
|
|
136
|
+
gain: gain,
|
|
137
|
+
copiers: copiers
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
if (matches.length >= parseInt(limit)) {
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return res.status(200).json({
|
|
148
|
+
results: matches,
|
|
149
|
+
count: matches.length,
|
|
150
|
+
query: query
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
} catch (error) {
|
|
154
|
+
logger.log('ERROR', `[searchPopularInvestors] Error searching PIs`, error);
|
|
155
|
+
return res.status(500).json({ error: error.message });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
module.exports = {
|
|
160
|
+
searchPopularInvestors
|
|
161
|
+
};
|
|
162
|
+
|