bulltrackers-module 1.0.591 → 1.0.592

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 +2 -1
  22. package/functions/generic-api/admin-api/index.js +0 -895
  23. package/functions/generic-api/helpers/api_helpers.js +0 -457
  24. package/functions/generic-api/index.js +0 -204
  25. package/functions/generic-api/user-api/ADDING_LEGACY_ROUTES_GUIDE.md +0 -345
  26. package/functions/generic-api/user-api/CODE_REORGANIZATION_PLAN.md +0 -320
  27. package/functions/generic-api/user-api/COMPLETE_REFACTORING_PLAN.md +0 -116
  28. package/functions/generic-api/user-api/FIRESTORE_PATHS_INVENTORY.md +0 -171
  29. package/functions/generic-api/user-api/FIRESTORE_PATH_MIGRATION_REFERENCE.md +0 -710
  30. package/functions/generic-api/user-api/FIRESTORE_PATH_VALIDATION.md +0 -109
  31. package/functions/generic-api/user-api/MIGRATION_PLAN.md +0 -499
  32. package/functions/generic-api/user-api/README_MIGRATION.md +0 -152
  33. package/functions/generic-api/user-api/REFACTORING_COMPLETE.md +0 -106
  34. package/functions/generic-api/user-api/REFACTORING_STATUS.md +0 -85
  35. package/functions/generic-api/user-api/VERIFICATION_MIGRATION_NOTES.md +0 -206
  36. package/functions/generic-api/user-api/helpers/ORGANIZATION_COMPLETE.md +0 -126
  37. package/functions/generic-api/user-api/helpers/alerts/alert_helpers.js +0 -355
  38. package/functions/generic-api/user-api/helpers/alerts/subscription_helpers.js +0 -327
  39. package/functions/generic-api/user-api/helpers/alerts/test_alert_helpers.js +0 -212
  40. package/functions/generic-api/user-api/helpers/collection_helpers.js +0 -193
  41. package/functions/generic-api/user-api/helpers/core/compression_helpers.js +0 -68
  42. package/functions/generic-api/user-api/helpers/core/data_lookup_helpers.js +0 -256
  43. package/functions/generic-api/user-api/helpers/core/path_resolution_helpers.js +0 -640
  44. package/functions/generic-api/user-api/helpers/core/user_status_helpers.js +0 -195
  45. package/functions/generic-api/user-api/helpers/data/computation_helpers.js +0 -503
  46. package/functions/generic-api/user-api/helpers/data/instrument_helpers.js +0 -55
  47. package/functions/generic-api/user-api/helpers/data/portfolio_helpers.js +0 -245
  48. package/functions/generic-api/user-api/helpers/data/social_helpers.js +0 -174
  49. package/functions/generic-api/user-api/helpers/data_helpers.js +0 -87
  50. package/functions/generic-api/user-api/helpers/dev/dev_helpers.js +0 -336
  51. package/functions/generic-api/user-api/helpers/fetch/on_demand_fetch_helpers.js +0 -615
  52. package/functions/generic-api/user-api/helpers/metrics/personalized_metrics_helpers.js +0 -231
  53. package/functions/generic-api/user-api/helpers/notifications/notification_helpers.js +0 -641
  54. package/functions/generic-api/user-api/helpers/profile/pi_profile_helpers.js +0 -182
  55. package/functions/generic-api/user-api/helpers/profile/profile_view_helpers.js +0 -137
  56. package/functions/generic-api/user-api/helpers/profile/user_profile_helpers.js +0 -190
  57. package/functions/generic-api/user-api/helpers/recommendations/recommendation_helpers.js +0 -66
  58. package/functions/generic-api/user-api/helpers/reviews/review_helpers.js +0 -550
  59. package/functions/generic-api/user-api/helpers/rootdata/rootdata_aggregation_helpers.js +0 -378
  60. package/functions/generic-api/user-api/helpers/search/pi_request_helpers.js +0 -295
  61. package/functions/generic-api/user-api/helpers/search/pi_search_helpers.js +0 -162
  62. package/functions/generic-api/user-api/helpers/sync/user_sync_helpers.js +0 -677
  63. package/functions/generic-api/user-api/helpers/verification/verification_helpers.js +0 -323
  64. package/functions/generic-api/user-api/helpers/watchlist/watchlist_analytics_helpers.js +0 -96
  65. package/functions/generic-api/user-api/helpers/watchlist/watchlist_data_helpers.js +0 -141
  66. package/functions/generic-api/user-api/helpers/watchlist/watchlist_generation_helpers.js +0 -310
  67. package/functions/generic-api/user-api/helpers/watchlist/watchlist_management_helpers.js +0 -829
  68. package/functions/generic-api/user-api/index.js +0 -109
@@ -1,212 +0,0 @@
1
- /**
2
- * @fileoverview Test Alert Helpers
3
- * Allows developers to send test alerts for testing the alert system
4
- */
5
-
6
- const { FieldValue } = require('@google-cloud/firestore');
7
- const { getAllAlertTypes, getAlertTypeByComputation } = require('../../../../alert-system/helpers/alert_type_registry');
8
- const { isDeveloperAccount, getDevOverride } = require('../dev/dev_helpers');
9
- const { getCidFromFirebaseUid } = require('../core/path_resolution_helpers');
10
- const { writeWithMigration } = require('../core/path_resolution_helpers');
11
-
12
- /**
13
- * POST /user/dev/test-alert
14
- * Send a test alert to users
15
- *
16
- * Request body:
17
- * {
18
- * userCid: number (required) - Developer account CID
19
- * alertTypeId: string (optional) - Alert type ID, defaults to first available
20
- * targetUsers: 'all' | 'dev' | number[] (optional) - Who to send to, defaults to 'dev'
21
- * piCid: number (optional) - PI CID for the alert, defaults to 1
22
- * piUsername: string (optional) - PI username, defaults to 'TestPI'
23
- * metadata: object (optional) - Additional metadata for the alert
24
- * }
25
- */
26
- async function sendTestAlert(req, res, dependencies, config) {
27
- const { db, logger, collectionRegistry } = dependencies;
28
- const { userCid, alertTypeId, targetUsers = 'dev', piCid = 1, piUsername = 'TestPI', metadata = {} } = req.body;
29
-
30
- if (!userCid) {
31
- return res.status(400).json({ error: "Missing userCid" });
32
- }
33
-
34
- // SECURITY CHECK: Only allow developer accounts
35
- if (!isDeveloperAccount(userCid)) {
36
- logger.log('WARN', `[sendTestAlert] Unauthorized attempt by user ${userCid}`);
37
- return res.status(403).json({
38
- error: "Forbidden",
39
- message: "Test alerts are only available for authorized developer accounts"
40
- });
41
- }
42
-
43
- try {
44
- // Get alert type
45
- let alertType;
46
- if (alertTypeId) {
47
- alertType = getAlertTypeByComputation(alertTypeId);
48
- if (!alertType) {
49
- const allTypes = getAllAlertTypes();
50
- alertType = allTypes.find(t => t.id === alertTypeId);
51
- }
52
- }
53
-
54
- // Default to first available alert type if not specified
55
- if (!alertType) {
56
- const allTypes = getAllAlertTypes();
57
- if (allTypes.length === 0) {
58
- return res.status(400).json({ error: "No alert types available" });
59
- }
60
- alertType = allTypes[0];
61
- logger.log('INFO', `[sendTestAlert] Using default alert type: ${alertType.id}`);
62
- }
63
-
64
- // Determine target users (as eToro CIDs)
65
- let targetCids = [];
66
-
67
- if (targetUsers === 'all') {
68
- // Get all users from signedInUsers collection (who have etoroCID)
69
- const signedInUsersSnapshot = await db.collection('signedInUsers')
70
- .where('etoroCID', '!=', null)
71
- .get();
72
-
73
- targetCids = signedInUsersSnapshot.docs
74
- .map(doc => doc.data().etoroCID)
75
- .filter(cid => cid != null);
76
- logger.log('INFO', `[sendTestAlert] Sending to all ${targetCids.length} users`);
77
- } else if (targetUsers === 'dev') {
78
- // Get all developer accounts with dev override enabled
79
- const devOverridesCollection = config.devOverridesCollection || 'dev_overrides';
80
- const devOverridesSnapshot = await db.collection(devOverridesCollection).get();
81
-
82
- const devCids = [];
83
- for (const doc of devOverridesSnapshot.docs) {
84
- const data = doc.data();
85
- if (data.enabled === true) {
86
- devCids.push(Number(doc.id));
87
- }
88
- }
89
-
90
- // Also include the requesting developer
91
- if (!devCids.includes(Number(userCid))) {
92
- devCids.push(Number(userCid));
93
- }
94
-
95
- targetCids = devCids;
96
- logger.log('INFO', `[sendTestAlert] Sending to ${targetCids.length} developer accounts`);
97
- } else if (Array.isArray(targetUsers)) {
98
- // Specific user CIDs
99
- targetCids = targetUsers.map(cid => Number(cid)).filter(cid => !isNaN(cid) && cid > 0);
100
- logger.log('INFO', `[sendTestAlert] Sending to ${targetCids.length} specific users`);
101
- } else {
102
- return res.status(400).json({
103
- error: "Invalid targetUsers",
104
- message: "targetUsers must be 'all', 'dev', or an array of user CIDs"
105
- });
106
- }
107
-
108
- if (targetCids.length === 0) {
109
- return res.status(400).json({
110
- error: "No target users",
111
- message: "No users found matching the target criteria"
112
- });
113
- }
114
-
115
- // Generate alert message
116
- const { generateAlertMessage } = require('../../../../alert-system/helpers/alert_type_registry');
117
- const alertMessage = generateAlertMessage(alertType, piUsername, {
118
- ...metadata,
119
- isTest: true,
120
- testSentBy: Number(userCid),
121
- testSentAt: new Date().toISOString()
122
- });
123
-
124
- // Create notifications for each target user (using eToro CIDs and collection registry)
125
- const notificationPromises = [];
126
- const today = new Date().toISOString().split('T')[0];
127
-
128
- for (const targetCid of targetCids) {
129
- const notificationId = `test_alert_${Date.now()}_${targetCid}_${piCid}_${Math.random().toString(36).substring(2, 9)}`;
130
-
131
- const notificationData = {
132
- id: notificationId,
133
- type: 'testAlerts', // Use testAlerts type for test notifications
134
- subType: 'alert',
135
- title: `[TEST] ${alertType.name}`,
136
- message: alertMessage,
137
- read: false,
138
- timestamp: FieldValue.serverTimestamp(),
139
- createdAt: FieldValue.serverTimestamp(),
140
- metadata: {
141
- piCid: Number(piCid),
142
- piUsername: piUsername,
143
- alertType: alertType.id,
144
- alertTypeName: alertType.name,
145
- computationName: alertType.computationName,
146
- computationDate: today,
147
- severity: alertType.severity,
148
- isTest: true,
149
- testSentBy: Number(userCid),
150
- testSentAt: new Date().toISOString(),
151
- notificationType: 'testAlerts',
152
- userCid: Number(targetCid),
153
- ...metadata
154
- }
155
- };
156
-
157
- // Use writeWithMigration to write to new path (with legacy fallback)
158
- // notifications is a subcollection, so we need isCollection: true and documentId
159
- const writePromise = writeWithMigration(
160
- db,
161
- 'signedInUsers',
162
- 'notifications',
163
- { cid: String(targetCid) },
164
- notificationData,
165
- {
166
- isCollection: true,
167
- merge: false,
168
- dataType: 'notifications',
169
- documentId: notificationId,
170
- dualWrite: false, // Disable dual write - we're fully migrated to new path
171
- config,
172
- collectionRegistry
173
- }
174
- ).catch(err => {
175
- logger.log('ERROR', `[sendTestAlert] Failed to write notification for CID ${targetCid}: ${err.message}`, err);
176
- throw err; // Re-throw so we know if writes are failing
177
- });
178
-
179
- notificationPromises.push(writePromise);
180
- }
181
-
182
- // Wait for all notifications to be written
183
- await Promise.all(notificationPromises);
184
-
185
- const successCount = notificationPromises.length;
186
- logger.log('SUCCESS', `[sendTestAlert] Created ${successCount} test notifications for alert type ${alertType.id}`);
187
-
188
- return res.status(200).json({
189
- success: true,
190
- message: `Test alert sent to ${targetCids.length} users`,
191
- alertType: {
192
- id: alertType.id,
193
- name: alertType.name,
194
- computationName: alertType.computationName
195
- },
196
- targetUsers: {
197
- count: targetCids.length,
198
- cids: targetCids
199
- },
200
- piCid: Number(piCid),
201
- piUsername: piUsername,
202
- notificationsCreated: successCount
203
- });
204
-
205
- } catch (error) {
206
- logger.log('ERROR', `[sendTestAlert] Error sending test alert:`, error);
207
- return res.status(500).json({ error: error.message });
208
- }
209
- }
210
-
211
- module.exports = { sendTestAlert };
212
-
@@ -1,193 +0,0 @@
1
- /**
2
- * @fileoverview Collection Path Helpers (Backward Compatibility)
3
- * Re-exports from core/path_resolution_helpers.js for backward compatibility
4
- * This file maintains the old API while using the new migration-enabled helpers
5
- */
6
-
7
- // Re-export from core helpers for backward compatibility
8
- const coreHelpers = require('./core/path_resolution_helpers');
9
-
10
- /**
11
- * Extract collection name from a full path
12
- * @param {string} path - Full Firestore path (e.g., 'signedInUsers/123/portfolio/latest')
13
- * @returns {string} - Collection name (e.g., 'signedInUsers')
14
- */
15
- function extractCollectionName(path) {
16
- return path.split('/')[0];
17
- }
18
-
19
- /**
20
- * Dual read helper - tries new structure first, falls back to legacy
21
- * @deprecated Use readWithMigration from core/path_resolution_helpers instead
22
- * @param {object} db - Firestore instance
23
- * @param {string} newPath - New collection path
24
- * @param {string} legacyPath - Legacy collection path
25
- * @param {object} [options={}] - Options for reading
26
- * @param {boolean} [options.isCollection=false] - If true, reads as collection; if false, reads as document
27
- * @returns {Promise<object|null>} - Document data or null if not found
28
- */
29
- async function readWithFallback(db, newPath, legacyPath, options = {}) {
30
- const { isCollection = false } = options;
31
-
32
- try {
33
- // Try new structure first
34
- if (isCollection) {
35
- const newSnapshot = await db.collection(newPath).get();
36
- if (!newSnapshot.empty) {
37
- return { snapshot: newSnapshot, source: 'new' };
38
- }
39
- } else {
40
- const newDoc = await db.doc(newPath).get();
41
- if (newDoc.exists) {
42
- return { data: newDoc.data(), source: 'new' };
43
- }
44
- }
45
- } catch (newError) {
46
- console.warn(`[readWithFallback] New path failed: ${newPath}`, newError.message);
47
- }
48
-
49
- // Fallback to legacy
50
- try {
51
- if (isCollection) {
52
- const legacySnapshot = await db.collection(legacyPath).get();
53
- if (!legacySnapshot.empty) {
54
- return { snapshot: legacySnapshot, source: 'legacy' };
55
- }
56
- } else {
57
- const legacyDoc = await db.doc(legacyPath).get();
58
- if (legacyDoc.exists) {
59
- return { data: legacyDoc.data(), source: 'legacy' };
60
- }
61
- }
62
- } catch (legacyError) {
63
- console.warn(`[readWithFallback] Legacy path failed: ${legacyPath}`, legacyError.message);
64
- }
65
-
66
- return null;
67
- }
68
-
69
- /**
70
- * Dual write helper - writes to both new and legacy locations during migration
71
- * @deprecated Use writeWithMigration from core/path_resolution_helpers instead
72
- * @param {object} db - Firestore instance
73
- * @param {string} newPath - New collection path
74
- * @param {string} legacyPath - Legacy collection path
75
- * @param {object} data - Data to write
76
- * @param {object} [options={}] - Write options
77
- * @param {boolean} [options.isCollection=false] - If true, writes as collection; if false, writes as document
78
- * @param {boolean} [options.merge=false] - If true, merges data instead of overwriting
79
- * @returns {Promise<void>}
80
- */
81
- async function writeDual(db, newPath, legacyPath, data, options = {}) {
82
- const { isCollection = false, merge = false } = options;
83
- const batch = db.batch();
84
-
85
- try {
86
- if (isCollection) {
87
- // For collections, we need the document ID
88
- if (!data.id) {
89
- throw new Error('Collection writes require document ID in data.id');
90
- }
91
- const newRef = db.collection(newPath).doc(data.id);
92
- const legacyRef = db.collection(legacyPath).doc(data.id);
93
-
94
- const { id, ...dataWithoutId } = data;
95
- if (merge) {
96
- batch.set(newRef, dataWithoutId, { merge: true });
97
- batch.set(legacyRef, dataWithoutId, { merge: true });
98
- } else {
99
- batch.set(newRef, dataWithoutId);
100
- batch.set(legacyRef, dataWithoutId);
101
- }
102
- } else {
103
- // For documents
104
- const newRef = db.doc(newPath);
105
- const legacyRef = db.doc(legacyPath);
106
-
107
- if (merge) {
108
- batch.set(newRef, data, { merge: true });
109
- batch.set(legacyRef, data, { merge: true });
110
- } else {
111
- batch.set(newRef, data);
112
- batch.set(legacyRef, data);
113
- }
114
- }
115
-
116
- await batch.commit();
117
- } catch (error) {
118
- console.error('[writeDual] Error writing to both locations:', error);
119
- throw error;
120
- }
121
- }
122
-
123
- /**
124
- * Get user-centric portfolio path (latest snapshot)
125
- * @param {object} collectionRegistry - Collection registry
126
- * @param {string|number} cid - User CID
127
- * @returns {string} - Path to latest portfolio snapshot
128
- */
129
- function getUserPortfolioPath(collectionRegistry, cid) {
130
- return coreHelpers.getCollectionPath(collectionRegistry, 'signedInUsers', 'portfolio', { cid: String(cid) });
131
- }
132
-
133
- /**
134
- * Get user-centric trade history path (latest snapshot)
135
- * @param {object} collectionRegistry - Collection registry
136
- * @param {string|number} cid - User CID
137
- * @returns {string} - Path to latest trade history snapshot
138
- */
139
- function getUserTradeHistoryPath(collectionRegistry, cid) {
140
- return coreHelpers.getCollectionPath(collectionRegistry, 'signedInUsers', 'tradeHistory', { cid: String(cid) });
141
- }
142
-
143
- /**
144
- * Get user-centric social posts path
145
- * @param {object} collectionRegistry - Collection registry
146
- * @param {string|number} cid - User CID
147
- * @returns {string} - Path to social posts collection
148
- */
149
- function getUserSocialPostsPath(collectionRegistry, cid) {
150
- return coreHelpers.getCollectionPath(collectionRegistry, 'signedInUsers', 'socialPosts', { cid: String(cid) });
151
- }
152
-
153
- /**
154
- * Get root data portfolio path (date-based)
155
- * @param {object} collectionRegistry - Collection registry
156
- * @param {string} date - Date string (YYYY-MM-DD)
157
- * @param {string|number} cid - User CID
158
- * @returns {string} - Path to date-based portfolio data
159
- */
160
- function getRootDataPortfolioPath(collectionRegistry, date, cid) {
161
- return coreHelpers.getCollectionPath(collectionRegistry, 'rootData', 'signedInUserPortfolio', { date, cid: String(cid) });
162
- }
163
-
164
- /**
165
- * Get root data trade history path (date-based)
166
- * @param {object} collectionRegistry - Collection registry
167
- * @param {string} date - Date string (YYYY-MM-DD)
168
- * @param {string|number} cid - User CID
169
- * @returns {string} - Path to date-based trade history data
170
- */
171
- function getRootDataTradeHistoryPath(collectionRegistry, date, cid) {
172
- return coreHelpers.getCollectionPath(collectionRegistry, 'rootData', 'signedInUserTradeHistory', { date, cid: String(cid) });
173
- }
174
-
175
- // Re-export core helpers
176
- module.exports = {
177
- getCidFromFirebaseUid: coreHelpers.getCidFromFirebaseUid,
178
- getCollectionPath: coreHelpers.getCollectionPath,
179
- extractCollectionName,
180
- readWithFallback,
181
- writeDual,
182
- getUserPortfolioPath,
183
- getUserTradeHistoryPath,
184
- getUserSocialPostsPath,
185
- getRootDataPortfolioPath,
186
- getRootDataTradeHistoryPath,
187
- // Also export new migration helpers
188
- readWithMigration: coreHelpers.readWithMigration,
189
- writeWithMigration: coreHelpers.writeWithMigration,
190
- getNewPath: coreHelpers.getNewPath,
191
- getLegacyPath: coreHelpers.getLegacyPath
192
- };
193
-
@@ -1,68 +0,0 @@
1
- /**
2
- * @fileoverview Compression/Decompression Utilities
3
- * Handles decompression of computation results stored as compressed byte strings
4
- */
5
-
6
- const zlib = require('zlib');
7
-
8
- /**
9
- * Decompress computation results if they are stored as compressed byte strings
10
- * @param {object} data - The raw Firestore document data
11
- * @returns {object} The decompressed JSON object or original data if not compressed
12
- */
13
- function tryDecompress(data) {
14
- if (data && data._compressed === true && data.payload) {
15
- try {
16
- let buffer;
17
-
18
- // Handle different payload types from Firestore
19
- if (Buffer.isBuffer(data.payload)) {
20
- // Already a Buffer - use directly
21
- buffer = data.payload;
22
- } else if (typeof data.payload === 'string') {
23
- // If it's a string, try base64 first, then direct
24
- try {
25
- buffer = Buffer.from(data.payload, 'base64');
26
- } catch (e) {
27
- // If not base64, might already be decompressed JSON string
28
- try {
29
- const parsed = JSON.parse(data.payload);
30
- // If parsing succeeds, return it (data was already decompressed)
31
- return parsed;
32
- } catch (e2) {
33
- // Not JSON either, try as raw buffer
34
- buffer = Buffer.from(data.payload);
35
- }
36
- }
37
- } else {
38
- // Try to convert to Buffer
39
- buffer = Buffer.from(data.payload);
40
- }
41
-
42
- // Decompress the buffer
43
- const decompressed = zlib.gunzipSync(buffer);
44
- const jsonString = decompressed.toString('utf8');
45
-
46
- // Parse the JSON string
47
- const parsed = JSON.parse(jsonString);
48
-
49
- // Verify it's an object, not a string
50
- if (typeof parsed === 'string') {
51
- // Might be double-encoded JSON string
52
- return JSON.parse(parsed);
53
- }
54
-
55
- return parsed;
56
- } catch (e) {
57
- console.error('[tryDecompress] Decompression failed:', e.message);
58
- // Return empty object on failure to avoid crashing
59
- return {};
60
- }
61
- }
62
- return data;
63
- }
64
-
65
- module.exports = {
66
- tryDecompress
67
- };
68
-