bulltrackers-module 1.0.629 → 1.0.631

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.
Files changed (42) hide show
  1. package/functions/alert-system/helpers/alert_helpers.js +69 -77
  2. package/functions/alert-system/index.js +19 -29
  3. package/functions/api-v2/helpers/notification_helpers.js +187 -0
  4. package/functions/computation-system/helpers/computation_worker.js +1 -1
  5. package/functions/task-engine/helpers/popular_investor_helpers.js +11 -7
  6. package/index.js +0 -5
  7. package/package.json +1 -2
  8. package/functions/old-generic-api/admin-api/index.js +0 -895
  9. package/functions/old-generic-api/helpers/api_helpers.js +0 -457
  10. package/functions/old-generic-api/index.js +0 -204
  11. package/functions/old-generic-api/user-api/helpers/alerts/alert_helpers.js +0 -355
  12. package/functions/old-generic-api/user-api/helpers/alerts/subscription_helpers.js +0 -327
  13. package/functions/old-generic-api/user-api/helpers/alerts/test_alert_helpers.js +0 -212
  14. package/functions/old-generic-api/user-api/helpers/collection_helpers.js +0 -193
  15. package/functions/old-generic-api/user-api/helpers/core/compression_helpers.js +0 -68
  16. package/functions/old-generic-api/user-api/helpers/core/data_lookup_helpers.js +0 -256
  17. package/functions/old-generic-api/user-api/helpers/core/path_resolution_helpers.js +0 -640
  18. package/functions/old-generic-api/user-api/helpers/core/user_status_helpers.js +0 -195
  19. package/functions/old-generic-api/user-api/helpers/data/computation_helpers.js +0 -503
  20. package/functions/old-generic-api/user-api/helpers/data/instrument_helpers.js +0 -55
  21. package/functions/old-generic-api/user-api/helpers/data/portfolio_helpers.js +0 -245
  22. package/functions/old-generic-api/user-api/helpers/data/social_helpers.js +0 -174
  23. package/functions/old-generic-api/user-api/helpers/data_helpers.js +0 -87
  24. package/functions/old-generic-api/user-api/helpers/dev/dev_helpers.js +0 -336
  25. package/functions/old-generic-api/user-api/helpers/fetch/on_demand_fetch_helpers.js +0 -615
  26. package/functions/old-generic-api/user-api/helpers/metrics/personalized_metrics_helpers.js +0 -231
  27. package/functions/old-generic-api/user-api/helpers/notifications/notification_helpers.js +0 -641
  28. package/functions/old-generic-api/user-api/helpers/profile/pi_profile_helpers.js +0 -182
  29. package/functions/old-generic-api/user-api/helpers/profile/profile_view_helpers.js +0 -137
  30. package/functions/old-generic-api/user-api/helpers/profile/user_profile_helpers.js +0 -190
  31. package/functions/old-generic-api/user-api/helpers/recommendations/recommendation_helpers.js +0 -66
  32. package/functions/old-generic-api/user-api/helpers/reviews/review_helpers.js +0 -550
  33. package/functions/old-generic-api/user-api/helpers/rootdata/rootdata_aggregation_helpers.js +0 -378
  34. package/functions/old-generic-api/user-api/helpers/search/pi_request_helpers.js +0 -295
  35. package/functions/old-generic-api/user-api/helpers/search/pi_search_helpers.js +0 -162
  36. package/functions/old-generic-api/user-api/helpers/sync/user_sync_helpers.js +0 -677
  37. package/functions/old-generic-api/user-api/helpers/verification/verification_helpers.js +0 -323
  38. package/functions/old-generic-api/user-api/helpers/watchlist/watchlist_analytics_helpers.js +0 -96
  39. package/functions/old-generic-api/user-api/helpers/watchlist/watchlist_data_helpers.js +0 -141
  40. package/functions/old-generic-api/user-api/helpers/watchlist/watchlist_generation_helpers.js +0 -310
  41. package/functions/old-generic-api/user-api/helpers/watchlist/watchlist_management_helpers.js +0 -829
  42. package/functions/old-generic-api/user-api/index.js +0 -109
@@ -1,182 +0,0 @@
1
- /**
2
- * @fileoverview Popular Investor Profile Helpers
3
- * Handles PI profile and analytics endpoints with migration support
4
- */
5
-
6
- const { findLatestComputationDate } = require('../core/data_lookup_helpers');
7
- const { checkPiInComputationDate } = require('../data/computation_helpers');
8
- const { readWithMigration } = require('../core/path_resolution_helpers');
9
-
10
- /**
11
- * GET /pi/:cid/analytics
12
- * Fetches pre-computed analytics from the 'analytics_results' collection
13
- */
14
- async function getPiAnalytics(req, res, dependencies, config) {
15
- const { db } = dependencies;
16
- const { cid } = req.params;
17
-
18
- try {
19
- // Try new path first with migration
20
- const result = await readWithMigration(
21
- db,
22
- 'popularInvestors',
23
- 'analytics',
24
- { piCid: cid },
25
- {
26
- isCollection: false,
27
- dataType: 'piAnalytics',
28
- config,
29
- documentId: String(cid),
30
- collectionRegistry: dependencies.collectionRegistry
31
- }
32
- );
33
-
34
- if (result && result.data) {
35
- return res.status(200).json(result.data);
36
- }
37
-
38
- // Fallback to legacy collection
39
- const docRef = db.collection(config.piAnalyticsSummaryCollection || 'pi_analytics_summary').doc(String(cid));
40
- const doc = await docRef.get();
41
-
42
- if (!doc.exists) {
43
- return res.status(404).json({ error: "No analytics found for this user." });
44
- }
45
-
46
- return res.status(200).json(doc.data());
47
-
48
- } catch (error) {
49
- return res.status(500).json({ error: error.message });
50
- }
51
- }
52
-
53
- /**
54
- * GET /pi/:cid/profile
55
- * Fetches Popular Investor profile data from computation
56
- * Falls back to latest available date if today's data doesn't exist
57
- */
58
- async function getPiProfile(req, res, dependencies, config) {
59
- const { db, logger } = dependencies;
60
- const { cid } = req.params;
61
-
62
- if (!cid) {
63
- return res.status(400).json({ error: "Missing PI CID" });
64
- }
65
-
66
- try {
67
- const insightsCollection = config.unifiedInsightsCollection || 'unified_insights';
68
- const resultsSub = config.resultsSubcollection || 'results';
69
- const compsSub = config.computationsSubcollection || 'computations';
70
- const computationName = 'PopularInvestorProfileMetrics';
71
- const category = 'popular-investor';
72
- const today = new Date().toISOString().split('T')[0];
73
- const cidStr = String(cid);
74
- const maxDaysBackForPi = 7;
75
-
76
- logger.log('INFO', `[getPiProfile] Starting search for PI CID: ${cid}`);
77
-
78
- // Find latest available computation date
79
- const latestDate = await findLatestComputationDate(
80
- db,
81
- insightsCollection,
82
- resultsSub,
83
- compsSub,
84
- category,
85
- computationName,
86
- null,
87
- 30
88
- );
89
-
90
- logger.log('INFO', `[getPiProfile] Latest computation date found: ${latestDate || 'NONE'}`);
91
-
92
- if (!latestDate) {
93
- logger.log('WARN', `[getPiProfile] No computation document found for ${computationName} in last 30 days`);
94
- return res.status(404).json({
95
- error: "Profile data not available",
96
- message: "No computation results found for this Popular Investor"
97
- });
98
- }
99
-
100
- // Try to find the PI starting from the latest date, then going back up to 7 days
101
- let foundDate = null;
102
- let profileData = null;
103
- let checkedDates = [];
104
-
105
- const latestDateObj = new Date(latestDate + 'T00:00:00Z');
106
-
107
- for (let daysBack = 0; daysBack <= maxDaysBackForPi; daysBack++) {
108
- const checkDate = new Date(latestDateObj);
109
- checkDate.setUTCDate(latestDateObj.getUTCDate() - daysBack);
110
- const dateStr = checkDate.toISOString().split('T')[0];
111
- checkedDates.push(dateStr);
112
-
113
- logger.log('INFO', `[getPiProfile] Checking date ${dateStr} for CID ${cid} (${daysBack} days back from latest)`);
114
-
115
- const result = await checkPiInComputationDate(
116
- db,
117
- insightsCollection,
118
- resultsSub,
119
- compsSub,
120
- category,
121
- computationName,
122
- dateStr,
123
- cidStr,
124
- logger
125
- );
126
-
127
- if (result.found) {
128
- foundDate = dateStr;
129
- profileData = result.profileData;
130
- logger.log('SUCCESS', `[getPiProfile] Found profile data for CID ${cid} in date ${dateStr} (${daysBack} days back from latest)`);
131
- break;
132
- } else {
133
- logger.log('INFO', `[getPiProfile] CID ${cid} not found in date ${dateStr}`);
134
- }
135
- }
136
-
137
- // If not found in any checked date, return 404
138
- if (!foundDate || !profileData) {
139
- logger.log('WARN', `[getPiProfile] CID ${cid} not found in any checked dates: ${checkedDates.join(', ')}`);
140
-
141
- // Note: With the new pages subcollection structure, we can't enumerate all available CIDs
142
- // from a single document, so we skip that debug information
143
- return res.status(404).json({
144
- error: "Profile data not found",
145
- message: `Popular Investor ${cid} does not exist in computation results for the last ${maxDaysBackForPi + 1} days. This PI may not have been processed recently.`,
146
- debug: {
147
- searchedCid: cidStr,
148
- checkedDates: checkedDates,
149
- latestDate: latestDate
150
- }
151
- });
152
- }
153
-
154
- // Get username from rankings
155
- const { getPiUsername } = require('../fetch/on_demand_fetch_helpers');
156
- const username = await getPiUsername(db, cid, config, logger);
157
-
158
- logger.log('SUCCESS', `[getPiProfile] Returning profile data for CID ${cid} from date ${foundDate} (requested: ${today}, latest available: ${latestDate})`);
159
-
160
- return res.status(200).json({
161
- status: 'success',
162
- cid: cidStr,
163
- username: username || null,
164
- data: profileData,
165
- isFallback: foundDate !== latestDate || foundDate !== today,
166
- dataDate: foundDate,
167
- latestComputationDate: latestDate,
168
- requestedDate: today,
169
- daysBackFromLatest: checkedDates.indexOf(foundDate)
170
- });
171
-
172
- } catch (error) {
173
- logger.log('ERROR', `[getPiProfile] Error fetching PI profile for ${cid}`, error);
174
- return res.status(500).json({ error: error.message });
175
- }
176
- }
177
-
178
- module.exports = {
179
- getPiAnalytics,
180
- getPiProfile
181
- };
182
-
@@ -1,137 +0,0 @@
1
- /**
2
- * @fileoverview Profile View Tracking Helpers
3
- * Handles profile view tracking with migration support
4
- */
5
-
6
- const { FieldValue } = require('@google-cloud/firestore');
7
- const { readWithMigration, writeWithMigration, getCidFromFirebaseUid } = require('../core/path_resolution_helpers');
8
- const { updatePageViewsRootData } = require('../rootdata/rootdata_aggregation_helpers');
9
-
10
- /**
11
- * POST /pi/:piCid/track-view
12
- * Tracks a profile view for a Popular Investor
13
- * Migrates from legacy path to new path automatically
14
- */
15
- async function trackProfileView(req, res, dependencies, config) {
16
- const { db, logger } = dependencies;
17
- const { piCid } = req.params;
18
- const { viewerCid, viewerType = 'anonymous', firebaseUid } = req.body;
19
-
20
- if (!piCid) {
21
- return res.status(400).json({ error: "Missing piCid" });
22
- }
23
-
24
- try {
25
- // If firebaseUid is provided, get CID
26
- let effectiveViewerCid = viewerCid;
27
- if (firebaseUid && !viewerCid) {
28
- effectiveViewerCid = await getCidFromFirebaseUid(db, firebaseUid);
29
- }
30
-
31
- const today = new Date().toISOString().split('T')[0];
32
- const timestamp = Date.now();
33
-
34
- // Create/update daily view document (new path)
35
- const viewData = {
36
- piCid: Number(piCid),
37
- date: today,
38
- totalViews: FieldValue.increment(1),
39
- lastUpdated: FieldValue.serverTimestamp()
40
- };
41
-
42
- // Add unique viewers if viewer CID is provided
43
- if (effectiveViewerCid) {
44
- // Read existing to merge unique viewers
45
- // Note: date is already in path params, so don't provide documentId
46
- const existingResult = await readWithMigration(
47
- db,
48
- 'popularInvestors',
49
- 'profileViews',
50
- { piCid, date: today },
51
- {
52
- isCollection: false,
53
- dataType: 'piProfileViews',
54
- config,
55
- // documentId not needed - date is already in path params
56
- collectionRegistry: dependencies.collectionRegistry
57
- }
58
- );
59
-
60
- const existingUniqueViewers = existingResult?.data?.uniqueViewers || [];
61
- const viewerCidStr = String(effectiveViewerCid);
62
-
63
- if (!existingUniqueViewers.includes(viewerCidStr)) {
64
- viewData.uniqueViewers = [...existingUniqueViewers, viewerCidStr];
65
- } else {
66
- viewData.uniqueViewers = existingUniqueViewers;
67
- }
68
- }
69
-
70
- // Write to new path (with dual write to legacy during migration)
71
- // Note: date is already in path params, so don't provide documentId
72
- await writeWithMigration(
73
- db,
74
- 'popularInvestors',
75
- 'profileViews',
76
- { piCid, date: today },
77
- viewData,
78
- {
79
- isCollection: false,
80
- merge: true,
81
- dataType: 'piProfileViews',
82
- config,
83
- // documentId not needed - date is already in path params
84
- dualWrite: true,
85
- collectionRegistry: dependencies.collectionRegistry
86
- }
87
- );
88
-
89
- // Track individual view if viewer CID is provided
90
- if (effectiveViewerCid) {
91
- const viewId = `${piCid}_${effectiveViewerCid}_${timestamp}`;
92
- const individualViewData = {
93
- piCid: Number(piCid),
94
- viewerCid: Number(effectiveViewerCid),
95
- viewerType: viewerType,
96
- viewedAt: FieldValue.serverTimestamp(),
97
- date: today
98
- };
99
-
100
- // Write individual view (new path)
101
- // Note: viewId is already in path params, so don't provide documentId
102
- await writeWithMigration(
103
- db,
104
- 'popularInvestors',
105
- 'individualViews',
106
- { piCid, viewId },
107
- individualViewData,
108
- {
109
- isCollection: false,
110
- merge: true,
111
- dataType: 'piIndividualViews',
112
- config,
113
- // documentId not needed - viewId is already in path params
114
- dualWrite: true,
115
- collectionRegistry: dependencies.collectionRegistry
116
- }
117
- );
118
- }
119
-
120
- // Update global rootdata collection for computation system
121
- if (effectiveViewerCid) {
122
- await updatePageViewsRootData(db, logger, piCid, effectiveViewerCid, today);
123
- }
124
-
125
- return res.status(200).json({ success: true, message: "View tracked" });
126
-
127
- } catch (error) {
128
- logger.log('ERROR', `[trackProfileView] Error tracking view for PI ${piCid}:`, error);
129
- // Don't fail the request if tracking fails
130
- return res.status(200).json({ success: false, message: "View tracking failed but request succeeded" });
131
- }
132
- }
133
-
134
- module.exports = {
135
- trackProfileView
136
- };
137
-
@@ -1,190 +0,0 @@
1
- /**
2
- * @fileoverview User Profile Helpers
3
- * Handles signed-in user profile endpoints with migration support
4
- */
5
-
6
- const { checkIfUserIsPI } = require('../core/user_status_helpers');
7
- const { readWithMigration, getCidFromFirebaseUid } = require('../core/path_resolution_helpers');
8
- const { getEffectiveCid, getDevOverride } = require('../dev/dev_helpers');
9
-
10
- /**
11
- * GET /user/me/verification
12
- * Fetches the signed-in user's verification data (includes avatar URL)
13
- * Uses migration to read from new path or legacy path
14
- */
15
- async function getUserVerification(req, res, dependencies, config) {
16
- const { db, logger } = dependencies;
17
- const { userCid, firebaseUid } = req.query;
18
-
19
- if (!userCid && !firebaseUid) {
20
- return res.status(400).json({ error: "Missing userCid or firebaseUid" });
21
- }
22
-
23
- try {
24
- // Get CID if firebaseUid provided
25
- let effectiveCid = userCid ? Number(userCid) : null;
26
- if (firebaseUid && !effectiveCid) {
27
- effectiveCid = await getCidFromFirebaseUid(db, firebaseUid);
28
- if (!effectiveCid) {
29
- return res.status(404).json({ error: "User not found" });
30
- }
31
- }
32
-
33
- // Check for dev override impersonation
34
- const devOverride = await getDevOverride(db, effectiveCid, config, logger);
35
- const isImpersonating = devOverride && devOverride.enabled && devOverride.impersonateCid && effectiveCid !== Number(userCid);
36
-
37
- if (isImpersonating) {
38
- effectiveCid = devOverride.impersonateCid;
39
- }
40
-
41
- // If impersonating a PI, try to get username from rankings
42
- if (isImpersonating) {
43
- const rankEntry = await checkIfUserIsPI(db, effectiveCid, config, logger);
44
- if (rankEntry) {
45
- return res.status(200).json({
46
- avatar: null,
47
- username: rankEntry.UserName || null,
48
- fullName: null,
49
- cid: effectiveCid,
50
- verifiedAt: null,
51
- isImpersonating: true,
52
- effectiveCid: effectiveCid,
53
- actualCid: Number(userCid)
54
- });
55
- }
56
- }
57
-
58
- // Try to read from new path with migration
59
- const result = await readWithMigration(
60
- db,
61
- 'signedInUsers',
62
- 'verification',
63
- { cid: effectiveCid },
64
- {
65
- collectionRegistry: dependencies.collectionRegistry,
66
- isCollection: false,
67
- dataType: 'verification',
68
- config,
69
- logger,
70
- documentId: 'data'
71
- }
72
- );
73
-
74
- if (result && result.data) {
75
- return res.status(200).json({
76
- avatar: result.data.avatar || null,
77
- username: result.data.username || null,
78
- fullName: result.data.fullName || null,
79
- cid: result.data.cid || effectiveCid,
80
- verifiedAt: result.data.verifiedAt || null,
81
- isImpersonating: isImpersonating || false,
82
- effectiveCid: effectiveCid,
83
- actualCid: Number(userCid),
84
- migrated: result.source === 'legacy'
85
- });
86
- }
87
-
88
- // Fallback to legacy: try reading from signedInUsers main document
89
- const { signedInUsersCollection } = config;
90
- const userDocRef = db.collection(signedInUsersCollection).doc(String(effectiveCid));
91
- const userDoc = await userDocRef.get();
92
-
93
- if (!userDoc.exists) {
94
- return res.status(404).json({ error: "User verification data not found" });
95
- }
96
-
97
- const data = userDoc.data();
98
-
99
- return res.status(200).json({
100
- avatar: data.avatar || null,
101
- username: data.username || data.etoroUsername || null,
102
- fullName: data.displayName || null,
103
- cid: data.cid || data.etoroCID || effectiveCid,
104
- verifiedAt: data.verifiedAt || null,
105
- isImpersonating: isImpersonating || false,
106
- effectiveCid: effectiveCid,
107
- actualCid: Number(userCid)
108
- });
109
-
110
- } catch (error) {
111
- logger.log('ERROR', `[getUserVerification] Error fetching verification for ${userCid}`, error);
112
- return res.status(500).json({ error: error.message });
113
- }
114
- }
115
-
116
- /**
117
- * GET /user/me/is-popular-investor
118
- * Check if signed-in user is also a Popular Investor
119
- * Supports dev override impersonation
120
- */
121
- async function checkIfUserIsPopularInvestor(req, res, dependencies, config) {
122
- const { db, logger } = dependencies;
123
- const { userCid } = req.query;
124
-
125
- if (!userCid) {
126
- return res.status(400).json({ error: "Missing userCid" });
127
- }
128
-
129
- try {
130
- // Check for dev override impersonation
131
- const devOverride = await getDevOverride(db, userCid, config, logger);
132
-
133
- // For PI-related checks, respect pretendToBePI flag
134
- // If pretendToBePI is false, use actual userCid even if impersonateCid is set
135
- let effectiveCid;
136
- if (devOverride && devOverride.enabled && devOverride.pretendToBePI === false) {
137
- // User explicitly doesn't want to pretend to be a PI, use actual CID
138
- effectiveCid = Number(userCid);
139
- } else {
140
- // Use normal effective CID logic (may use impersonateCid if set)
141
- effectiveCid = await getEffectiveCid(db, userCid, config, logger);
142
- }
143
-
144
- const isImpersonating = devOverride && devOverride.enabled && devOverride.impersonateCid && effectiveCid !== Number(userCid);
145
-
146
- // Use effective CID (impersonated or actual) to check PI status
147
- const rankEntry = await checkIfUserIsPI(db, effectiveCid, config, logger);
148
-
149
- if (!rankEntry) {
150
- return res.status(200).json({
151
- isPopularInvestor: false,
152
- rankingData: null,
153
- isImpersonating: isImpersonating || false,
154
- effectiveCid: effectiveCid
155
- });
156
- }
157
-
158
- // Check if this is a dev override (pretendToBePI)
159
- const isDevOverride = devOverride && devOverride.enabled && devOverride.pretendToBePI;
160
-
161
- // Return ranking data
162
- return res.status(200).json({
163
- isPopularInvestor: true,
164
- rankingData: {
165
- cid: rankEntry.CustomerId,
166
- username: rankEntry.UserName,
167
- aum: rankEntry.AUMValue || 0,
168
- copiers: rankEntry.Copiers || 0,
169
- riskScore: rankEntry.RiskScore || 0,
170
- gain: rankEntry.Gain || 0,
171
- winRatio: rankEntry.WinRatio || 0,
172
- trades: rankEntry.Trades || 0
173
- },
174
- isDevOverride: isDevOverride || false,
175
- isImpersonating: isImpersonating || false,
176
- effectiveCid: effectiveCid,
177
- actualCid: Number(userCid)
178
- });
179
-
180
- } catch (error) {
181
- logger.log('ERROR', `[checkIfUserIsPopularInvestor] Error checking PI status for ${userCid}:`, error);
182
- return res.status(500).json({ error: error.message });
183
- }
184
- }
185
-
186
- module.exports = {
187
- getUserVerification,
188
- checkIfUserIsPopularInvestor
189
- };
190
-
@@ -1,66 +0,0 @@
1
- /**
2
- * @fileoverview User Recommendations Helpers
3
- * Handles personalized recommendations endpoints
4
- */
5
-
6
- const { readWithMigration } = require('../core/path_resolution_helpers');
7
-
8
- /**
9
- * GET /user/me/hedges (and /similar)
10
- * Returns personalized recommendations calculated in Phase 3
11
- */
12
- async function getUserRecommendations(req, res, dependencies, config, type = 'hedges') {
13
- const { db, logger } = dependencies;
14
- const { userCid } = req.query;
15
-
16
- if (!userCid) {
17
- return res.status(400).json({ error: "Missing userCid" });
18
- }
19
-
20
- try {
21
- // Try new path with migration
22
- const result = await readWithMigration(
23
- db,
24
- 'signedInUsers',
25
- 'recommendations',
26
- { cid: userCid },
27
- {
28
- isCollection: false,
29
- dataType: 'recommendations',
30
- config,
31
- logger,
32
- documentId: type,
33
- collectionRegistry: dependencies.collectionRegistry
34
- }
35
- );
36
-
37
- if (result && result.data) {
38
- const recs = result.data[type] || result.data || [];
39
- return res.status(200).json({
40
- [type]: recs,
41
- migrated: result.source === 'legacy'
42
- });
43
- }
44
-
45
- // Fallback: Recommendations may be stored in user doc
46
- const userDoc = await db.collection(config.signedInUsersCollection || 'signedInUsers').doc(String(userCid)).get();
47
-
48
- if (!userDoc.exists) {
49
- return res.status(404).json({ error: "User not found" });
50
- }
51
-
52
- const data = userDoc.data();
53
- const recs = data.recommendations ? data.recommendations[type] : [];
54
-
55
- return res.status(200).json({ [type]: recs || [] });
56
-
57
- } catch (error) {
58
- logger.log('ERROR', `[getUserRecommendations] Error fetching recommendations for ${userCid}`, error);
59
- return res.status(500).json({ error: error.message });
60
- }
61
- }
62
-
63
- module.exports = {
64
- getUserRecommendations
65
- };
66
-