bulltrackers-module 1.0.480 → 1.0.491

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.
@@ -231,6 +231,28 @@ async function handleComputationTask(message, config, dependencies) {
231
231
 
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
+
235
+ // Send notification if this was an on-demand computation
236
+ if (metadata?.onDemand && metadata?.requestId && metadata?.requestingUserCid) {
237
+ try {
238
+ const { notifyComputationComplete, getComputationDisplayName } = require('../../generic-api/user-api/helpers/notification_helpers');
239
+ await notifyComputationComplete(
240
+ dependencies.db,
241
+ dependencies.logger,
242
+ metadata.requestingUserCid,
243
+ metadata.requestId,
244
+ computation,
245
+ getComputationDisplayName(computation),
246
+ true,
247
+ null
248
+ );
249
+ } catch (notifError) {
250
+ // Non-critical, log and continue
251
+ if (dependencies.logger) {
252
+ dependencies.logger.log('WARN', `[Worker] Failed to send completion notification for ${computation}`, notifError);
253
+ }
254
+ }
255
+ }
234
256
 
235
257
  } catch (err) {
236
258
  heartbeats.stop();
@@ -257,6 +279,29 @@ async function handleComputationTask(message, config, dependencies) {
257
279
  }, { merge: true });
258
280
 
259
281
  await recordRunAttempt(db, { date, computation, pass }, 'FAILURE', { message: err.message, stage: err.stage || 'FATAL' }, { peakMemoryMB: heartbeats.getPeak() }, triggerReason, resourceTier);
282
+
283
+ // Send error notification if this was an on-demand computation
284
+ if (metadata?.onDemand && metadata?.requestId && metadata?.requestingUserCid) {
285
+ try {
286
+ const { notifyComputationComplete, getComputationDisplayName } = require('../../generic-api/user-api/helpers/notification_helpers');
287
+ await notifyComputationComplete(
288
+ db,
289
+ dependencies.logger,
290
+ metadata.requestingUserCid,
291
+ metadata.requestId,
292
+ computation,
293
+ getComputationDisplayName(computation),
294
+ false,
295
+ err.message
296
+ );
297
+ } catch (notifError) {
298
+ // Non-critical, log and continue
299
+ if (dependencies.logger) {
300
+ dependencies.logger.log('WARN', `[Worker] Failed to send error notification for ${computation}`, notifError);
301
+ }
302
+ }
303
+ }
304
+
260
305
  return;
261
306
  }
262
307
 
@@ -0,0 +1,98 @@
1
+ # Profile Page Fallback Conditions
2
+
3
+ ## Overview
4
+
5
+ This document explains when and why the profile page uses fallback mode instead of computation data.
6
+
7
+ ## Data Storage Locations
8
+
9
+ ### Signed-In User Computations
10
+ - **Category**: `popular-investor` (NOT `signed_in_user`)
11
+ - **Reason**: ResultCommitter ignores category metadata when computation is in a non-core folder
12
+ - **Computation**: `SignedInUserProfileMetrics` is in `calculations/popular-investor/` folder
13
+ - **Path**: `/unified_insights/{date}/results/popular-investor/computations/SignedInUserProfileMetrics`
14
+
15
+ ### Signed-In User Raw Data
16
+ - **Portfolio**: `signed_in_users/{blockId}/snapshots/{date}/parts/part_*`
17
+ - **History**: `signed_in_user_history/{blockId}/snapshots/{date}/parts/part_*` (or `signed_in_users_history` depending on config)
18
+ - **Social**: `signed_in_users_social/{cid}/posts/{postId}`
19
+
20
+ ## Fallback Conditions
21
+
22
+ ### Frontend Fallback Logic (UserProfile.tsx)
23
+
24
+ The profile page uses fallback mode when:
25
+
26
+ 1. **Computation Not Found**:
27
+ - API call to `/user/me/computations?computation=SignedInUserProfileMetrics` returns empty `data: {}`
28
+ - OR the computation exists but doesn't contain data for the user's CID
29
+
30
+ 2. **API Error**:
31
+ - Any error fetching the computation (network error, 500, etc.)
32
+
33
+ 3. **Data Structure Issues**:
34
+ - Computation exists but `metricsResponse.data[latestDate]?.SignedInUserProfileMetrics` is undefined/null
35
+
36
+ ### Backend Fallback Logic (getUserComputations)
37
+
38
+ The API returns `isFallback: false` but `data: {}` when:
39
+
40
+ 1. **Wrong Category Lookup** (FIXED):
41
+ - Was looking in `signed_in_user` category
42
+ - Should look in `popular-investor` category
43
+
44
+ 2. **No Data for User**:
45
+ - Computation document exists but doesn't contain `data[String(effectiveCid)]`
46
+ - This can happen if:
47
+ - Computation ran but user wasn't included (shouldn't happen with targetCid optimization)
48
+ - Data is sharded and shard doesn't contain user's data
49
+ - Data exists but in wrong format
50
+
51
+ 3. **Date Mismatch**:
52
+ - Computation exists for different date than requested
53
+ - `mode=latest` tries to find latest date, but if none found, returns empty
54
+
55
+ ## Current Issue
56
+
57
+ The user reported:
58
+ - API returns `"isFallback": false, "data": {}`
59
+ - Profile page shows fallback mode
60
+ - Computation exists at `/unified_insights/2025-12-29/results/popular-investor/computations/SignedInUserProfileMetrics/_shards/shard_0`
61
+
62
+ **Root Cause**: The `getUserComputations` endpoint was looking in the wrong category (`signed_in_user` instead of `popular-investor`).
63
+
64
+ **Fix Applied**: Updated `getUserComputations` to look in `popular-investor` category for `SignedInUserProfileMetrics`.
65
+
66
+ ## Verification Steps
67
+
68
+ 1. Check computation exists:
69
+ ```
70
+ /unified_insights/2025-12-29/results/popular-investor/computations/SignedInUserProfileMetrics
71
+ ```
72
+
73
+ 2. Check if data is sharded:
74
+ - If `_sharded: true`, check `_shards` subcollection
75
+ - Merge all shards to get full data
76
+
77
+ 3. Check if user's CID exists in data:
78
+ - Data structure: `{ "29312236": { ...metrics... } }`
79
+ - Must use `String(cid)` as key
80
+
81
+ 4. Check API response:
82
+ - Should return `data: { "2025-12-29": { "SignedInUserProfileMetrics": { ... } } }`
83
+ - If empty, check logs for why lookup failed
84
+
85
+ ## History Data Storage
86
+
87
+ Signed-in user trade history is stored in:
88
+ - Collection: `signed_in_user_history` (or `signed_in_users_history` depending on config)
89
+ - Path: `{collection}/{blockId}/snapshots/{date}/parts/part_*`
90
+ - Block ID: Typically `19M` (canary block)
91
+
92
+ The task engine stores history via:
93
+ ```javascript
94
+ await batchManager.addToTradingHistoryBatch(String(cid), blockId, today, historyData, 'signed_in_user');
95
+ ```
96
+
97
+ This uses `getHistoryCollection('signed_in_user')` which returns `signedInHistory` collection.
98
+
@@ -0,0 +1,66 @@
1
+ # Signed-In User History Storage Location
2
+
3
+ ## Current Issue
4
+
5
+ History data is being stored in the **wrong collection** because `FIRESTORE_COLLECTION_SIGNED_IN_HISTORY` is not defined in the task engine config, causing it to fall back to `NormalUserTradeHistory`.
6
+
7
+ ## Storage Path Structure
8
+
9
+ For signed-in users, history data should be stored at:
10
+ ```
11
+ {signed_in_user_history}/{blockId}/snapshots/{date}/parts/part_{shardIndex}
12
+ ```
13
+
14
+ Where:
15
+ - `signed_in_user_history` = Collection name (default: `signed_in_user_history`)
16
+ - `blockId` = `'19M'` (canary block ID)
17
+ - `date` = Date string in format `YYYY-MM-DD` (e.g., `2025-12-29`)
18
+ - `shardIndex` = `cid % TOTAL_SHARDS` (modulo sharding for even distribution)
19
+
20
+ ## Example Path
21
+
22
+ For user CID `29312236` on date `2025-12-29`:
23
+ ```
24
+ signed_in_user_history/19M/snapshots/2025-12-29/parts/part_{shardIndex}
25
+ ```
26
+
27
+ Where `shardIndex = 29312236 % TOTAL_SHARDS` (typically 7 shards, so `29312236 % 7 = 1`)
28
+
29
+ ## Fix Applied
30
+
31
+ Added `FIRESTORE_COLLECTION_SIGNED_IN_HISTORY` to `taskEngine_config.js`:
32
+ ```javascript
33
+ FIRESTORE_COLLECTION_SIGNED_IN_HISTORY: getEnvVar('FIRESTORE_COLLECTION_SIGNED_IN_HISTORY', 'signed_in_user_history'),
34
+ ```
35
+
36
+ ## Before Fix
37
+
38
+ Without this config, the batch manager was using:
39
+ - `config.FIRESTORE_COLLECTION_SIGNED_IN_HISTORY` = `undefined`
40
+ - Fallback: `config.FIRESTORE_COLLECTION_NORMAL_HISTORY` = `'NormalUserTradeHistory'`
41
+
42
+ So data was being stored at:
43
+ ```
44
+ NormalUserTradeHistory/19M/snapshots/2025-12-29/parts/part_{shardIndex}
45
+ ```
46
+
47
+ ## After Fix
48
+
49
+ With the config added, data will be stored at:
50
+ ```
51
+ signed_in_user_history/19M/snapshots/2025-12-29/parts/part_{shardIndex}
52
+ ```
53
+
54
+ ## Verification
55
+
56
+ To verify history data exists for a user:
57
+ 1. Calculate shard index: `userCid % 7` (or whatever TOTAL_SHARDS is)
58
+ 2. Check path: `signed_in_user_history/19M/snapshots/2025-12-29/parts/part_{shardIndex}`
59
+ 3. Look for user's CID as a key in the document: `{ "29312236": { ...historyData... } }`
60
+
61
+ ## Related Collections
62
+
63
+ - **Portfolio**: `signed_in_users/19M/snapshots/{date}/parts/part_{shardIndex}`
64
+ - **History**: `signed_in_user_history/19M/snapshots/{date}/parts/part_{shardIndex}` (FIXED)
65
+ - **Social**: `signed_in_users_social/{cid}/posts/{postId}`
66
+
@@ -870,8 +870,10 @@ async function getUserComputations(req, res, dependencies, config) {
870
870
  const resultsSub = config.resultsSubcollection || 'results';
871
871
  const compsSub = config.computationsSubcollection || 'computations';
872
872
 
873
- // For signed-in users, computations are stored in the 'signed_in_user' category
874
- const category = 'signed_in_user';
873
+ // NOTE: ResultCommitter ignores category metadata if computation is in a non-core folder
874
+ // SignedInUserProfileMetrics is in popular-investor folder, so it's stored in popular-investor category
875
+ // For signed-in users, computations are stored in the 'popular-investor' category (not 'signed_in_user')
876
+ const category = 'popular-investor';
875
877
  const today = new Date().toISOString().split('T')[0];
876
878
 
877
879
  const computationNames = computation ? computation.split(',') : [];
@@ -0,0 +1,131 @@
1
+ /**
2
+ * @fileoverview Notification Helpers for On-Demand Requests
3
+ * Sends notifications to users when their on-demand sync/computation requests complete
4
+ */
5
+
6
+ const { FieldValue } = require('@google-cloud/firestore');
7
+
8
+ /**
9
+ * Send a notification to a user about their on-demand request
10
+ * @param {Firestore} db - Firestore instance
11
+ * @param {Object} logger - Logger instance
12
+ * @param {number} userCid - User CID to notify
13
+ * @param {string} type - Notification type ('success', 'error', 'progress')
14
+ * @param {string} title - Notification title
15
+ * @param {string} message - Notification message
16
+ * @param {Object} metadata - Additional metadata (requestId, computationName, etc.)
17
+ */
18
+ async function sendOnDemandNotification(db, logger, userCid, type, title, message, metadata = {}) {
19
+ try {
20
+ const notificationId = `ondemand_${Date.now()}_${userCid}_${Math.random().toString(36).substring(2, 9)}`;
21
+ const notificationRef = db.collection('user_notifications')
22
+ .doc(String(userCid))
23
+ .collection('notifications')
24
+ .doc(notificationId);
25
+
26
+ const notificationData = {
27
+ id: notificationId,
28
+ type: 'on_demand',
29
+ subType: type, // 'success', 'error', 'progress'
30
+ title,
31
+ message,
32
+ read: false,
33
+ createdAt: FieldValue.serverTimestamp(),
34
+ metadata: {
35
+ ...metadata,
36
+ userCid: Number(userCid)
37
+ }
38
+ };
39
+
40
+ await notificationRef.set(notificationData);
41
+
42
+ // Update notification counter
43
+ const today = new Date().toISOString().split('T')[0];
44
+ const counterRef = db.collection('user_notifications')
45
+ .doc(String(userCid))
46
+ .collection('counters')
47
+ .doc(today);
48
+
49
+ await counterRef.set({
50
+ date: today,
51
+ unreadCount: FieldValue.increment(1),
52
+ totalCount: FieldValue.increment(1),
53
+ lastUpdated: FieldValue.serverTimestamp()
54
+ }, { merge: true });
55
+
56
+ logger.log('INFO', `[sendOnDemandNotification] Sent ${type} notification to user ${userCid}: ${title}`);
57
+
58
+ } catch (error) {
59
+ logger.log('ERROR', `[sendOnDemandNotification] Failed to send notification to user ${userCid}`, error);
60
+ // Don't throw - notifications are non-critical
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Send notification when task engine completes data fetch
66
+ */
67
+ async function notifyTaskEngineComplete(db, logger, requestingUserCid, requestId, username, success, error = null) {
68
+ if (!requestingUserCid) return; // No user to notify
69
+
70
+ const type = success ? 'success' : 'error';
71
+ const title = success
72
+ ? 'Data Sync Complete'
73
+ : 'Data Sync Failed';
74
+ const message = success
75
+ ? `Your data sync for ${username} has completed. Portfolio, history, and social data have been stored.`
76
+ : (error || 'An error occurred while syncing your data. Please try again.');
77
+
78
+ await sendOnDemandNotification(db, logger, requestingUserCid, type, title, message, {
79
+ requestId,
80
+ username,
81
+ stage: 'task_engine',
82
+ success
83
+ });
84
+ }
85
+
86
+ /**
87
+ * Send notification when computation completes
88
+ */
89
+ async function notifyComputationComplete(db, logger, requestingUserCid, requestId, computationName, displayName, success, error = null) {
90
+ if (!requestingUserCid) return; // No user to notify
91
+
92
+ const type = success ? 'success' : 'error';
93
+ const title = success
94
+ ? 'Computation Complete'
95
+ : 'Computation Failed';
96
+ const message = success
97
+ ? `${displayName || computationName} has been computed and stored.`
98
+ : (error || `Failed to compute ${displayName || computationName}. Please try again.`);
99
+
100
+ await sendOnDemandNotification(db, logger, requestingUserCid, type, title, message, {
101
+ requestId,
102
+ computationName,
103
+ displayName,
104
+ stage: 'computation',
105
+ success
106
+ });
107
+ }
108
+
109
+ /**
110
+ * Get human-readable name for a computation
111
+ */
112
+ function getComputationDisplayName(computationName) {
113
+ const displayNames = {
114
+ 'SignedInUserProfileMetrics': 'Profile Metrics',
115
+ 'SignedInUserCopiedList': 'Copied Investors List',
116
+ 'SignedInUserCopiedPIs': 'Copied Popular Investors',
117
+ 'SignedInUserPastCopies': 'Past Copy History',
118
+ 'PopularInvestorProfileMetrics': 'Popular Investor Profile',
119
+ 'SignedInUserPIPersonalizedMetrics': 'Personalized Metrics'
120
+ };
121
+
122
+ return displayNames[computationName] || computationName;
123
+ }
124
+
125
+ module.exports = {
126
+ sendOnDemandNotification,
127
+ notifyTaskEngineComplete,
128
+ notifyComputationComplete,
129
+ getComputationDisplayName
130
+ };
131
+
@@ -163,11 +163,16 @@ async function requestUserSync(req, res, dependencies, config) {
163
163
  requestId,
164
164
  requestedBy: Number(requestingUserCid),
165
165
  effectiveRequestedBy: effectiveCid,
166
+ data: {
167
+ includeSocial: true, // Always include social data for on-demand syncs
168
+ since: new Date(Date.now() - (7 * 24 * 60 * 60 * 1000)).toISOString() // Last 7 days
169
+ },
166
170
  metadata: {
167
171
  onDemand: true,
168
172
  targetCid: targetCidNum, // Target specific user for optimization
169
173
  requestedAt: now.toISOString(),
170
- isImpersonating: isImpersonating || false
174
+ isImpersonating: isImpersonating || false,
175
+ requestingUserCid: Number(requestingUserCid) // Store for notifications
171
176
  }
172
177
  };
173
178
 
@@ -245,12 +250,15 @@ async function getUserSyncStatus(req, res, dependencies, config) {
245
250
  const compsSub = config.computationsSubcollection || 'computations';
246
251
 
247
252
  // Determine category and computation name based on user type
253
+ // NOTE: ResultCommitter ignores category metadata if computation is in a non-core folder
254
+ // SignedInUserProfileMetrics is in popular-investor folder, so it's stored in popular-investor category
248
255
  let category, computationName;
249
256
  if (userType === 'POPULAR_INVESTOR') {
250
257
  category = 'popular-investor';
251
258
  computationName = 'PopularInvestorProfileMetrics';
252
259
  } else {
253
- category = 'signed_in_user';
260
+ // SignedInUserProfileMetrics is in popular-investor folder, so stored in popular-investor category
261
+ category = 'popular-investor';
254
262
  computationName = 'SignedInUserProfileMetrics';
255
263
  }
256
264
 
@@ -318,7 +326,8 @@ async function getUserSyncStatus(req, res, dependencies, config) {
318
326
  }
319
327
 
320
328
  // Check if request is stale (stuck in processing state for too long)
321
- const STALE_THRESHOLD_MS = 5 * 60 * 1000; // 5 minutes
329
+ // Increased timeout to 15 minutes to account for computation time
330
+ const STALE_THRESHOLD_MS = 15 * 60 * 1000; // 15 minutes
322
331
  const processingStates = ['processing', 'dispatched', 'indexing', 'computing', 'queued'];
323
332
  const isProcessingState = processingStates.includes(status);
324
333
 
@@ -336,8 +345,22 @@ async function getUserSyncStatus(req, res, dependencies, config) {
336
345
  const referenceTime = dispatchedAt || createdAt || updatedAt;
337
346
 
338
347
  if (referenceTime && (now - referenceTime) > STALE_THRESHOLD_MS) {
339
- isStale = true;
340
- logger.log('WARN', `[getUserSyncStatus] Detected stale request ${latestRequest.requestId} for user ${targetCidNum}. Status: ${status}, Age: ${Math.round((now - referenceTime) / 60000)} minutes`);
348
+ // Before marking as stale, do one final check for computation results
349
+ // Sometimes computation completes but status wasn't updated
350
+ const finalCheck = await checkComputationResults(db, targetCidNum, userType, insightsCollection, resultsSub, compsSub, logger);
351
+ if (!finalCheck) {
352
+ isStale = true;
353
+ logger.log('WARN', `[getUserSyncStatus] Detected stale request ${latestRequest.requestId} for user ${targetCidNum}. Status: ${status}, Age: ${Math.round((now - referenceTime) / 60000)} minutes`);
354
+ } else {
355
+ // Found results, update status to completed
356
+ status = 'completed';
357
+ await requestsSnapshot.docs[0].ref.update({
358
+ status: 'completed',
359
+ completedAt: FieldValue.serverTimestamp(),
360
+ updatedAt: FieldValue.serverTimestamp()
361
+ });
362
+ logger.log('INFO', `[getUserSyncStatus] Found computation results on stale check, marked as completed`);
363
+ }
341
364
  }
342
365
  }
343
366
 
@@ -489,6 +512,80 @@ async function checkRateLimits(db, targetUserCid, logger) {
489
512
  };
490
513
  }
491
514
 
515
+ /**
516
+ * Helper function to check for computation results
517
+ */
518
+ async function checkComputationResults(db, targetCidNum, userType, insightsCollection, resultsSub, compsSub, logger) {
519
+ try {
520
+ // Determine category and computation name based on user type
521
+ // NOTE: ResultCommitter ignores category metadata if computation is in a non-core folder
522
+ // SignedInUserProfileMetrics is in popular-investor folder, so it's stored in popular-investor category
523
+ let category, computationName;
524
+ if (userType === 'POPULAR_INVESTOR') {
525
+ category = 'popular-investor';
526
+ computationName = 'PopularInvestorProfileMetrics';
527
+ } else {
528
+ // SignedInUserProfileMetrics is in popular-investor folder, so stored in popular-investor category
529
+ category = 'popular-investor';
530
+ computationName = 'SignedInUserProfileMetrics';
531
+ }
532
+
533
+ // Check today and yesterday for computation results
534
+ const checkDate = new Date();
535
+ for (let i = 0; i < 2; i++) {
536
+ const dateStr = new Date(checkDate);
537
+ dateStr.setDate(checkDate.getDate() - i);
538
+ const dateStrFormatted = dateStr.toISOString().split('T')[0];
539
+
540
+ const docRef = db.collection(insightsCollection)
541
+ .doc(dateStrFormatted)
542
+ .collection(resultsSub)
543
+ .doc(category)
544
+ .collection(compsSub)
545
+ .doc(computationName);
546
+
547
+ const doc = await docRef.get();
548
+ if (doc.exists) {
549
+ const docData = doc.data();
550
+ let mergedData = null;
551
+
552
+ // Check if data is sharded
553
+ if (docData._sharded === true && docData._shardCount) {
554
+ const shardsCol = docRef.collection('_shards');
555
+ const shardsSnapshot = await shardsCol.get();
556
+
557
+ if (!shardsSnapshot.empty) {
558
+ mergedData = {};
559
+ for (const shardDoc of shardsSnapshot.docs) {
560
+ const shardData = shardDoc.data();
561
+ Object.assign(mergedData, shardData);
562
+ }
563
+ }
564
+ } else {
565
+ const { tryDecompress } = require('./data_helpers');
566
+ mergedData = tryDecompress(docData);
567
+
568
+ if (typeof mergedData === 'string') {
569
+ try {
570
+ mergedData = JSON.parse(mergedData);
571
+ } catch (e) {
572
+ mergedData = null;
573
+ }
574
+ }
575
+ }
576
+
577
+ if (mergedData && typeof mergedData === 'object' && mergedData[String(targetCidNum)]) {
578
+ return true; // Found results
579
+ }
580
+ }
581
+ }
582
+ return false; // No results found
583
+ } catch (error) {
584
+ logger.log('WARN', `[checkComputationResults] Error checking computation results`, error);
585
+ return false;
586
+ }
587
+ }
588
+
492
589
  module.exports = {
493
590
  requestUserSync,
494
591
  getUserSyncStatus
@@ -761,6 +761,26 @@ async function handleOnDemandUserUpdate(taskData, config, dependencies) {
761
761
 
762
762
  logger.log('SUCCESS', `[On-Demand Update] Complete for ${username} (Portfolio: ${portfolioFetched ? '✓' : '✗'}, History: ${historyFetched ? '✓' : '✗'}, Social: ${socialFetched ? '✓' : '✗'})`);
763
763
 
764
+ // Send notification to requesting user if this is an on-demand sync
765
+ if (requestId && source === 'on_demand_sync' && metadata?.requestingUserCid) {
766
+ try {
767
+ const { notifyTaskEngineComplete } = require('../../generic-api/user-api/helpers/notification_helpers');
768
+ const success = portfolioFetched || historyFetched; // At least one should succeed
769
+ await notifyTaskEngineComplete(
770
+ db,
771
+ logger,
772
+ metadata.requestingUserCid,
773
+ requestId,
774
+ username,
775
+ success,
776
+ success ? null : 'Failed to fetch portfolio or history data'
777
+ );
778
+ } catch (notifError) {
779
+ logger.log('WARN', `[On-Demand Update] Failed to send notification`, notifError);
780
+ // Non-critical, continue
781
+ }
782
+ }
783
+
764
784
  // Update root data indexer after all data is fetched
765
785
  // Then trigger computations for both sync requests AND user signups
766
786
  const shouldTriggerComputations = (requestId && (source === 'on_demand_sync' || source === 'user_signup')) || isNewUser;
@@ -834,7 +854,9 @@ async function handleOnDemandUserUpdate(taskData, config, dependencies) {
834
854
  requestId: requestId,
835
855
  userCid: cid,
836
856
  username: username,
837
- targetCid: targetCid // Pass targetCid for optimization
857
+ targetCid: targetCid, // Pass targetCid for optimization
858
+ requestingUserCid: metadata?.requestingUserCid || null, // Pass for notifications
859
+ onDemand: true
838
860
  }
839
861
  );
840
862
  triggeredMessages.push(...messages);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.480",
3
+ "version": "1.0.491",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [