bulltrackers-module 1.0.458 → 1.0.460

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.
@@ -135,12 +135,34 @@ async function hasUserCopied(db, userCid, piCid, config) {
135
135
  const userDoc = await db.collection(signedInUsersCollection).doc(String(userCid)).get();
136
136
  if (userDoc.exists) {
137
137
  const data = userDoc.data();
138
+
139
+ // Check AggregatedMirrors (current copies)
138
140
  if (data.AggregatedMirrors && Array.isArray(data.AggregatedMirrors)) {
139
141
  const isCopying = data.AggregatedMirrors.some(m => Number(m.ParentCID) === piCidNum);
140
- if (isCopying) return true;
142
+ if (isCopying) {
143
+ console.log(`[hasUserCopied] Found PI ${piCidNum} in AggregatedMirrors for user ${userCid}`);
144
+ return true;
145
+ }
146
+ }
147
+
148
+ // Also check historical mirrors if available
149
+ if (data.snapshots && typeof data.snapshots === 'object') {
150
+ // Check recent snapshots for any copy history
151
+ const snapshotDates = Object.keys(data.snapshots).sort().reverse().slice(0, 30); // Last 30 days
152
+ for (const date of snapshotDates) {
153
+ const snapshot = data.snapshots[date];
154
+ if (snapshot && snapshot.AggregatedMirrors && Array.isArray(snapshot.AggregatedMirrors)) {
155
+ const wasCopying = snapshot.AggregatedMirrors.some(m => Number(m.ParentCID) === piCidNum);
156
+ if (wasCopying) {
157
+ console.log(`[hasUserCopied] Found PI ${piCidNum} in historical snapshot ${date} for user ${userCid}`);
158
+ return true;
159
+ }
160
+ }
161
+ }
141
162
  }
142
163
  }
143
164
 
165
+ console.log(`[hasUserCopied] User ${userCid} has not copied PI ${piCidNum} (checked computation and portfolio)`);
144
166
  return false;
145
167
  } catch (error) {
146
168
  console.error('[hasUserCopied] Error checking copy status:', error);
@@ -435,6 +457,8 @@ async function checkReviewEligibility(req, res, dependencies, config) {
435
457
 
436
458
  const canReview = await hasUserCopied(db, effectiveCid, piCid, config);
437
459
 
460
+ logger.log('INFO', `[checkReviewEligibility] User ${effectiveCid} eligibility for PI ${piCid}: ${canReview}`);
461
+
438
462
  return res.status(200).json({
439
463
  piCid: Number(piCid),
440
464
  eligible: canReview,
@@ -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: latestRequest.completedAt?.toDate?.()?.toISOString() || null,
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.458",
3
+ "version": "1.0.460",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [