bulltrackers-module 1.0.462 → 1.0.464

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.
@@ -1,5 +1,5 @@
1
- # Data Feeder Pipeline (V2.2 - Static Jump Fixed)
2
- # Orchestrates data fetching with UTC-alignment and Targeted Step execution.
1
+ # Data Feeder Pipeline (Final Verified Version)
2
+ # Orchestrates data fetching with UTC-alignment, Test Mode, and extended Timeouts.
3
3
 
4
4
  main:
5
5
  params: [input]
@@ -11,7 +11,7 @@ main:
11
11
  - market_date: '${text.split(time.format(sys.now()), "T")[0]}'
12
12
 
13
13
  # --- TEST MODE / SELECTIVE EXECUTION ---
14
- # Since 'next' cannot be dynamic, we map specific strings to steps.
14
+ # Static routing to allow manual testing of specific steps via Input JSON.
15
15
  - check_test_mode:
16
16
  switch:
17
17
  - condition: '${input != null and "target_step" in input}'
@@ -38,6 +38,8 @@ main:
38
38
  args:
39
39
  url: '${"https://" + location + "-" + project + ".cloudfunctions.net/price-fetcher"}'
40
40
  auth: { type: OIDC }
41
+ timeout: 300 # Fixed: Prevents timeout errors for long-running functions
42
+ retry: ${http.default_retry}
41
43
  - insights_fetch:
42
44
  steps:
43
45
  - call_insights_fetcher:
@@ -45,6 +47,8 @@ main:
45
47
  args:
46
48
  url: '${"https://" + location + "-" + project + ".cloudfunctions.net/insights-fetcher"}'
47
49
  auth: { type: OIDC }
50
+ timeout: 300 # Fixed: Prevents timeout errors for long-running functions
51
+ retry: ${http.default_retry}
48
52
 
49
53
  - index_market_data:
50
54
  call: http.post
@@ -53,8 +57,10 @@ main:
53
57
  body:
54
58
  targetDate: '${market_date}'
55
59
  auth: { type: OIDC }
60
+ timeout: 300
56
61
 
57
62
  # --- PHASE 2: ALIGN TO MIDNIGHT ---
63
+ # Dynamically calculates seconds remaining until exactly 00:00 UTC.
58
64
  - wait_for_midnight:
59
65
  assign:
60
66
  - now_sec: '${int(sys.now())}'
@@ -72,6 +78,7 @@ main:
72
78
  args:
73
79
  url: '${"https://" + location + "-" + project + ".cloudfunctions.net/fetch-popular-investors"}'
74
80
  auth: { type: OIDC }
81
+ timeout: 300
75
82
  except:
76
83
  as: e
77
84
  steps:
@@ -86,7 +93,9 @@ main:
86
93
  args:
87
94
  url: '${"https://" + location + "-" + project + ".cloudfunctions.net/social-orchestrator"}'
88
95
  auth: { type: OIDC }
96
+ timeout: 300
89
97
 
98
+ # Fixed: Split assign and call into two separate steps
90
99
  - prepare_midnight_index:
91
100
  assign:
92
101
  - current_date: '${text.split(time.format(sys.now()), "T")[0]}'
@@ -97,12 +106,14 @@ main:
97
106
  body:
98
107
  targetDate: '${current_date}'
99
108
  auth: { type: OIDC }
109
+ timeout: 300
100
110
 
101
111
  - run_global_indexer:
102
112
  call: http.post
103
113
  args:
104
114
  url: '${"https://" + location + "-" + project + ".cloudfunctions.net/root-data-indexer"}'
105
115
  auth: { type: OIDC }
116
+ timeout: 300
106
117
 
107
118
  # --- PHASE 4: RECURRING SOCIAL FETCH (UTC Aligned 3hr) ---
108
119
  - init_social_loop:
@@ -116,7 +127,7 @@ main:
116
127
  - calculate_next_window:
117
128
  assign:
118
129
  - now_sec_loop: '${int(sys.now())}'
119
- - window_size: 10800
130
+ - window_size: 10800 # Fixed: Aligns to 03:00, 06:00, 09:00 UTC...
120
131
  - sleep_loop: '${window_size - (now_sec_loop % window_size)}'
121
132
 
122
133
  - wait_for_3hr_window:
@@ -129,7 +140,9 @@ main:
129
140
  args:
130
141
  url: '${"https://" + location + "-" + project + ".cloudfunctions.net/social-orchestrator"}'
131
142
  auth: { type: OIDC }
143
+ timeout: 300
132
144
 
145
+ # Fixed: Split assign and call into two separate steps
133
146
  - prepare_recurring_index:
134
147
  assign:
135
148
  - cur_date_rec: '${text.split(time.format(sys.now()), "T")[0]}'
@@ -140,6 +153,7 @@ main:
140
153
  body:
141
154
  targetDate: '${cur_date_rec}'
142
155
  auth: { type: OIDC }
156
+ timeout: 300
143
157
 
144
158
  - increment:
145
159
  assign:
@@ -213,6 +213,30 @@ async function updateWatchlist(req, res, dependencies, config) {
213
213
  return res.status(403).json({ error: "You can only modify your own watchlists" });
214
214
  }
215
215
 
216
+ // Check if this is a copied watchlist
217
+ const isCopiedWatchlist = existingData.copiedFrom && existingData.copiedFromCreator;
218
+ const wasModified = existingData.hasBeenModified || false;
219
+
220
+ // Determine if meaningful changes are being made (not just name)
221
+ let hasMeaningfulChanges = wasModified;
222
+ if (items !== undefined && existingData.type === 'static') {
223
+ // Check if items actually changed
224
+ const itemsChanged = JSON.stringify(items) !== JSON.stringify(existingData.items || []);
225
+ hasMeaningfulChanges = hasMeaningfulChanges || itemsChanged;
226
+ }
227
+ if (dynamicConfig !== undefined && existingData.type === 'dynamic') {
228
+ // Check if dynamicConfig actually changed
229
+ const configChanged = JSON.stringify(dynamicConfig) !== JSON.stringify(existingData.dynamicConfig || {});
230
+ hasMeaningfulChanges = hasMeaningfulChanges || configChanged;
231
+ }
232
+
233
+ // If trying to make a copied watchlist public, check if meaningful changes were made
234
+ if (visibility === 'public' && isCopiedWatchlist && !hasMeaningfulChanges) {
235
+ return res.status(400).json({
236
+ error: "Cannot publish copied watchlist without making meaningful changes. Please modify the watchlist items, thresholds, or parameters before publishing."
237
+ });
238
+ }
239
+
216
240
  const updates = {
217
241
  updatedAt: FieldValue.serverTimestamp()
218
242
  };
@@ -221,6 +245,11 @@ async function updateWatchlist(req, res, dependencies, config) {
221
245
  updates.name = name.trim();
222
246
  }
223
247
 
248
+ // Track if watchlist has been modified (for copied watchlists)
249
+ if (isCopiedWatchlist && hasMeaningfulChanges) {
250
+ updates.hasBeenModified = true;
251
+ }
252
+
224
253
  if (visibility !== undefined) {
225
254
  if (visibility !== 'public' && visibility !== 'private') {
226
255
  return res.status(400).json({ error: "Visibility must be 'public' or 'private'" });
@@ -235,7 +264,7 @@ async function updateWatchlist(req, res, dependencies, config) {
235
264
  createdBy: existingData.createdBy,
236
265
  name: updates.name || existingData.name,
237
266
  type: existingData.type,
238
- description: dynamicConfig?.description || '',
267
+ description: (dynamicConfig?.description || existingData.dynamicConfig?.description || ''),
239
268
  copyCount: existingData.copyCount || 0,
240
269
  createdAt: existingData.createdAt,
241
270
  updatedAt: FieldValue.serverTimestamp()
@@ -345,6 +374,8 @@ async function copyWatchlist(req, res, dependencies, config) {
345
374
  return res.status(400).json({ error: "Missing userCid or watchlist id" });
346
375
  }
347
376
 
377
+ const userCidNum = Number(userCid);
378
+
348
379
  try {
349
380
  // First, try to find in public watchlists
350
381
  const publicRef = db.collection('public_watchlists').doc(id);
@@ -355,10 +386,11 @@ async function copyWatchlist(req, res, dependencies, config) {
355
386
  }
356
387
 
357
388
  const publicData = publicDoc.data();
389
+ const originalCreatorCid = Number(publicData.createdBy);
358
390
 
359
391
  // Find the original watchlist
360
392
  const originalRef = db.collection(config.watchlistsCollection || 'watchlists')
361
- .doc(String(publicData.createdBy))
393
+ .doc(String(originalCreatorCid))
362
394
  .collection('lists')
363
395
  .doc(id);
364
396
 
@@ -370,15 +402,52 @@ async function copyWatchlist(req, res, dependencies, config) {
370
402
 
371
403
  const originalData = originalDoc.data();
372
404
 
405
+ // Check if user is copying their own watchlist
406
+ const isCopyingOwn = originalCreatorCid === userCidNum;
407
+
408
+ // If copying own watchlist, find existing copies to determine number
409
+ let copyNumber = 1;
410
+ let newName = name;
411
+
412
+ if (isCopyingOwn) {
413
+ // Get all watchlists by this user to find existing copies
414
+ const userWatchlistsRef = db.collection(config.watchlistsCollection || 'watchlists')
415
+ .doc(String(userCidNum))
416
+ .collection('lists');
417
+
418
+ const userWatchlistsSnapshot = await userWatchlistsRef.get();
419
+ const baseName = originalData.name.replace(/\s*#\d+$/, ''); // Remove existing #N suffix
420
+
421
+ // Count existing copies (including original)
422
+ const existingCopies = [];
423
+ userWatchlistsSnapshot.forEach(doc => {
424
+ const data = doc.data();
425
+ const docName = data.name.replace(/\s*#\d+$/, '');
426
+ if (docName === baseName || docName === originalData.name) {
427
+ existingCopies.push(data);
428
+ }
429
+ });
430
+
431
+ // Determine next copy number
432
+ copyNumber = existingCopies.length + 1;
433
+ newName = name || `${baseName} #${copyNumber}`;
434
+ } else {
435
+ // Copying someone else's watchlist
436
+ newName = name || `${originalData.name} (Copy)`;
437
+ }
438
+
373
439
  // Create new watchlist for the copying user
374
440
  const newWatchlistId = generateWatchlistId();
375
441
  const watchlistData = {
376
442
  ...originalData,
377
443
  id: newWatchlistId,
378
- name: name || `${originalData.name} (Copy)`,
379
- createdBy: Number(userCid),
444
+ name: newName,
445
+ createdBy: userCidNum,
380
446
  visibility: 'private', // Copied watchlists are always private
381
447
  copiedFrom: id,
448
+ copiedFromCreator: originalCreatorCid,
449
+ originalName: originalData.name, // Store original name for comparison
450
+ hasBeenModified: false, // Track if user made meaningful changes
382
451
  createdAt: FieldValue.serverTimestamp(),
383
452
  updatedAt: FieldValue.serverTimestamp(),
384
453
  isAutoGenerated: false
@@ -388,19 +457,21 @@ async function copyWatchlist(req, res, dependencies, config) {
388
457
  delete watchlistData.copyCount;
389
458
 
390
459
  const newWatchlistRef = db.collection(config.watchlistsCollection || 'watchlists')
391
- .doc(String(userCid))
460
+ .doc(String(userCidNum))
392
461
  .collection('lists')
393
462
  .doc(newWatchlistId);
394
463
 
395
464
  await newWatchlistRef.set(watchlistData);
396
465
 
397
- // Increment copy count on original
398
- await publicRef.update({
399
- copyCount: FieldValue.increment(1),
400
- updatedAt: FieldValue.serverTimestamp()
401
- });
466
+ // Increment copy count on original (only if copying someone else's)
467
+ if (!isCopyingOwn) {
468
+ await publicRef.update({
469
+ copyCount: FieldValue.increment(1),
470
+ updatedAt: FieldValue.serverTimestamp()
471
+ });
472
+ }
402
473
 
403
- logger.log('SUCCESS', `[copyWatchlist] User ${userCid} copied watchlist ${id} as ${newWatchlistId}`);
474
+ logger.log('SUCCESS', `[copyWatchlist] User ${userCid} copied watchlist ${id} as ${newWatchlistId}${isCopyingOwn ? ' (own watchlist)' : ''}`);
404
475
 
405
476
  return res.status(201).json({
406
477
  success: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.462",
3
+ "version": "1.0.464",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [