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)
|
|
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:
|
|
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
|