bulltrackers-module 1.0.591 → 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.
Files changed (68) hide show
  1. package/functions/alert-system/helpers/alert_helpers.js +6 -6
  2. package/functions/alert-system/index.js +1 -1
  3. package/functions/api-v2/helpers/data-fetchers/firestore.js +2218 -0
  4. package/functions/api-v2/helpers/task_engine_helper.js +51 -0
  5. package/functions/api-v2/index.js +36 -0
  6. package/functions/api-v2/middleware/identity_middleware.js +48 -0
  7. package/functions/api-v2/package.json +6 -0
  8. package/functions/api-v2/routes/alerts.js +168 -0
  9. package/functions/api-v2/routes/index.js +35 -0
  10. package/functions/api-v2/routes/notifications.js +38 -0
  11. package/functions/api-v2/routes/popular_investors.js +204 -0
  12. package/functions/api-v2/routes/profile.js +212 -0
  13. package/functions/api-v2/routes/reviews.js +72 -0
  14. package/functions/api-v2/routes/settings.js +71 -0
  15. package/functions/api-v2/routes/sync.js +132 -0
  16. package/functions/api-v2/routes/verification.js +47 -0
  17. package/functions/api-v2/routes/watchlists.js +148 -0
  18. package/functions/computation-system/helpers/computation_worker.js +1 -1
  19. package/functions/task-engine/helpers/popular_investor_helpers.js +2 -2
  20. package/index.js +6 -2
  21. package/package.json +3 -2
  22. package/functions/generic-api/user-api/ADDING_LEGACY_ROUTES_GUIDE.md +0 -345
  23. package/functions/generic-api/user-api/CODE_REORGANIZATION_PLAN.md +0 -320
  24. package/functions/generic-api/user-api/COMPLETE_REFACTORING_PLAN.md +0 -116
  25. package/functions/generic-api/user-api/FIRESTORE_PATHS_INVENTORY.md +0 -171
  26. package/functions/generic-api/user-api/FIRESTORE_PATH_MIGRATION_REFERENCE.md +0 -710
  27. package/functions/generic-api/user-api/FIRESTORE_PATH_VALIDATION.md +0 -109
  28. package/functions/generic-api/user-api/MIGRATION_PLAN.md +0 -499
  29. package/functions/generic-api/user-api/README_MIGRATION.md +0 -152
  30. package/functions/generic-api/user-api/REFACTORING_COMPLETE.md +0 -106
  31. package/functions/generic-api/user-api/REFACTORING_STATUS.md +0 -85
  32. package/functions/generic-api/user-api/VERIFICATION_MIGRATION_NOTES.md +0 -206
  33. package/functions/generic-api/user-api/helpers/ORGANIZATION_COMPLETE.md +0 -126
  34. /package/functions/{generic-api → old-generic-api}/admin-api/index.js +0 -0
  35. /package/functions/{generic-api → old-generic-api}/helpers/api_helpers.js +0 -0
  36. /package/functions/{generic-api → old-generic-api}/index.js +0 -0
  37. /package/functions/{generic-api → old-generic-api}/user-api/helpers/alerts/alert_helpers.js +0 -0
  38. /package/functions/{generic-api → old-generic-api}/user-api/helpers/alerts/subscription_helpers.js +0 -0
  39. /package/functions/{generic-api → old-generic-api}/user-api/helpers/alerts/test_alert_helpers.js +0 -0
  40. /package/functions/{generic-api → old-generic-api}/user-api/helpers/collection_helpers.js +0 -0
  41. /package/functions/{generic-api → old-generic-api}/user-api/helpers/core/compression_helpers.js +0 -0
  42. /package/functions/{generic-api → old-generic-api}/user-api/helpers/core/data_lookup_helpers.js +0 -0
  43. /package/functions/{generic-api → old-generic-api}/user-api/helpers/core/path_resolution_helpers.js +0 -0
  44. /package/functions/{generic-api → old-generic-api}/user-api/helpers/core/user_status_helpers.js +0 -0
  45. /package/functions/{generic-api → old-generic-api}/user-api/helpers/data/computation_helpers.js +0 -0
  46. /package/functions/{generic-api → old-generic-api}/user-api/helpers/data/instrument_helpers.js +0 -0
  47. /package/functions/{generic-api → old-generic-api}/user-api/helpers/data/portfolio_helpers.js +0 -0
  48. /package/functions/{generic-api → old-generic-api}/user-api/helpers/data/social_helpers.js +0 -0
  49. /package/functions/{generic-api → old-generic-api}/user-api/helpers/data_helpers.js +0 -0
  50. /package/functions/{generic-api → old-generic-api}/user-api/helpers/dev/dev_helpers.js +0 -0
  51. /package/functions/{generic-api → old-generic-api}/user-api/helpers/fetch/on_demand_fetch_helpers.js +0 -0
  52. /package/functions/{generic-api → old-generic-api}/user-api/helpers/metrics/personalized_metrics_helpers.js +0 -0
  53. /package/functions/{generic-api → old-generic-api}/user-api/helpers/notifications/notification_helpers.js +0 -0
  54. /package/functions/{generic-api → old-generic-api}/user-api/helpers/profile/pi_profile_helpers.js +0 -0
  55. /package/functions/{generic-api → old-generic-api}/user-api/helpers/profile/profile_view_helpers.js +0 -0
  56. /package/functions/{generic-api → old-generic-api}/user-api/helpers/profile/user_profile_helpers.js +0 -0
  57. /package/functions/{generic-api → old-generic-api}/user-api/helpers/recommendations/recommendation_helpers.js +0 -0
  58. /package/functions/{generic-api → old-generic-api}/user-api/helpers/reviews/review_helpers.js +0 -0
  59. /package/functions/{generic-api → old-generic-api}/user-api/helpers/rootdata/rootdata_aggregation_helpers.js +0 -0
  60. /package/functions/{generic-api → old-generic-api}/user-api/helpers/search/pi_request_helpers.js +0 -0
  61. /package/functions/{generic-api → old-generic-api}/user-api/helpers/search/pi_search_helpers.js +0 -0
  62. /package/functions/{generic-api → old-generic-api}/user-api/helpers/sync/user_sync_helpers.js +0 -0
  63. /package/functions/{generic-api → old-generic-api}/user-api/helpers/verification/verification_helpers.js +0 -0
  64. /package/functions/{generic-api → old-generic-api}/user-api/helpers/watchlist/watchlist_analytics_helpers.js +0 -0
  65. /package/functions/{generic-api → old-generic-api}/user-api/helpers/watchlist/watchlist_data_helpers.js +0 -0
  66. /package/functions/{generic-api → old-generic-api}/user-api/helpers/watchlist/watchlist_generation_helpers.js +0 -0
  67. /package/functions/{generic-api → old-generic-api}/user-api/helpers/watchlist/watchlist_management_helpers.js +0 -0
  68. /package/functions/{generic-api → old-generic-api}/user-api/index.js +0 -0
@@ -0,0 +1,212 @@
1
+ const express = require('express');
2
+ const {
3
+ pageCollection,
4
+ latestUserCentricSnapshot,
5
+ fetchPopularInvestorMasterList,
6
+ fetchUserRecommendations,
7
+ checkDataStatus,
8
+ getComputationResults,
9
+ isSignedInUser
10
+ } = require('../helpers/data-fetchers/firestore.js');
11
+
12
+ const router = express.Router();
13
+
14
+ /**
15
+ * PRIVATE PROFILE PAGE - Signed-in User's Own Profile
16
+ *
17
+ * This route returns the signed-in user's own profile data.
18
+ * - For regular users: Uses SignedInUserProfileMetrics computation
19
+ * - For Popular Investors: Uses SignedInUserPIPersonalizedMetrics computation
20
+ *
21
+ * Access: Private (requires authentication - only the signed-in user can access their own profile)
22
+ */
23
+
24
+ // GET /profile/me - Private profile page (signed-in user's own data)
25
+ router.get('/me', async (req, res) => {
26
+ try {
27
+ const { db } = req.dependencies;
28
+ const { date, lookback = 7 } = req.query;
29
+ const userId = req.targetUserId;
30
+
31
+ // Default to today if no date provided
32
+ const targetDate = date || new Date().toISOString().split('T')[0];
33
+
34
+ let computationName = 'SignedInUserProfileMetrics';
35
+
36
+ // Check if the signed-in user is a Popular Investor
37
+ try {
38
+ await fetchPopularInvestorMasterList(db, userId);
39
+ // User is a PI, use the specialized PI metrics
40
+ computationName = 'SignedInUserPIPersonalizedMetrics';
41
+ } catch (e) {
42
+ // User is not a PI, keep default computation
43
+ }
44
+
45
+ const metrics = await pageCollection(db, targetDate, computationName, userId, parseInt(lookback));
46
+ res.json({
47
+ success: true,
48
+ computation: computationName,
49
+ data: metrics,
50
+ profileType: 'private' // Indicates this is the user's own private profile
51
+ });
52
+ } catch (error) {
53
+ res.status(404).json({ error: error.message });
54
+ }
55
+ });
56
+
57
+ // GET /profile/metrics - Legacy route (deprecated, use /profile/me instead)
58
+ // Kept for backward compatibility
59
+ router.get('/metrics', async (req, res) => {
60
+ try {
61
+ const { db } = req.dependencies;
62
+ const { date, lookback = 7 } = req.query;
63
+ const userId = req.targetUserId;
64
+
65
+ // Default to today if no date provided
66
+ const targetDate = date || new Date().toISOString().split('T')[0];
67
+
68
+ let computationName = 'SignedInUserProfileMetrics';
69
+
70
+ try {
71
+ await fetchPopularInvestorMasterList(db, userId);
72
+ computationName = 'SignedInUserPIPersonalizedMetrics';
73
+ } catch (e) { /* Not PI */ }
74
+
75
+ const metrics = await pageCollection(db, targetDate, computationName, userId, parseInt(lookback));
76
+ res.json({ success: true, computation: computationName, data: metrics });
77
+ } catch (error) {
78
+ res.status(404).json({ error: error.message });
79
+ }
80
+ });
81
+
82
+ // GET /profile/data-status (Rec 2)
83
+ router.get('/data-status', async (req, res) => {
84
+ try {
85
+ const status = await checkDataStatus(req.dependencies.db, req.targetUserId);
86
+ res.json({ success: true, data: status });
87
+ } catch (error) {
88
+ res.status(500).json({ error: error.message });
89
+ }
90
+ });
91
+
92
+ // GET /profile/posts (Rec 3)
93
+ router.get('/posts', async (req, res) => {
94
+ try {
95
+ // Fetch latest posts snapshot
96
+ const posts = await latestUserCentricSnapshot(req.dependencies.db, req.targetUserId, 'posts', 'post', 'SignedInUsers', 'latest');
97
+ res.json({ success: true, data: posts });
98
+ } catch (error) {
99
+ res.status(404).json({ error: error.message });
100
+ }
101
+ });
102
+
103
+ // GET /profile/is-popular-investor (Rec 5)
104
+ router.get('/is-popular-investor', async (req, res) => {
105
+ try {
106
+ await fetchPopularInvestorMasterList(req.dependencies.db, req.targetUserId);
107
+ res.json({ isPi: true });
108
+ } catch (e) {
109
+ res.json({ isPi: false });
110
+ }
111
+ });
112
+
113
+ // GET /profile/recommendations (Updated for Rec 16)
114
+ router.get('/recommendations', async (req, res) => {
115
+ try {
116
+ const { db } = req.dependencies;
117
+ const { type } = req.query; // 'similar', 'hedges'
118
+
119
+ if (type === 'hedges' || type === 'similar') {
120
+ const today = new Date().toISOString().split('T')[0];
121
+ const compName = type === 'hedges' ? 'RecommendedHedges' : 'SimilarInvestors';
122
+ const data = await getComputationResults(db, compName, today, req.targetUserId);
123
+ return res.json({ success: true, data });
124
+ }
125
+
126
+ const data = await fetchUserRecommendations(db, req.targetUserId);
127
+ res.json({ success: true, data });
128
+ } catch (error) {
129
+ res.status(500).json({ error: error.message });
130
+ }
131
+ });
132
+
133
+ // GET /profile/portfolio (Existing)
134
+ router.get('/portfolio', async (req, res) => {
135
+ try {
136
+ const { db } = req.dependencies;
137
+ const data = await latestUserCentricSnapshot(db, req.targetUserId, 'portfolio', 'portfolio', 'SignedInUsers', 'latest');
138
+ res.json({ success: true, data });
139
+ } catch (error) {
140
+ res.status(404).json({ error: error.message });
141
+ }
142
+ });
143
+
144
+ // GET /profile/is-signed-in-user/:cid - Check if user exists in SignedInUsers
145
+ router.get('/is-signed-in-user/:cid', async (req, res) => {
146
+ try {
147
+ const { db } = req.dependencies;
148
+ const { cid } = req.params;
149
+ const isSignedIn = await isSignedInUser(db, cid);
150
+ res.json({ success: true, isSignedInUser: isSignedIn });
151
+ } catch (error) {
152
+ res.status(500).json({ error: error.message });
153
+ }
154
+ });
155
+
156
+ // GET /profile/computations - Get computation results for the signed-in user
157
+ router.get('/computations', async (req, res) => {
158
+ try {
159
+ const { db } = req.dependencies;
160
+ const { computation, date, mode = 'latest' } = req.query;
161
+ const userId = req.targetUserId;
162
+
163
+ if (!computation) {
164
+ return res.status(400).json({ error: "Missing required parameter: computation" });
165
+ }
166
+
167
+ // Parse computation names (comma-separated)
168
+ const computationNames = computation.split(',').map(c => c.trim()).filter(c => c);
169
+ if (computationNames.length === 0) {
170
+ return res.status(400).json({ error: "Invalid computation parameter" });
171
+ }
172
+
173
+ // Use today's date if not provided
174
+ const targetDate = date || new Date().toISOString().split('T')[0];
175
+
176
+ // Fetch results for each computation
177
+ const results = {};
178
+ for (const compName of computationNames) {
179
+ try {
180
+ const data = await getComputationResults(db, compName, targetDate, userId);
181
+ results[compName] = data;
182
+ } catch (error) {
183
+ // If latest mode and today fails, try yesterday
184
+ if (mode === 'latest') {
185
+ try {
186
+ const yesterday = new Date();
187
+ yesterday.setDate(yesterday.getDate() - 1);
188
+ const yesterdayStr = yesterday.toISOString().split('T')[0];
189
+ const data = await getComputationResults(db, compName, yesterdayStr, userId);
190
+ results[compName] = data;
191
+ } catch (e) {
192
+ results[compName] = null;
193
+ }
194
+ } else {
195
+ results[compName] = null;
196
+ }
197
+ }
198
+ }
199
+
200
+ res.json({
201
+ success: true,
202
+ userId,
203
+ date: targetDate,
204
+ mode,
205
+ computations: results
206
+ });
207
+ } catch (error) {
208
+ res.status(500).json({ error: error.message });
209
+ }
210
+ });
211
+
212
+ module.exports = router;
@@ -0,0 +1,72 @@
1
+ const express = require('express');
2
+ const { manageReviews } = require('../helpers/data-fetchers/firestore.js');
3
+
4
+ const router = express.Router();
5
+
6
+ // POST /reviews/submit
7
+ router.post('/submit', async (req, res) => {
8
+ try {
9
+ const { db } = req.dependencies;
10
+ const { piId, rating, comment, username, isAnonymous } = req.body;
11
+
12
+ const result = await manageReviews(db, req.targetUserId, 'submit', {
13
+ piId, rating, comment, username, isAnonymous
14
+ });
15
+
16
+ res.json(result);
17
+ } catch (error) {
18
+ res.status(403).json({ error: error.message });
19
+ }
20
+ });
21
+
22
+ // GET /reviews/me - Get all reviews by the signed-in user
23
+ // Optional query param: ?piId=xxx to filter by specific PI
24
+ router.get('/me', async (req, res) => {
25
+ try {
26
+ const { db } = req.dependencies;
27
+ const { piId } = req.query; // Optional filter by PI ID
28
+
29
+ let snap;
30
+ if (piId) {
31
+ // Fetch specific review for a PI
32
+ const doc = await db.collection('SignedInUsers').doc(req.targetUserId).collection('reviews').doc(String(piId)).get();
33
+ if (!doc.exists) {
34
+ return res.json({ success: true, exists: false, review: null });
35
+ }
36
+ return res.json({ success: true, exists: true, review: doc.data() });
37
+ } else {
38
+ // Fetch all reviews
39
+ snap = await db.collection('SignedInUsers').doc(req.targetUserId).collection('reviews').get();
40
+ const data = snap.docs.map(d => d.data());
41
+ res.json({ success: true, data });
42
+ }
43
+ } catch (e) { res.status(500).json({ error: e.message }); }
44
+ });
45
+
46
+ // GET /reviews/:piId
47
+ router.get('/:piId', async (req, res) => {
48
+ try {
49
+ const { db } = req.dependencies;
50
+ const { piId } = req.params;
51
+ const { fetchAllReviewsForPI } = require('../helpers/data-fetchers/firestore.js');
52
+ const result = await fetchAllReviewsForPI(db, piId);
53
+ res.json({ success: true, ...result });
54
+ } catch (error) {
55
+ res.status(500).json({ error: error.message });
56
+ }
57
+ });
58
+
59
+ // GET /reviews/:piId/eligibility
60
+ router.get('/:piId/eligibility', async (req, res) => {
61
+ try {
62
+ const { db } = req.dependencies;
63
+ const { piId } = req.params;
64
+ const { checkReviewEligibility } = require('../helpers/data-fetchers/firestore.js');
65
+ const result = await checkReviewEligibility(db, req.targetUserId, piId);
66
+ res.json(result);
67
+ } catch (error) {
68
+ res.status(500).json({ error: error.message });
69
+ }
70
+ });
71
+
72
+ module.exports = router;
@@ -0,0 +1,71 @@
1
+ const express = require('express');
2
+ const { manageNotificationPreferences, isDeveloper, sendTestAlert } = require('../helpers/data-fetchers/firestore.js');
3
+
4
+ const router = express.Router();
5
+
6
+ // GET /settings/notifications
7
+ router.get('/notifications', async (req, res) => {
8
+ try {
9
+ const { db } = req.dependencies;
10
+ const data = await manageNotificationPreferences(db, req.targetUserId, 'get');
11
+ res.json({ success: true, data });
12
+ } catch (error) {
13
+ res.status(500).json({ error: error.message });
14
+ }
15
+ });
16
+
17
+ // PUT /settings/notifications
18
+ router.put('/notifications', async (req, res) => {
19
+ try {
20
+ const { db } = req.dependencies;
21
+ const result = await manageNotificationPreferences(db, req.targetUserId, 'update', req.body);
22
+ res.json(result);
23
+ } catch (error) {
24
+ res.status(500).json({ error: error.message });
25
+ }
26
+ });
27
+
28
+ // GET /settings/dev/override (Rec 17)
29
+ router.get('/dev/override', async (req, res) => {
30
+ try {
31
+ const { db } = req.dependencies;
32
+ if (!(await isDeveloper(db, req.targetUserId))) return res.status(403).json({ error: "Unauthorized" });
33
+
34
+ const doc = await db.collection('dev_overrides').doc(req.targetUserId).get();
35
+ res.json({ success: true, data: doc.exists ? doc.data() : { enabled: false } });
36
+ } catch (e) { res.status(500).json({ error: e.message }); }
37
+ });
38
+
39
+ // POST /settings/dev/override
40
+ router.post('/dev/override', async (req, res) => {
41
+ try {
42
+ const { db } = req.dependencies;
43
+ const { enabled, impersonateCid, fakeCopiedPIs } = req.body;
44
+
45
+ if (!(await isDeveloper(db, req.targetUserId))) return res.status(403).json({ error: "Unauthorized" });
46
+
47
+ await db.collection('dev_overrides').doc(req.targetUserId).set({
48
+ enabled,
49
+ impersonateCid,
50
+ fakeCopiedPIs: fakeCopiedPIs || [],
51
+ updatedAt: new Date()
52
+ }, { merge: true });
53
+
54
+ res.json({ success: true, message: "Dev override updated" });
55
+ } catch (error) {
56
+ res.status(500).json({ error: error.message });
57
+ }
58
+ });
59
+
60
+ // POST /settings/dev/test-alert (Rec 17)
61
+ router.post('/dev/test-alert', async (req, res) => {
62
+ try {
63
+ const { db } = req.dependencies;
64
+ if (!(await isDeveloper(db, req.targetUserId))) return res.status(403).json({ error: "Unauthorized" });
65
+
66
+ const result = await sendTestAlert(db, req.targetUserId, req.body);
67
+ res.json(result);
68
+ } catch (e) { res.status(500).json({ error: e.message }); }
69
+ });
70
+
71
+ module.exports = router;
@@ -0,0 +1,132 @@
1
+ const express = require('express');
2
+ const { dispatchSyncRequest } = require('../helpers/task_engine_helper.js');
3
+ const {
4
+ checkSyncRateLimits,
5
+ getSyncStatus,
6
+ isDeveloper,
7
+ isSignedInUser,
8
+ fetchPopularInvestorMasterList,
9
+ getUserUsername
10
+ } = require('../helpers/data-fetchers/firestore.js');
11
+
12
+ const router = express.Router();
13
+
14
+ // POST /sync/request (Unified - Auto-detects user type)
15
+ router.post('/request', async (req, res) => {
16
+ try {
17
+ const { pubsub, db } = req.dependencies;
18
+ // targetId can be passed (for PIs) or default to self
19
+ const targetId = req.body.targetId || req.targetUserId;
20
+
21
+ // 1. Rate Limits
22
+ const isDev = await isDeveloper(db, req.targetUserId);
23
+ const limit = await checkSyncRateLimits(db, targetId, req.targetUserId, isDev);
24
+ if (!limit.allowed) return res.status(429).json({ error: limit.message });
25
+
26
+ // 2. Detect User Types
27
+ const [isSignedIn, isPI] = await Promise.all([
28
+ isSignedInUser(db, targetId),
29
+ fetchPopularInvestorMasterList(db, String(targetId)).then(() => true).catch(() => false)
30
+ ]);
31
+
32
+ if (!isSignedIn && !isPI) {
33
+ return res.status(404).json({ error: "User not found in SignedInUsers or Popular Investor master list" });
34
+ }
35
+
36
+ // 3. Get username
37
+ const username = await getUserUsername(db, targetId) || String(targetId);
38
+
39
+ // 4. Create request IDs and dispatch tasks
40
+ const requestIds = [];
41
+ const tasks = [];
42
+
43
+ if (isSignedIn) {
44
+ const requestId = `sync_${targetId}_user_${Date.now()}`;
45
+ requestIds.push(requestId);
46
+
47
+ // Log request for signed-in user
48
+ await db.collection('user_sync_requests').doc(String(targetId)).collection('requests').doc(requestId).set({
49
+ requestId,
50
+ status: 'queued',
51
+ userType: 'SIGNED_IN_USER',
52
+ requestedBy: req.targetUserId,
53
+ createdAt: new Date()
54
+ });
55
+
56
+ tasks.push(
57
+ dispatchSyncRequest(pubsub, targetId, username, {
58
+ type: 'ON_DEMAND_USER_UPDATE',
59
+ priority: 'high',
60
+ source: 'on_demand_sync',
61
+ requestId,
62
+ requestedBy: req.targetUserId,
63
+ userType: 'SIGNED_IN_USER',
64
+ metadata: {
65
+ requestingUserCid: req.targetUserId
66
+ }
67
+ })
68
+ );
69
+ }
70
+
71
+ if (isPI) {
72
+ const requestId = `sync_${targetId}_pi_${Date.now()}`;
73
+ requestIds.push(requestId);
74
+
75
+ // Log request for PI
76
+ await db.collection('user_sync_requests').doc(String(targetId)).collection('requests').doc(requestId).set({
77
+ requestId,
78
+ status: 'queued',
79
+ userType: 'POPULAR_INVESTOR',
80
+ requestedBy: req.targetUserId,
81
+ createdAt: new Date()
82
+ });
83
+
84
+ tasks.push(
85
+ dispatchSyncRequest(pubsub, targetId, username, {
86
+ type: 'POPULAR_INVESTOR_UPDATE',
87
+ priority: 'high',
88
+ source: 'on_demand_sync',
89
+ requestId,
90
+ requestedBy: req.targetUserId,
91
+ userType: 'POPULAR_INVESTOR',
92
+ metadata: {
93
+ requestingUserCid: req.targetUserId
94
+ }
95
+ })
96
+ );
97
+ }
98
+
99
+ // Update global limit tracker (only once, regardless of how many tasks)
100
+ await db.collection('user_sync_requests').doc(String(targetId)).collection('global').doc('latest').set({
101
+ lastRequestedAt: new Date()
102
+ }, { merge: true });
103
+
104
+ // Dispatch all tasks
105
+ await Promise.all(tasks);
106
+
107
+ res.json({
108
+ success: true,
109
+ requestIds,
110
+ userTypes: {
111
+ isSignedInUser: isSignedIn,
112
+ isPopularInvestor: isPI
113
+ },
114
+ message: `Sync dispatched for ${isSignedIn && isPI ? 'both user types' : isSignedIn ? 'signed-in user' : 'popular investor'}`
115
+ });
116
+ } catch (error) {
117
+ res.status(500).json({ error: error.message });
118
+ }
119
+ });
120
+
121
+ // GET /sync/status/:targetId
122
+ router.get('/status/:targetId', async (req, res) => {
123
+ try {
124
+ const { db } = req.dependencies;
125
+ const status = await getSyncStatus(db, req.params.targetId);
126
+ res.json({ success: true, data: status });
127
+ } catch (error) {
128
+ res.status(500).json({ error: error.message });
129
+ }
130
+ });
131
+
132
+ module.exports = router;
@@ -0,0 +1,47 @@
1
+ const express = require('express');
2
+ const { initiateVerification, finalizeVerification, fetchUserVerificationData } = require('../helpers/data-fetchers/firestore.js');
3
+
4
+ const router = express.Router();
5
+
6
+ // GET /verification/status
7
+ router.get('/status', async (req, res) => {
8
+ try {
9
+ const { db } = req.dependencies;
10
+ const data = await fetchUserVerificationData(db, req.targetUserId);
11
+ res.json({ success: true, data });
12
+ } catch (error) {
13
+ res.status(200).json({ success: false, message: "Not Verified", error: error.message });
14
+ }
15
+ });
16
+
17
+ // POST /verification/init
18
+ // Generates OTP for a specific username
19
+ router.post('/init', async (req, res) => {
20
+ try {
21
+ const { db } = req.dependencies;
22
+ const { username } = req.body; // Logic requires username to key the request
23
+ const result = await initiateVerification(db, username);
24
+ res.json(result);
25
+ } catch (error) {
26
+ res.status(500).json({ error: error.message });
27
+ }
28
+ });
29
+
30
+ // POST /verification/finalize
31
+ // Checks Bio -> Verifies User -> Triggers Sync
32
+ router.post('/finalize', async (req, res) => {
33
+ try {
34
+ const { db, pubsub } = req.dependencies;
35
+ const { username } = req.body;
36
+
37
+ if (!username) return res.status(400).json({ error: "Username required" });
38
+
39
+ // Pass req.targetUserId if available, but the logic primarily uses username/realCID
40
+ const result = await finalizeVerification(db, pubsub, req.targetUserId, username);
41
+ res.json(result);
42
+ } catch (error) {
43
+ res.status(400).json({ success: false, error: error.message });
44
+ }
45
+ });
46
+
47
+ module.exports = router;
@@ -0,0 +1,148 @@
1
+ const express = require('express');
2
+ const {
3
+ manageUserWatchlist,
4
+ latestUserCentricSnapshot,
5
+ fetchPublicWatchlists,
6
+ publishWatchlistVersion,
7
+ copyWatchlist,
8
+ fetchWatchlistVersions,
9
+ autoGenerateWatchlist,
10
+ fetchPopularInvestorMasterList,
11
+ getWatchlistTriggerCounts,
12
+ subscribeToAllWatchlistPIs
13
+ } = require('../helpers/data-fetchers/firestore.js');
14
+
15
+ const router = express.Router();
16
+
17
+ // GET /watchlists/all
18
+ router.get('/all', async (req, res) => {
19
+ try {
20
+ const { db } = req.dependencies;
21
+ const watchlists = await latestUserCentricSnapshot(db, req.targetUserId, 'watchlists', 'watchlist', 'SignedInUsers', null);
22
+ res.json({ success: true, count: watchlists.length, data: watchlists });
23
+ } catch (error) {
24
+ res.status(500).json({ error: error.message });
25
+ }
26
+ });
27
+
28
+ // POST /watchlists/auto-generate (Rec 11)
29
+ router.post('/auto-generate', async (req, res) => {
30
+ try {
31
+ const result = await autoGenerateWatchlist(req.dependencies.db, req.targetUserId);
32
+ res.json(result);
33
+ } catch (error) {
34
+ res.status(500).json({ error: error.message });
35
+ }
36
+ });
37
+
38
+ // GET /watchlists/:id (Rec 12)
39
+ router.get('/:id', async (req, res) => {
40
+ try {
41
+ const { db } = req.dependencies;
42
+ const { id } = req.params;
43
+ const doc = await latestUserCentricSnapshot(db, req.targetUserId, 'watchlists', 'watchlist', 'SignedInUsers', id);
44
+ if (!doc) return res.status(404).json({ error: "Watchlist not found" });
45
+ res.json({ success: true, data: doc });
46
+ } catch (error) {
47
+ res.status(500).json({ error: error.message });
48
+ }
49
+ });
50
+
51
+ // GET /watchlists/:id/rankings-check (Rec 10)
52
+ router.get('/:id/rankings-check', async (req, res) => {
53
+ try {
54
+ const { db } = req.dependencies;
55
+ const { id } = req.params;
56
+
57
+ // Fetch watchlist
58
+ const wl = await latestUserCentricSnapshot(db, req.targetUserId, 'watchlists', 'watchlist', 'SignedInUsers', id);
59
+ if (!wl) return res.status(404).json({ error: "Watchlist not found" });
60
+
61
+ const results = {};
62
+ for (const item of (wl.items || [])) {
63
+ try {
64
+ await fetchPopularInvestorMasterList(db, String(item.cid));
65
+ results[item.cid] = true;
66
+ } catch {
67
+ results[item.cid] = false;
68
+ }
69
+ }
70
+ res.json({ success: true, data: results });
71
+ } catch (error) {
72
+ res.status(500).json({ error: error.message });
73
+ }
74
+ });
75
+
76
+ // POST /watchlists/manage (Unified)
77
+ router.post('/manage', async (req, res) => {
78
+ try {
79
+ const { db } = req.dependencies;
80
+ const { instruction, payload } = req.body;
81
+ const result = await manageUserWatchlist(db, req.targetUserId, instruction, payload);
82
+ res.json(result);
83
+ } catch (error) {
84
+ res.status(500).json({ error: error.message });
85
+ }
86
+ });
87
+
88
+ // Public & Copy Routes (Existing)
89
+ router.get('/public', async (req, res) => {
90
+ try {
91
+ const data = await fetchPublicWatchlists(req.dependencies.db, req.query.limit, req.query.offset);
92
+ res.json({ success: true, data });
93
+ } catch (e) { res.status(500).json({ error: e.message }); }
94
+ });
95
+
96
+ router.post('/:id/publish', async (req, res) => {
97
+ try {
98
+ const result = await publishWatchlistVersion(req.dependencies.db, req.targetUserId, req.params.id);
99
+ res.json(result);
100
+ } catch (e) { res.status(500).json({ error: e.message }); }
101
+ });
102
+
103
+ router.post('/:id/copy', async (req, res) => {
104
+ try {
105
+ const result = await copyWatchlist(req.dependencies.db, req.targetUserId, req.params.id, req.body.version);
106
+ res.json(result);
107
+ } catch (e) { res.status(500).json({ error: e.message }); }
108
+ });
109
+
110
+ router.get('/:id/versions', async (req, res) => {
111
+ try {
112
+ const data = await fetchWatchlistVersions(req.dependencies.db, req.params.id);
113
+ res.json({ success: true, data });
114
+ } catch (e) { res.status(500).json({ error: e.message }); }
115
+ });
116
+
117
+ // GET /watchlists/:id/trigger-counts - Get alert trigger counts for watchlist PIs
118
+ router.get('/:id/trigger-counts', async (req, res) => {
119
+ try {
120
+ const { db } = req.dependencies;
121
+ const { id } = req.params;
122
+ const result = await getWatchlistTriggerCounts(db, req.targetUserId, id);
123
+ res.json({ success: true, ...result });
124
+ } catch (error) {
125
+ if (error.message === "Watchlist not found") {
126
+ return res.status(404).json({ error: error.message });
127
+ }
128
+ res.status(500).json({ error: error.message });
129
+ }
130
+ });
131
+
132
+ // POST /watchlists/:id/subscribe-all - Subscribe to all PIs in a watchlist
133
+ router.post('/:id/subscribe-all', async (req, res) => {
134
+ try {
135
+ const { db } = req.dependencies;
136
+ const { id } = req.params;
137
+ const { alertTypes, thresholds } = req.body;
138
+ const result = await subscribeToAllWatchlistPIs(db, req.targetUserId, id, alertTypes, thresholds);
139
+ res.json(result);
140
+ } catch (error) {
141
+ if (error.message === "Watchlist not found") {
142
+ return res.status(404).json({ error: error.message });
143
+ }
144
+ res.status(500).json({ error: error.message });
145
+ }
146
+ });
147
+
148
+ module.exports = router;
@@ -232,7 +232,7 @@ async function handleComputationTask(message, config, dependencies) {
232
232
  await db.doc(ledgerPath).update({ status: 'COMPLETED', completedAt: new Date() });
233
233
  await recordRunAttempt(db, { date, computation, pass }, 'SUCCESS', null, metrics, triggerReason, resourceTier);
234
234
 
235
- const { notifyComputationComplete, getComputationDisplayName } = require('../../generic-api/user-api/helpers/notifications/notification_helpers');
235
+ const { notifyComputationComplete, getComputationDisplayName } = require('../../old-generic-api/user-api/helpers/notifications/notification_helpers.js');
236
236
  // Send notification if this was an on-demand computation
237
237
  if (metadata?.onDemand && metadata?.requestId && metadata?.requestingUserCid) {
238
238
  try {