bulltrackers-module 1.0.594 → 1.0.596
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.
|
@@ -1517,7 +1517,7 @@ const finalizeVerification = async (db, pubsub, userId, username) => {
|
|
|
1517
1517
|
isOptOut
|
|
1518
1518
|
});
|
|
1519
1519
|
|
|
1520
|
-
// 2. Create/Update User Doc in SignedInUsers
|
|
1520
|
+
// 2. Create/Update User Doc in SignedInUsers (legacy location - for backward compatibility)
|
|
1521
1521
|
// Note: We use the realCID as the document ID, matching the generic-api logic
|
|
1522
1522
|
await db.collection(signedInUsersCollection).doc(String(realCID)).set({
|
|
1523
1523
|
username: profileData.username,
|
|
@@ -1529,7 +1529,30 @@ const finalizeVerification = async (db, pubsub, userId, username) => {
|
|
|
1529
1529
|
lastLogin: FieldValue.serverTimestamp()
|
|
1530
1530
|
}, { merge: true });
|
|
1531
1531
|
|
|
1532
|
-
// 3.
|
|
1532
|
+
// 3. Create/Update verification data in new format location: /SignedInUsers/{cid}/verification/data
|
|
1533
|
+
const verificationDataRef = db.collection('SignedInUsers').doc(String(realCID)).collection('verification').doc('data');
|
|
1534
|
+
const existingVerificationDoc = await verificationDataRef.get();
|
|
1535
|
+
|
|
1536
|
+
let emails = [];
|
|
1537
|
+
if (existingVerificationDoc.exists()) {
|
|
1538
|
+
const existingData = existingVerificationDoc.data();
|
|
1539
|
+
// Preserve existing email array if present
|
|
1540
|
+
emails = Array.isArray(existingData.email) ? existingData.email : (existingData.email ? [existingData.email] : []);
|
|
1541
|
+
}
|
|
1542
|
+
// Note: Email will be added by frontend completeAccountSetup when Firebase Auth email is available
|
|
1543
|
+
|
|
1544
|
+
await verificationDataRef.set({
|
|
1545
|
+
etoroUsername: profileData.username,
|
|
1546
|
+
etoroCID: realCID,
|
|
1547
|
+
email: emails, // Array - will be populated by frontend
|
|
1548
|
+
displayName: `${profileData.firstName || ''} ${profileData.lastName || ''}`.trim(),
|
|
1549
|
+
photoURL: profileData.avatars?.find(a => a.type === 'Original')?.url || null,
|
|
1550
|
+
verifiedAt: FieldValue.serverTimestamp(),
|
|
1551
|
+
accountSetupComplete: false, // Will be set to true by frontend completeAccountSetup
|
|
1552
|
+
createdAt: FieldValue.serverTimestamp(),
|
|
1553
|
+
}, { merge: true });
|
|
1554
|
+
|
|
1555
|
+
// 4. Trigger Downstream Systems via Task Engine
|
|
1533
1556
|
if (pubsub) {
|
|
1534
1557
|
try {
|
|
1535
1558
|
// Replicating the 'unifiedTask' payload from generic-api
|
|
@@ -1875,10 +1898,82 @@ const checkDataStatus = async (db, userId) => {
|
|
|
1875
1898
|
const snap = await db.collection('SignedInUsers').doc(userId).collection(type).doc('latest').get();
|
|
1876
1899
|
status[type] = {
|
|
1877
1900
|
exists: snap.exists,
|
|
1878
|
-
updatedAt: snap.exists ? snap.updateTime.toDate() : null
|
|
1901
|
+
updatedAt: snap.exists ? snap.updateTime.toDate() : null,
|
|
1902
|
+
// Frontend-compatible fields
|
|
1903
|
+
available: snap.exists,
|
|
1904
|
+
lastUpdated: snap.exists ? snap.updateTime.toDate().toISOString() : null
|
|
1879
1905
|
};
|
|
1880
1906
|
}
|
|
1881
|
-
|
|
1907
|
+
|
|
1908
|
+
// Find the latest computation date for profile metrics (7-day lookback)
|
|
1909
|
+
const lookbackDays = 7;
|
|
1910
|
+
const today = new Date();
|
|
1911
|
+
let computationDate = null;
|
|
1912
|
+
let fallbackWindowExhausted = false;
|
|
1913
|
+
|
|
1914
|
+
// Determine which computation to check based on user type
|
|
1915
|
+
let computationName = 'SignedInUserProfileMetrics';
|
|
1916
|
+
try {
|
|
1917
|
+
await fetchPopularInvestorMasterList(db, userId);
|
|
1918
|
+
computationName = 'SignedInUserPIPersonalizedMetrics';
|
|
1919
|
+
} catch (e) {
|
|
1920
|
+
// User is not a PI, use default
|
|
1921
|
+
}
|
|
1922
|
+
|
|
1923
|
+
// Check for computation results in the last 7 days
|
|
1924
|
+
for (let i = 0; i < lookbackDays; i++) {
|
|
1925
|
+
const checkDate = new Date(today);
|
|
1926
|
+
checkDate.setDate(checkDate.getDate() - i);
|
|
1927
|
+
const dateStr = checkDate.toISOString().split('T')[0];
|
|
1928
|
+
|
|
1929
|
+
try {
|
|
1930
|
+
const pageRef = db.collection('unified_insights')
|
|
1931
|
+
.doc(dateStr)
|
|
1932
|
+
.collection('results')
|
|
1933
|
+
.doc('popular-investor')
|
|
1934
|
+
.collection('computations')
|
|
1935
|
+
.doc(computationName)
|
|
1936
|
+
.collection('pages')
|
|
1937
|
+
.doc(String(userId));
|
|
1938
|
+
|
|
1939
|
+
const pageSnap = await pageRef.get();
|
|
1940
|
+
if (pageSnap.exists) {
|
|
1941
|
+
computationDate = dateStr;
|
|
1942
|
+
break;
|
|
1943
|
+
}
|
|
1944
|
+
} catch (error) {
|
|
1945
|
+
// Continue checking other dates
|
|
1946
|
+
console.error(`Error checking computation for ${dateStr}:`, error);
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
// If no computation found in 7 days, mark fallback window as exhausted
|
|
1951
|
+
if (!computationDate) {
|
|
1952
|
+
fallbackWindowExhausted = true;
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1955
|
+
// Return frontend-compatible format
|
|
1956
|
+
return {
|
|
1957
|
+
portfolio: {
|
|
1958
|
+
...status.portfolio,
|
|
1959
|
+
available: status.portfolio?.exists || false,
|
|
1960
|
+
lastUpdated: status.portfolio?.updatedAt ? status.portfolio.updatedAt.toISOString() : null
|
|
1961
|
+
},
|
|
1962
|
+
tradeHistory: {
|
|
1963
|
+
...status.tradeHistory,
|
|
1964
|
+
available: status.tradeHistory?.exists || false,
|
|
1965
|
+
lastUpdated: status.tradeHistory?.updatedAt ? status.tradeHistory.updatedAt.toISOString() : null
|
|
1966
|
+
},
|
|
1967
|
+
posts: {
|
|
1968
|
+
...status.posts,
|
|
1969
|
+
available: status.posts?.exists || false,
|
|
1970
|
+
lastUpdated: status.posts?.updatedAt ? status.posts.updatedAt.toISOString() : null
|
|
1971
|
+
},
|
|
1972
|
+
// Top-level fields for frontend
|
|
1973
|
+
portfolioAvailable: status.portfolio?.exists || false,
|
|
1974
|
+
computationDate: computationDate,
|
|
1975
|
+
fallbackWindowExhausted: fallbackWindowExhausted
|
|
1976
|
+
};
|
|
1882
1977
|
};
|
|
1883
1978
|
|
|
1884
1979
|
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
|
|