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
- return status;
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
- if (!actualUserId) {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.593",
3
+ "version": "1.0.595",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -1,6 +0,0 @@
1
- {
2
- "type": "module",
3
- "name": "api-v2",
4
- "version": "1.0.0",
5
- "description": "API v2 - Refactored user API with middleware architecture"
6
- }