bulltrackers-module 1.0.593 → 1.0.595
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.
|
@@ -1875,10 +1875,82 @@ const checkDataStatus = async (db, userId) => {
|
|
|
1875
1875
|
const snap = await db.collection('SignedInUsers').doc(userId).collection(type).doc('latest').get();
|
|
1876
1876
|
status[type] = {
|
|
1877
1877
|
exists: snap.exists,
|
|
1878
|
-
updatedAt: snap.exists ? snap.updateTime.toDate() : null
|
|
1878
|
+
updatedAt: snap.exists ? snap.updateTime.toDate() : null,
|
|
1879
|
+
// Frontend-compatible fields
|
|
1880
|
+
available: snap.exists,
|
|
1881
|
+
lastUpdated: snap.exists ? snap.updateTime.toDate().toISOString() : null
|
|
1879
1882
|
};
|
|
1880
1883
|
}
|
|
1881
|
-
|
|
1884
|
+
|
|
1885
|
+
// Find the latest computation date for profile metrics (7-day lookback)
|
|
1886
|
+
const lookbackDays = 7;
|
|
1887
|
+
const today = new Date();
|
|
1888
|
+
let computationDate = null;
|
|
1889
|
+
let fallbackWindowExhausted = false;
|
|
1890
|
+
|
|
1891
|
+
// Determine which computation to check based on user type
|
|
1892
|
+
let computationName = 'SignedInUserProfileMetrics';
|
|
1893
|
+
try {
|
|
1894
|
+
await fetchPopularInvestorMasterList(db, userId);
|
|
1895
|
+
computationName = 'SignedInUserPIPersonalizedMetrics';
|
|
1896
|
+
} catch (e) {
|
|
1897
|
+
// User is not a PI, use default
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1900
|
+
// Check for computation results in the last 7 days
|
|
1901
|
+
for (let i = 0; i < lookbackDays; i++) {
|
|
1902
|
+
const checkDate = new Date(today);
|
|
1903
|
+
checkDate.setDate(checkDate.getDate() - i);
|
|
1904
|
+
const dateStr = checkDate.toISOString().split('T')[0];
|
|
1905
|
+
|
|
1906
|
+
try {
|
|
1907
|
+
const pageRef = db.collection('unified_insights')
|
|
1908
|
+
.doc(dateStr)
|
|
1909
|
+
.collection('results')
|
|
1910
|
+
.doc('popular-investor')
|
|
1911
|
+
.collection('computations')
|
|
1912
|
+
.doc(computationName)
|
|
1913
|
+
.collection('pages')
|
|
1914
|
+
.doc(String(userId));
|
|
1915
|
+
|
|
1916
|
+
const pageSnap = await pageRef.get();
|
|
1917
|
+
if (pageSnap.exists) {
|
|
1918
|
+
computationDate = dateStr;
|
|
1919
|
+
break;
|
|
1920
|
+
}
|
|
1921
|
+
} catch (error) {
|
|
1922
|
+
// Continue checking other dates
|
|
1923
|
+
console.error(`Error checking computation for ${dateStr}:`, error);
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
|
|
1927
|
+
// If no computation found in 7 days, mark fallback window as exhausted
|
|
1928
|
+
if (!computationDate) {
|
|
1929
|
+
fallbackWindowExhausted = true;
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
// Return frontend-compatible format
|
|
1933
|
+
return {
|
|
1934
|
+
portfolio: {
|
|
1935
|
+
...status.portfolio,
|
|
1936
|
+
available: status.portfolio?.exists || false,
|
|
1937
|
+
lastUpdated: status.portfolio?.updatedAt ? status.portfolio.updatedAt.toISOString() : null
|
|
1938
|
+
},
|
|
1939
|
+
tradeHistory: {
|
|
1940
|
+
...status.tradeHistory,
|
|
1941
|
+
available: status.tradeHistory?.exists || false,
|
|
1942
|
+
lastUpdated: status.tradeHistory?.updatedAt ? status.tradeHistory.updatedAt.toISOString() : null
|
|
1943
|
+
},
|
|
1944
|
+
posts: {
|
|
1945
|
+
...status.posts,
|
|
1946
|
+
available: status.posts?.exists || false,
|
|
1947
|
+
lastUpdated: status.posts?.updatedAt ? status.posts.updatedAt.toISOString() : null
|
|
1948
|
+
},
|
|
1949
|
+
// Top-level fields for frontend
|
|
1950
|
+
portfolioAvailable: status.portfolio?.exists || false,
|
|
1951
|
+
computationDate: computationDate,
|
|
1952
|
+
fallbackWindowExhausted: fallbackWindowExhausted
|
|
1953
|
+
};
|
|
1882
1954
|
};
|
|
1883
1955
|
|
|
1884
1956
|
const sendTestAlert = async (db, userId, payload) => {
|
|
@@ -1,18 +1,52 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Middleware to resolve the effective user ID, handling Developer Impersonation.
|
|
3
3
|
* Sets req.targetUserId, req.isImpersonating, and req.actualUserId.
|
|
4
|
+
*
|
|
5
|
+
* Public routes (that don't require authentication):
|
|
6
|
+
* - /watchlists/public
|
|
7
|
+
* - /popular-investors/trending
|
|
8
|
+
* - /popular-investors/categories
|
|
9
|
+
* - /popular-investors/master-list
|
|
10
|
+
* - /popular-investors/search
|
|
4
11
|
*/
|
|
5
12
|
const { isDeveloper } = require('../helpers/data-fetchers/firestore.js'); // Using your provided helper
|
|
6
13
|
|
|
14
|
+
// List of public routes that don't require userCid
|
|
15
|
+
const PUBLIC_ROUTES = [
|
|
16
|
+
'/watchlists/public',
|
|
17
|
+
'/popular-investors/trending',
|
|
18
|
+
'/popular-investors/categories',
|
|
19
|
+
'/popular-investors/master-list',
|
|
20
|
+
'/popular-investors/search'
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
const isPublicRoute = (path, originalUrl) => {
|
|
24
|
+
// Check both the path and originalUrl to handle Express routing
|
|
25
|
+
const fullPath = originalUrl || path;
|
|
26
|
+
return PUBLIC_ROUTES.some(route => fullPath.includes(route));
|
|
27
|
+
};
|
|
28
|
+
|
|
7
29
|
const resolveUserIdentity = async (req, res, next) => {
|
|
8
30
|
try {
|
|
31
|
+
// Check if this is a public route (check both path and originalUrl for Express routing)
|
|
32
|
+
const isPublic = isPublicRoute(req.path, req.originalUrl);
|
|
33
|
+
|
|
9
34
|
// 1. Identify the actual authenticated user (from Auth middleware or params)
|
|
10
35
|
const actualUserId = req.query.userCid || req.body.userCid || req.headers['x-user-cid'];
|
|
11
36
|
|
|
12
|
-
|
|
37
|
+
// For public routes, userCid is optional
|
|
38
|
+
if (!actualUserId && !isPublic) {
|
|
13
39
|
return res.status(400).json({ error: "Missing user identification (userCid)" });
|
|
14
40
|
}
|
|
15
41
|
|
|
42
|
+
// If no user ID provided and it's a public route, skip identity resolution
|
|
43
|
+
if (!actualUserId && isPublic) {
|
|
44
|
+
req.actualUserId = null;
|
|
45
|
+
req.targetUserId = null;
|
|
46
|
+
req.isImpersonating = false;
|
|
47
|
+
return next();
|
|
48
|
+
}
|
|
49
|
+
|
|
16
50
|
// 2. Check for Impersonation Request (Headers or Query)
|
|
17
51
|
const impersonateId = req.headers['x-impersonate-cid'] || req.query.impersonateCid;
|
|
18
52
|
|
package/package.json
CHANGED