bulltrackers-module 1.0.452 → 1.0.454

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.
@@ -234,6 +234,47 @@ async function getUserSyncStatus(req, res, dependencies, config) {
234
234
  const latestRequest = requestsSnapshot.docs[0].data();
235
235
  let status = latestRequest.status || 'queued';
236
236
 
237
+ // Check if request is stale (stuck in processing state for too long)
238
+ const STALE_THRESHOLD_MS = 5 * 60 * 1000; // 5 minutes
239
+ const processingStates = ['processing', 'dispatched', 'indexing', 'computing', 'queued'];
240
+ const isProcessingState = processingStates.includes(status);
241
+
242
+ let isStale = false;
243
+ if (isProcessingState) {
244
+ const now = Date.now();
245
+ const createdAt = latestRequest.createdAt?.toDate?.()?.getTime() ||
246
+ latestRequest.createdAt?.toMillis?.() || null;
247
+ const dispatchedAt = latestRequest.dispatchedAt?.toDate?.()?.getTime() ||
248
+ latestRequest.dispatchedAt?.toMillis?.() || null;
249
+ const updatedAt = latestRequest.updatedAt?.toDate?.()?.getTime() ||
250
+ latestRequest.updatedAt?.toMillis?.() || null;
251
+
252
+ // Use the most recent timestamp to determine age
253
+ const referenceTime = dispatchedAt || createdAt || updatedAt;
254
+
255
+ if (referenceTime && (now - referenceTime) > STALE_THRESHOLD_MS) {
256
+ isStale = true;
257
+ logger.log('WARN', `[getUserSyncStatus] Detected stale request ${latestRequest.requestId} for user ${targetCidNum}. Status: ${status}, Age: ${Math.round((now - referenceTime) / 60000)} minutes`);
258
+ }
259
+ }
260
+
261
+ // If stale, mark as failed to stop polling
262
+ if (isStale) {
263
+ status = 'failed';
264
+ const requestDocRef = requestsSnapshot.docs[0].ref;
265
+ try {
266
+ await requestDocRef.update({
267
+ status: 'failed',
268
+ error: 'Request timed out - task may have failed to process. Please try again.',
269
+ failedAt: FieldValue.serverTimestamp(),
270
+ updatedAt: FieldValue.serverTimestamp()
271
+ });
272
+ logger.log('INFO', `[getUserSyncStatus] Marked stale request ${latestRequest.requestId} as failed`);
273
+ } catch (updateErr) {
274
+ logger.log('WARN', `[getUserSyncStatus] Failed to update stale request status`, updateErr);
275
+ }
276
+ }
277
+
237
278
  const response = {
238
279
  success: true,
239
280
  status,
@@ -249,8 +290,11 @@ async function getUserSyncStatus(req, res, dependencies, config) {
249
290
 
250
291
  // Include error details if status is failed
251
292
  if (status === 'failed') {
252
- response.error = latestRequest.error || 'Unknown error occurred';
253
- response.failedAt = latestRequest.failedAt?.toDate?.()?.toISOString() || null;
293
+ response.error = isStale
294
+ ? 'Request timed out - task may have failed to process. Please try again.'
295
+ : (latestRequest.error || 'Unknown error occurred');
296
+ response.failedAt = latestRequest.failedAt?.toDate?.()?.toISOString() ||
297
+ (isStale ? new Date().toISOString() : null);
254
298
  }
255
299
 
256
300
  // Include raw data status if processing
@@ -474,10 +474,19 @@ async function handlePopularInvestorUpdate(taskData, config, dependencies) {
474
474
  */
475
475
  async function handleOnDemandUserUpdate(taskData, config, dependencies) {
476
476
  const { cid, username, requestId, source } = taskData;
477
- const { ETORO_API_PORTFOLIO_URL, ETORO_API_HISTORY_URL } = config;
478
477
 
479
- // [FIX] Destructure headerManager
478
+ // [FIX] Destructure dependencies first
480
479
  const { logger, proxyManager, batchManager, headerManager, db } = dependencies;
480
+
481
+ // Validate and set API URLs with defaults and fallbacks
482
+ const ETORO_API_PORTFOLIO_URL = config.ETORO_API_PORTFOLIO_URL || process.env.ETORO_API_PORTFOLIO_URL || 'https://www.etoro.com/sapi/portfolios/portfolio';
483
+ 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';
484
+
485
+ if (!ETORO_API_PORTFOLIO_URL || ETORO_API_PORTFOLIO_URL === 'undefined') {
486
+ const errorMsg = 'ETORO_API_PORTFOLIO_URL is not configured. Check taskEngine config.';
487
+ logger.log('ERROR', `[On-Demand Update] ${errorMsg}`);
488
+ throw new Error(errorMsg);
489
+ }
481
490
 
482
491
  logger.log('INFO', `[On-Demand Update] Fetching Portfolio & History for Signed-In User: ${username} (${cid})${requestId ? ` [Request: ${requestId}]` : ''}`);
483
492
 
@@ -636,8 +645,49 @@ async function handleOnDemandUserUpdate(taskData, config, dependencies) {
636
645
  }
637
646
 
638
647
  logger.log('SUCCESS', `[On-Demand Update] Complete for ${username}`);
648
+
649
+ // Update request status to completed if this is a sync request
650
+ if (requestId && source === 'on_demand_sync' && db) {
651
+ try {
652
+ const requestRef = db.collection('user_sync_requests')
653
+ .doc(String(cid))
654
+ .collection('requests')
655
+ .doc(requestId);
656
+
657
+ await requestRef.update({
658
+ status: 'completed',
659
+ completedAt: require('@google-cloud/firestore').FieldValue.serverTimestamp(),
660
+ updatedAt: require('@google-cloud/firestore').FieldValue.serverTimestamp()
661
+ });
662
+ logger.log('INFO', `[On-Demand Update] Updated sync request ${requestId} to completed`);
663
+ } catch (err) {
664
+ logger.log('WARN', `[On-Demand Update] Failed to update request status to completed for ${requestId}`, err);
665
+ }
666
+ }
639
667
  } catch (error) {
640
668
  logger.log('ERROR', `[On-Demand Update] Failed for ${username}`, error);
669
+
670
+ // Update request status to failed if this is a sync request
671
+ if (requestId && source === 'on_demand_sync' && db) {
672
+ try {
673
+ const requestRef = db.collection('user_sync_requests')
674
+ .doc(String(cid))
675
+ .collection('requests')
676
+ .doc(requestId);
677
+
678
+ const errorMessage = error.message || 'Unknown error occurred';
679
+ await requestRef.update({
680
+ status: 'failed',
681
+ error: errorMessage,
682
+ failedAt: require('@google-cloud/firestore').FieldValue.serverTimestamp(),
683
+ updatedAt: require('@google-cloud/firestore').FieldValue.serverTimestamp()
684
+ });
685
+ logger.log('INFO', `[On-Demand Update] Updated sync request ${requestId} to failed: ${errorMessage}`);
686
+ } catch (err) {
687
+ logger.log('WARN', `[On-Demand Update] Failed to update request status to failed for ${requestId}`, err);
688
+ }
689
+ }
690
+
641
691
  throw error;
642
692
  } finally {
643
693
  if (headerManager && headerId !== 'fallback') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.452",
3
+ "version": "1.0.454",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [