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.
- 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 +2 -1
- package/functions/generic-api/admin-api/index.js +0 -895
- package/functions/generic-api/helpers/api_helpers.js +0 -457
- package/functions/generic-api/index.js +0 -204
- 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/user-api/helpers/alerts/alert_helpers.js +0 -355
- package/functions/generic-api/user-api/helpers/alerts/subscription_helpers.js +0 -327
- package/functions/generic-api/user-api/helpers/alerts/test_alert_helpers.js +0 -212
- package/functions/generic-api/user-api/helpers/collection_helpers.js +0 -193
- package/functions/generic-api/user-api/helpers/core/compression_helpers.js +0 -68
- package/functions/generic-api/user-api/helpers/core/data_lookup_helpers.js +0 -256
- package/functions/generic-api/user-api/helpers/core/path_resolution_helpers.js +0 -640
- package/functions/generic-api/user-api/helpers/core/user_status_helpers.js +0 -195
- package/functions/generic-api/user-api/helpers/data/computation_helpers.js +0 -503
- package/functions/generic-api/user-api/helpers/data/instrument_helpers.js +0 -55
- package/functions/generic-api/user-api/helpers/data/portfolio_helpers.js +0 -245
- package/functions/generic-api/user-api/helpers/data/social_helpers.js +0 -174
- package/functions/generic-api/user-api/helpers/data_helpers.js +0 -87
- package/functions/generic-api/user-api/helpers/dev/dev_helpers.js +0 -336
- package/functions/generic-api/user-api/helpers/fetch/on_demand_fetch_helpers.js +0 -615
- package/functions/generic-api/user-api/helpers/metrics/personalized_metrics_helpers.js +0 -231
- package/functions/generic-api/user-api/helpers/notifications/notification_helpers.js +0 -641
- package/functions/generic-api/user-api/helpers/profile/pi_profile_helpers.js +0 -182
- package/functions/generic-api/user-api/helpers/profile/profile_view_helpers.js +0 -137
- package/functions/generic-api/user-api/helpers/profile/user_profile_helpers.js +0 -190
- package/functions/generic-api/user-api/helpers/recommendations/recommendation_helpers.js +0 -66
- package/functions/generic-api/user-api/helpers/reviews/review_helpers.js +0 -550
- package/functions/generic-api/user-api/helpers/rootdata/rootdata_aggregation_helpers.js +0 -378
- package/functions/generic-api/user-api/helpers/search/pi_request_helpers.js +0 -295
- package/functions/generic-api/user-api/helpers/search/pi_search_helpers.js +0 -162
- package/functions/generic-api/user-api/helpers/sync/user_sync_helpers.js +0 -677
- package/functions/generic-api/user-api/helpers/verification/verification_helpers.js +0 -323
- package/functions/generic-api/user-api/helpers/watchlist/watchlist_analytics_helpers.js +0 -96
- package/functions/generic-api/user-api/helpers/watchlist/watchlist_data_helpers.js +0 -141
- package/functions/generic-api/user-api/helpers/watchlist/watchlist_generation_helpers.js +0 -310
- package/functions/generic-api/user-api/helpers/watchlist/watchlist_management_helpers.js +0 -829
- 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
|
-
|