bulltrackers-module 1.0.456 → 1.0.459
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.
|
@@ -233,6 +233,86 @@ async function getUserSyncStatus(req, res, dependencies, config) {
|
|
|
233
233
|
if (!requestsSnapshot.empty) {
|
|
234
234
|
const latestRequest = requestsSnapshot.docs[0].data();
|
|
235
235
|
let status = latestRequest.status || 'queued';
|
|
236
|
+
const userType = latestRequest.userType || 'SIGNED_IN_USER'; // Default to signed-in user
|
|
237
|
+
|
|
238
|
+
// If status is 'indexing' or 'computing', check if computation results are now available
|
|
239
|
+
if (status === 'indexing' || status === 'computing') {
|
|
240
|
+
const insightsCollection = config.unifiedInsightsCollection || 'unified_insights';
|
|
241
|
+
const resultsSub = config.resultsSubcollection || 'results';
|
|
242
|
+
const compsSub = config.computationsSubcollection || 'computations';
|
|
243
|
+
|
|
244
|
+
// Determine category and computation name based on user type
|
|
245
|
+
let category, computationName;
|
|
246
|
+
if (userType === 'POPULAR_INVESTOR') {
|
|
247
|
+
category = 'popular-investor';
|
|
248
|
+
computationName = 'PopularInvestorProfileMetrics';
|
|
249
|
+
} else {
|
|
250
|
+
category = 'signed_in_user';
|
|
251
|
+
computationName = 'SignedInUserProfileMetrics';
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Check today and yesterday for computation results
|
|
255
|
+
const checkDate = new Date();
|
|
256
|
+
for (let i = 0; i < 2; i++) {
|
|
257
|
+
const dateStr = new Date(checkDate);
|
|
258
|
+
dateStr.setDate(checkDate.getDate() - i);
|
|
259
|
+
const dateStrFormatted = dateStr.toISOString().split('T')[0];
|
|
260
|
+
|
|
261
|
+
const docRef = db.collection(insightsCollection)
|
|
262
|
+
.doc(dateStrFormatted)
|
|
263
|
+
.collection(resultsSub)
|
|
264
|
+
.doc(category)
|
|
265
|
+
.collection(compsSub)
|
|
266
|
+
.doc(computationName);
|
|
267
|
+
|
|
268
|
+
const doc = await docRef.get();
|
|
269
|
+
if (doc.exists) {
|
|
270
|
+
const docData = doc.data();
|
|
271
|
+
let mergedData = null;
|
|
272
|
+
|
|
273
|
+
// Check if data is sharded
|
|
274
|
+
if (docData._sharded === true && docData._shardCount) {
|
|
275
|
+
// Data is stored in shards - read all shards and merge
|
|
276
|
+
const shardsCol = docRef.collection('_shards');
|
|
277
|
+
const shardsSnapshot = await shardsCol.get();
|
|
278
|
+
|
|
279
|
+
if (!shardsSnapshot.empty) {
|
|
280
|
+
mergedData = {};
|
|
281
|
+
for (const shardDoc of shardsSnapshot.docs) {
|
|
282
|
+
const shardData = shardDoc.data();
|
|
283
|
+
Object.assign(mergedData, shardData);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
} else {
|
|
287
|
+
// Data is in the main document (compressed or not)
|
|
288
|
+
const { tryDecompress } = require('./data_helpers');
|
|
289
|
+
mergedData = tryDecompress(docData);
|
|
290
|
+
|
|
291
|
+
// Handle string decompression result
|
|
292
|
+
if (typeof mergedData === 'string') {
|
|
293
|
+
try {
|
|
294
|
+
mergedData = JSON.parse(mergedData);
|
|
295
|
+
} catch (e) {
|
|
296
|
+
logger.log('WARN', `[getUserSyncStatus] Failed to parse decompressed string for date ${dateStrFormatted}:`, e.message);
|
|
297
|
+
mergedData = null;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (mergedData && typeof mergedData === 'object' && mergedData[String(targetCidNum)]) {
|
|
303
|
+
// Computation completed! Update status
|
|
304
|
+
status = 'completed';
|
|
305
|
+
await requestsSnapshot.docs[0].ref.update({
|
|
306
|
+
status: 'completed',
|
|
307
|
+
completedAt: FieldValue.serverTimestamp(),
|
|
308
|
+
updatedAt: FieldValue.serverTimestamp()
|
|
309
|
+
});
|
|
310
|
+
logger.log('INFO', `[getUserSyncStatus] Computation completed for user ${targetCidNum} (${userType}). Found results in date ${dateStrFormatted}`);
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
236
316
|
|
|
237
317
|
// Check if request is stale (stuck in processing state for too long)
|
|
238
318
|
const STALE_THRESHOLD_MS = 5 * 60 * 1000; // 5 minutes
|
|
@@ -275,6 +355,17 @@ async function getUserSyncStatus(req, res, dependencies, config) {
|
|
|
275
355
|
}
|
|
276
356
|
}
|
|
277
357
|
|
|
358
|
+
// Re-fetch request if we updated it to get the latest completedAt
|
|
359
|
+
let completedAt = latestRequest.completedAt?.toDate?.()?.toISOString() || null;
|
|
360
|
+
if (status === 'completed' && !completedAt) {
|
|
361
|
+
// If we just updated to completed, re-fetch to get the server timestamp
|
|
362
|
+
const updatedDoc = await requestsSnapshot.docs[0].ref.get();
|
|
363
|
+
if (updatedDoc.exists) {
|
|
364
|
+
const updatedData = updatedDoc.data();
|
|
365
|
+
completedAt = updatedData.completedAt?.toDate?.()?.toISOString() || null;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
278
369
|
const response = {
|
|
279
370
|
success: true,
|
|
280
371
|
status,
|
|
@@ -282,7 +373,7 @@ async function getUserSyncStatus(req, res, dependencies, config) {
|
|
|
282
373
|
startedAt: latestRequest.startedAt?.toDate?.()?.toISOString() || null,
|
|
283
374
|
createdAt: latestRequest.createdAt?.toDate?.()?.toISOString() || null,
|
|
284
375
|
dispatchedAt: latestRequest.dispatchedAt?.toDate?.()?.toISOString() || null,
|
|
285
|
-
completedAt:
|
|
376
|
+
completedAt: completedAt,
|
|
286
377
|
estimatedCompletion: latestRequest.dispatchedAt
|
|
287
378
|
? new Date(new Date(latestRequest.dispatchedAt.toDate()).getTime() + 5 * 60 * 1000).toISOString() // 5 min estimate
|
|
288
379
|
: null
|
|
@@ -38,11 +38,16 @@ async function handlePopularInvestorUpdate(taskData, config, dependencies) {
|
|
|
38
38
|
// If we have a requestId but no cid, try to extract from requestId or use username
|
|
39
39
|
const effectiveCid = cid || (requestId ? null : null); // Will need to be handled
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
41
|
+
// Validate and set API URLs with defaults and fallbacks
|
|
42
|
+
const ETORO_API_PORTFOLIO_URL = config.ETORO_API_PORTFOLIO_URL || process.env.ETORO_API_PORTFOLIO_URL || 'https://www.etoro.com/sapi/trade-data-real/live/public/portfolios';
|
|
43
|
+
const ETORO_API_POSITIONS_URL = config.ETORO_API_POSITIONS_URL || process.env.ETORO_API_POSITIONS_URL || 'https://www.etoro.com/sapi/trade-data-real/live/public/positions';
|
|
44
|
+
const ETORO_API_HISTORY_URL = config.ETORO_API_HISTORY_URL || process.env.ETORO_API_HISTORY_URL || 'https://www.etoro.com/sapi/trade-data-real/history/public/credit/flat';
|
|
45
|
+
|
|
46
|
+
if (!ETORO_API_PORTFOLIO_URL || ETORO_API_PORTFOLIO_URL === 'undefined') {
|
|
47
|
+
const errorMsg = 'ETORO_API_PORTFOLIO_URL is not configured. Check taskEngine config.';
|
|
48
|
+
logger.log('ERROR', `[PI Update] ${errorMsg}`);
|
|
49
|
+
throw new Error(errorMsg);
|
|
50
|
+
}
|
|
46
51
|
|
|
47
52
|
logger.log('INFO', `[PI Update] Starting update for User: ${username || 'unknown'} (${cid || 'unknown'})${requestId ? ` [Request: ${requestId}]` : ''}`);
|
|
48
53
|
|