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.
- package/functions/alert-system/helpers/alert_helpers.js +6 -6
- package/functions/alert-system/index.js +1 -1
- package/functions/api-v2/helpers/data-fetchers/firestore.js +2218 -0
- package/functions/api-v2/helpers/task_engine_helper.js +51 -0
- package/functions/api-v2/index.js +36 -0
- package/functions/api-v2/middleware/identity_middleware.js +48 -0
- package/functions/api-v2/package.json +6 -0
- package/functions/api-v2/routes/alerts.js +168 -0
- package/functions/api-v2/routes/index.js +35 -0
- package/functions/api-v2/routes/notifications.js +38 -0
- package/functions/api-v2/routes/popular_investors.js +204 -0
- package/functions/api-v2/routes/profile.js +212 -0
- package/functions/api-v2/routes/reviews.js +72 -0
- package/functions/api-v2/routes/settings.js +71 -0
- package/functions/api-v2/routes/sync.js +132 -0
- package/functions/api-v2/routes/verification.js +47 -0
- package/functions/api-v2/routes/watchlists.js +148 -0
- package/functions/computation-system/helpers/computation_worker.js +1 -1
- package/functions/task-engine/helpers/popular_investor_helpers.js +2 -2
- package/index.js +6 -2
- package/package.json +3 -2
- package/functions/generic-api/user-api/ADDING_LEGACY_ROUTES_GUIDE.md +0 -345
- package/functions/generic-api/user-api/CODE_REORGANIZATION_PLAN.md +0 -320
- package/functions/generic-api/user-api/COMPLETE_REFACTORING_PLAN.md +0 -116
- package/functions/generic-api/user-api/FIRESTORE_PATHS_INVENTORY.md +0 -171
- package/functions/generic-api/user-api/FIRESTORE_PATH_MIGRATION_REFERENCE.md +0 -710
- package/functions/generic-api/user-api/FIRESTORE_PATH_VALIDATION.md +0 -109
- package/functions/generic-api/user-api/MIGRATION_PLAN.md +0 -499
- package/functions/generic-api/user-api/README_MIGRATION.md +0 -152
- package/functions/generic-api/user-api/REFACTORING_COMPLETE.md +0 -106
- package/functions/generic-api/user-api/REFACTORING_STATUS.md +0 -85
- package/functions/generic-api/user-api/VERIFICATION_MIGRATION_NOTES.md +0 -206
- package/functions/generic-api/user-api/helpers/ORGANIZATION_COMPLETE.md +0 -126
- /package/functions/{generic-api → old-generic-api}/admin-api/index.js +0 -0
- /package/functions/{generic-api → old-generic-api}/helpers/api_helpers.js +0 -0
- /package/functions/{generic-api → old-generic-api}/index.js +0 -0
- /package/functions/{generic-api → old-generic-api}/user-api/helpers/alerts/alert_helpers.js +0 -0
- /package/functions/{generic-api → old-generic-api}/user-api/helpers/alerts/subscription_helpers.js +0 -0
- /package/functions/{generic-api → old-generic-api}/user-api/helpers/alerts/test_alert_helpers.js +0 -0
- /package/functions/{generic-api → old-generic-api}/user-api/helpers/collection_helpers.js +0 -0
- /package/functions/{generic-api → old-generic-api}/user-api/helpers/core/compression_helpers.js +0 -0
- /package/functions/{generic-api → old-generic-api}/user-api/helpers/core/data_lookup_helpers.js +0 -0
- /package/functions/{generic-api → old-generic-api}/user-api/helpers/core/path_resolution_helpers.js +0 -0
- /package/functions/{generic-api → old-generic-api}/user-api/helpers/core/user_status_helpers.js +0 -0
- /package/functions/{generic-api → old-generic-api}/user-api/helpers/data/computation_helpers.js +0 -0
- /package/functions/{generic-api → old-generic-api}/user-api/helpers/data/instrument_helpers.js +0 -0
- /package/functions/{generic-api → old-generic-api}/user-api/helpers/data/portfolio_helpers.js +0 -0
- /package/functions/{generic-api → old-generic-api}/user-api/helpers/data/social_helpers.js +0 -0
- /package/functions/{generic-api → old-generic-api}/user-api/helpers/data_helpers.js +0 -0
- /package/functions/{generic-api → old-generic-api}/user-api/helpers/dev/dev_helpers.js +0 -0
- /package/functions/{generic-api → old-generic-api}/user-api/helpers/fetch/on_demand_fetch_helpers.js +0 -0
- /package/functions/{generic-api → old-generic-api}/user-api/helpers/metrics/personalized_metrics_helpers.js +0 -0
- /package/functions/{generic-api → old-generic-api}/user-api/helpers/notifications/notification_helpers.js +0 -0
- /package/functions/{generic-api → old-generic-api}/user-api/helpers/profile/pi_profile_helpers.js +0 -0
- /package/functions/{generic-api → old-generic-api}/user-api/helpers/profile/profile_view_helpers.js +0 -0
- /package/functions/{generic-api → old-generic-api}/user-api/helpers/profile/user_profile_helpers.js +0 -0
- /package/functions/{generic-api → old-generic-api}/user-api/helpers/recommendations/recommendation_helpers.js +0 -0
- /package/functions/{generic-api → old-generic-api}/user-api/helpers/reviews/review_helpers.js +0 -0
- /package/functions/{generic-api → old-generic-api}/user-api/helpers/rootdata/rootdata_aggregation_helpers.js +0 -0
- /package/functions/{generic-api → old-generic-api}/user-api/helpers/search/pi_request_helpers.js +0 -0
- /package/functions/{generic-api → old-generic-api}/user-api/helpers/search/pi_search_helpers.js +0 -0
- /package/functions/{generic-api → old-generic-api}/user-api/helpers/sync/user_sync_helpers.js +0 -0
- /package/functions/{generic-api → old-generic-api}/user-api/helpers/verification/verification_helpers.js +0 -0
- /package/functions/{generic-api → old-generic-api}/user-api/helpers/watchlist/watchlist_analytics_helpers.js +0 -0
- /package/functions/{generic-api → old-generic-api}/user-api/helpers/watchlist/watchlist_data_helpers.js +0 -0
- /package/functions/{generic-api → old-generic-api}/user-api/helpers/watchlist/watchlist_generation_helpers.js +0 -0
- /package/functions/{generic-api → old-generic-api}/user-api/helpers/watchlist/watchlist_management_helpers.js +0 -0
- /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 {
|