bulltrackers-module 1.0.561 → 1.0.563
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.
|
@@ -628,6 +628,267 @@ const createAdminRouter = (config, dependencies, unifiedCalculations) => {
|
|
|
628
628
|
}
|
|
629
629
|
});
|
|
630
630
|
|
|
631
|
+
// --- 10. DEVELOPER USER MANAGEMENT (Force Sync & Lookup) ---
|
|
632
|
+
// Only accessible to developer accounts
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* POST /admin/users/:cid/sync
|
|
636
|
+
* Force sync a signed-in user (developer only)
|
|
637
|
+
* Allows developers to trigger sync for any user to fix stuck accounts
|
|
638
|
+
*/
|
|
639
|
+
router.post('/users/:cid/sync', async (req, res) => {
|
|
640
|
+
const { cid } = req.params;
|
|
641
|
+
const { userCid: developerCid } = req.query; // Developer's CID
|
|
642
|
+
|
|
643
|
+
if (!developerCid) {
|
|
644
|
+
return res.status(400).json({
|
|
645
|
+
success: false,
|
|
646
|
+
error: "Missing userCid",
|
|
647
|
+
message: "Please provide userCid query parameter (your developer CID)"
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// SECURITY: Only allow developer accounts
|
|
652
|
+
const { isDeveloperAccount } = require('../user-api/helpers/dev/dev_helpers');
|
|
653
|
+
if (!isDeveloperAccount(Number(developerCid))) {
|
|
654
|
+
logger.log('WARN', `[AdminAPI] Unauthorized sync attempt by non-developer ${developerCid}`);
|
|
655
|
+
return res.status(403).json({
|
|
656
|
+
success: false,
|
|
657
|
+
error: "Forbidden",
|
|
658
|
+
message: "This endpoint is only available for developer accounts"
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
const targetCidNum = Number(cid);
|
|
663
|
+
if (isNaN(targetCidNum) || targetCidNum <= 0) {
|
|
664
|
+
return res.status(400).json({
|
|
665
|
+
success: false,
|
|
666
|
+
error: "Invalid CID",
|
|
667
|
+
message: "Please provide a valid user CID"
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
try {
|
|
672
|
+
// Import sync helper
|
|
673
|
+
const { requestUserSync } = require('../user-api/helpers/sync/user_sync_helpers');
|
|
674
|
+
|
|
675
|
+
// Create a mock request object with the target CID
|
|
676
|
+
const mockReq = {
|
|
677
|
+
params: { userCid: String(targetCidNum) },
|
|
678
|
+
query: { userCid: String(developerCid), sourcePage: 'admin' },
|
|
679
|
+
headers: {},
|
|
680
|
+
body: {}
|
|
681
|
+
};
|
|
682
|
+
|
|
683
|
+
// Call the sync function
|
|
684
|
+
await requestUserSync(mockReq, res, dependencies, config);
|
|
685
|
+
|
|
686
|
+
logger.log('INFO', `[AdminAPI] Developer ${developerCid} force-synced user ${targetCidNum}`);
|
|
687
|
+
|
|
688
|
+
} catch (error) {
|
|
689
|
+
logger.log('ERROR', `[AdminAPI] Error force-syncing user ${targetCidNum}`, error);
|
|
690
|
+
return res.status(500).json({
|
|
691
|
+
success: false,
|
|
692
|
+
error: "Internal server error",
|
|
693
|
+
message: error.message
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
/**
|
|
699
|
+
* GET /admin/users/:cid/lookup
|
|
700
|
+
* Look up user information by CID (developer only)
|
|
701
|
+
* Helps developers find users to sync
|
|
702
|
+
*/
|
|
703
|
+
router.get('/users/:cid/lookup', async (req, res) => {
|
|
704
|
+
const { cid } = req.params;
|
|
705
|
+
const { userCid: developerCid } = req.query; // Developer's CID
|
|
706
|
+
|
|
707
|
+
if (!developerCid) {
|
|
708
|
+
return res.status(400).json({
|
|
709
|
+
success: false,
|
|
710
|
+
error: "Missing userCid",
|
|
711
|
+
message: "Please provide userCid query parameter (your developer CID)"
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// SECURITY: Only allow developer accounts
|
|
716
|
+
const { isDeveloperAccount } = require('../user-api/helpers/dev/dev_helpers');
|
|
717
|
+
if (!isDeveloperAccount(Number(developerCid))) {
|
|
718
|
+
logger.log('WARN', `[AdminAPI] Unauthorized lookup attempt by non-developer ${developerCid}`);
|
|
719
|
+
return res.status(403).json({
|
|
720
|
+
success: false,
|
|
721
|
+
error: "Forbidden",
|
|
722
|
+
message: "This endpoint is only available for developer accounts"
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
const targetCidNum = Number(cid);
|
|
727
|
+
if (isNaN(targetCidNum) || targetCidNum <= 0) {
|
|
728
|
+
return res.status(400).json({
|
|
729
|
+
success: false,
|
|
730
|
+
error: "Invalid CID",
|
|
731
|
+
message: "Please provide a valid user CID"
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
try {
|
|
736
|
+
const signedInUsersCollection = config.signedInUsersCollection || 'signed_in_users';
|
|
737
|
+
const { checkIfUserIsPI } = require('../user-api/helpers/core/user_status_helpers');
|
|
738
|
+
|
|
739
|
+
// Check if user is a PI
|
|
740
|
+
const rankEntry = await checkIfUserIsPI(db, targetCidNum, config, logger);
|
|
741
|
+
const isPI = !!rankEntry;
|
|
742
|
+
|
|
743
|
+
// Get signed-in user data
|
|
744
|
+
const userDoc = await db.collection(signedInUsersCollection).doc(String(targetCidNum)).get();
|
|
745
|
+
const userData = userDoc.exists ? userDoc.data() : null;
|
|
746
|
+
|
|
747
|
+
// Get verification data
|
|
748
|
+
const verificationsCollection = config.verificationsCollection || 'verifications';
|
|
749
|
+
let verificationData = null;
|
|
750
|
+
if (userData && userData.username) {
|
|
751
|
+
const verificationDoc = await db.collection(verificationsCollection)
|
|
752
|
+
.doc(userData.username.toLowerCase())
|
|
753
|
+
.get();
|
|
754
|
+
if (verificationDoc.exists) {
|
|
755
|
+
verificationData = verificationDoc.data();
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
// Get latest sync request status
|
|
760
|
+
const syncRequestsRef = db.collection('user_sync_requests')
|
|
761
|
+
.doc(String(targetCidNum))
|
|
762
|
+
.collection('requests')
|
|
763
|
+
.orderBy('createdAt', 'desc')
|
|
764
|
+
.limit(1);
|
|
765
|
+
|
|
766
|
+
const latestSyncSnapshot = await syncRequestsRef.get();
|
|
767
|
+
let latestSync = null;
|
|
768
|
+
if (!latestSyncSnapshot.empty) {
|
|
769
|
+
latestSync = {
|
|
770
|
+
requestId: latestSyncSnapshot.docs[0].id,
|
|
771
|
+
...latestSyncSnapshot.docs[0].data()
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// Get data status directly (don't use the endpoint wrapper)
|
|
776
|
+
let dataStatus = null;
|
|
777
|
+
try {
|
|
778
|
+
const { signedInUsersCollection, signedInHistoryCollection } = config;
|
|
779
|
+
const { findLatestPortfolioDate } = require('../user-api/helpers/core/data_lookup_helpers');
|
|
780
|
+
const CANARY_BLOCK_ID = '19M';
|
|
781
|
+
const today = new Date().toISOString().split('T')[0];
|
|
782
|
+
|
|
783
|
+
// Check portfolio
|
|
784
|
+
const portfolioDate = await findLatestPortfolioDate(db, signedInUsersCollection, targetCidNum, 30);
|
|
785
|
+
const portfolioExists = !!portfolioDate;
|
|
786
|
+
const isPortfolioFallback = portfolioDate && portfolioDate !== today;
|
|
787
|
+
|
|
788
|
+
// Check history
|
|
789
|
+
let historyExists = false;
|
|
790
|
+
let historyDate = null;
|
|
791
|
+
let isHistoryFallback = false;
|
|
792
|
+
|
|
793
|
+
const historyCollection = signedInHistoryCollection || 'signed_in_user_history';
|
|
794
|
+
const todayHistoryRef = db.collection(historyCollection)
|
|
795
|
+
.doc(CANARY_BLOCK_ID)
|
|
796
|
+
.collection('snapshots')
|
|
797
|
+
.doc(today)
|
|
798
|
+
.collection('parts');
|
|
799
|
+
|
|
800
|
+
const todayHistorySnapshot = await todayHistoryRef.get();
|
|
801
|
+
|
|
802
|
+
if (!todayHistorySnapshot.empty) {
|
|
803
|
+
historyExists = true;
|
|
804
|
+
historyDate = today;
|
|
805
|
+
} else {
|
|
806
|
+
// Check fallback dates
|
|
807
|
+
for (let i = 1; i <= 30; i++) {
|
|
808
|
+
const checkDate = new Date(today);
|
|
809
|
+
checkDate.setDate(checkDate.getDate() - i);
|
|
810
|
+
const dateStr = checkDate.toISOString().split('T')[0];
|
|
811
|
+
|
|
812
|
+
const historyRef = db.collection(historyCollection)
|
|
813
|
+
.doc(CANARY_BLOCK_ID)
|
|
814
|
+
.collection('snapshots')
|
|
815
|
+
.doc(dateStr)
|
|
816
|
+
.collection('parts');
|
|
817
|
+
|
|
818
|
+
const snapshot = await historyRef.get();
|
|
819
|
+
if (!snapshot.empty) {
|
|
820
|
+
// Check if this user's data exists in any part
|
|
821
|
+
for (const partDoc of snapshot.docs) {
|
|
822
|
+
const partData = partDoc.data();
|
|
823
|
+
if (partData.users && partData.users[String(targetCidNum)]) {
|
|
824
|
+
historyExists = true;
|
|
825
|
+
historyDate = dateStr;
|
|
826
|
+
isHistoryFallback = true;
|
|
827
|
+
break;
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
if (historyExists) break;
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
dataStatus = {
|
|
836
|
+
portfolioAvailable: portfolioExists,
|
|
837
|
+
historyAvailable: historyExists,
|
|
838
|
+
date: portfolioDate || today,
|
|
839
|
+
portfolioDate: portfolioDate,
|
|
840
|
+
historyDate: historyDate,
|
|
841
|
+
isPortfolioFallback: isPortfolioFallback,
|
|
842
|
+
isHistoryFallback: isHistoryFallback,
|
|
843
|
+
requestedDate: today,
|
|
844
|
+
userCid: String(targetCidNum)
|
|
845
|
+
};
|
|
846
|
+
} catch (e) {
|
|
847
|
+
logger.log('WARN', `[AdminAPI] Error getting data status: ${e.message}`);
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
const result = {
|
|
851
|
+
success: true,
|
|
852
|
+
cid: targetCidNum,
|
|
853
|
+
isPI: isPI,
|
|
854
|
+
userData: userData ? {
|
|
855
|
+
username: userData.username,
|
|
856
|
+
fullName: userData.fullName,
|
|
857
|
+
avatar: userData.avatar,
|
|
858
|
+
verifiedAt: userData.verifiedAt,
|
|
859
|
+
lastLogin: userData.lastLogin,
|
|
860
|
+
isOptOut: userData.isOptOut
|
|
861
|
+
} : null,
|
|
862
|
+
piData: rankEntry ? {
|
|
863
|
+
username: rankEntry.UserName || rankEntry.username,
|
|
864
|
+
aum: rankEntry.AUMValue,
|
|
865
|
+
copiers: rankEntry.Copiers,
|
|
866
|
+
riskScore: rankEntry.RiskScore,
|
|
867
|
+
gain: rankEntry.Gain
|
|
868
|
+
} : null,
|
|
869
|
+
verification: verificationData ? {
|
|
870
|
+
status: verificationData.status,
|
|
871
|
+
verifiedAt: verificationData.verifiedAt,
|
|
872
|
+
isOptOut: verificationData.isOptOut
|
|
873
|
+
} : null,
|
|
874
|
+
latestSync: latestSync,
|
|
875
|
+
dataStatus: dataStatus
|
|
876
|
+
};
|
|
877
|
+
|
|
878
|
+
logger.log('INFO', `[AdminAPI] Developer ${developerCid} looked up user ${targetCidNum}`);
|
|
879
|
+
|
|
880
|
+
return res.status(200).json(result);
|
|
881
|
+
|
|
882
|
+
} catch (error) {
|
|
883
|
+
logger.log('ERROR', `[AdminAPI] Error looking up user ${targetCidNum}`, error);
|
|
884
|
+
return res.status(500).json({
|
|
885
|
+
success: false,
|
|
886
|
+
error: "Internal server error",
|
|
887
|
+
message: error.message
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
});
|
|
891
|
+
|
|
631
892
|
return router;
|
|
632
893
|
};
|
|
633
894
|
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
/**
|
|
7
7
|
* Find the latest available date for signed-in user portfolio data
|
|
8
8
|
* Searches backwards from today up to maxDaysBack days
|
|
9
|
+
* Checks both new root data path and legacy path for compatibility
|
|
9
10
|
* @param {Firestore} db - Firestore instance
|
|
10
11
|
* @param {string} signedInUsersCollection - Collection name (legacy)
|
|
11
12
|
* @param {string|number} userCid - User CID
|
|
@@ -13,9 +14,51 @@
|
|
|
13
14
|
* @returns {Promise<string|null>} - Date string (YYYY-MM-DD) or null if not found
|
|
14
15
|
*/
|
|
15
16
|
async function findLatestPortfolioDate(db, signedInUsersCollection, userCid, maxDaysBack = 30) {
|
|
16
|
-
const CANARY_BLOCK_ID = '19M';
|
|
17
17
|
const today = new Date();
|
|
18
|
+
const userCidStr = String(userCid);
|
|
19
|
+
|
|
20
|
+
// First, check the user-centric latest snapshot (fastest check)
|
|
21
|
+
try {
|
|
22
|
+
const latestRef = db.collection('SignedInUsers')
|
|
23
|
+
.doc(userCidStr)
|
|
24
|
+
.collection('portfolio')
|
|
25
|
+
.doc('latest');
|
|
26
|
+
|
|
27
|
+
const latestDoc = await latestRef.get();
|
|
28
|
+
if (latestDoc.exists) {
|
|
29
|
+
const data = latestDoc.data();
|
|
30
|
+
if (data && data.date) {
|
|
31
|
+
return data.date; // Found in latest snapshot
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
} catch (error) {
|
|
35
|
+
// Continue to other checks if this fails
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Second, check the new root data path: SignedInUserPortfolioData/{date}/{cid}/{cid}
|
|
39
|
+
for (let i = 0; i < maxDaysBack; i++) {
|
|
40
|
+
const checkDate = new Date(today);
|
|
41
|
+
checkDate.setDate(checkDate.getDate() - i);
|
|
42
|
+
const dateStr = checkDate.toISOString().split('T')[0];
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const rootDataRef = db.collection('SignedInUserPortfolioData')
|
|
46
|
+
.doc(dateStr)
|
|
47
|
+
.collection(userCidStr)
|
|
48
|
+
.doc(userCidStr);
|
|
49
|
+
|
|
50
|
+
const rootDataDoc = await rootDataRef.get();
|
|
51
|
+
if (rootDataDoc.exists) {
|
|
52
|
+
return dateStr; // Found data in root data collection
|
|
53
|
+
}
|
|
54
|
+
} catch (error) {
|
|
55
|
+
// Continue to next date if error
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
18
59
|
|
|
60
|
+
// Third, check legacy path for backward compatibility: {signedInUsersCollection}/19M/snapshots/{date}/parts
|
|
61
|
+
const CANARY_BLOCK_ID = '19M';
|
|
19
62
|
for (let i = 0; i < maxDaysBack; i++) {
|
|
20
63
|
const checkDate = new Date(today);
|
|
21
64
|
checkDate.setDate(checkDate.getDate() - i);
|
|
@@ -33,8 +76,8 @@ async function findLatestPortfolioDate(db, signedInUsersCollection, userCid, max
|
|
|
33
76
|
// Check if user's CID exists in any part document
|
|
34
77
|
for (const partDoc of partsSnapshot.docs) {
|
|
35
78
|
const partData = partDoc.data();
|
|
36
|
-
if (partData && partData[
|
|
37
|
-
return dateStr; // Found data for this date
|
|
79
|
+
if (partData && partData[userCidStr]) {
|
|
80
|
+
return dateStr; // Found data for this date in legacy path
|
|
38
81
|
}
|
|
39
82
|
}
|
|
40
83
|
} catch (error) {
|
|
@@ -147,64 +147,115 @@ async function getUserDataStatus(req, res, dependencies, config) {
|
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
// Check history data with fallback
|
|
150
|
+
// Check new root data path first: SignedInUsers/{cid}/tradeHistory/latest
|
|
150
151
|
let historyExists = false;
|
|
151
152
|
let historyDate = null;
|
|
152
153
|
let isHistoryFallback = false;
|
|
153
154
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
.
|
|
161
|
-
.
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
if (!todayHistorySnapshot.empty) {
|
|
166
|
-
for (const partDoc of todayHistorySnapshot.docs) {
|
|
167
|
-
const partData = partDoc.data();
|
|
168
|
-
if (partData && partData[String(userCid)]) {
|
|
155
|
+
try {
|
|
156
|
+
const latestHistoryRef = db.collection('SignedInUsers')
|
|
157
|
+
.doc(String(userCid))
|
|
158
|
+
.collection('tradeHistory')
|
|
159
|
+
.doc('latest');
|
|
160
|
+
|
|
161
|
+
const latestHistoryDoc = await latestHistoryRef.get();
|
|
162
|
+
if (latestHistoryDoc.exists) {
|
|
163
|
+
const data = latestHistoryDoc.data();
|
|
164
|
+
if (data && data.date) {
|
|
169
165
|
historyExists = true;
|
|
170
|
-
historyDate =
|
|
171
|
-
|
|
166
|
+
historyDate = data.date;
|
|
167
|
+
isHistoryFallback = data.date !== today;
|
|
168
|
+
logger.log('INFO', `[getUserDataStatus] Found history data in latest snapshot: ${historyDate}`);
|
|
172
169
|
}
|
|
173
170
|
}
|
|
171
|
+
} catch (error) {
|
|
172
|
+
// Continue to other checks if this fails
|
|
174
173
|
}
|
|
175
174
|
|
|
176
|
-
// If not found
|
|
175
|
+
// If not found in latest snapshot, check new root data path: SignedInUserTradeHistoryData/{date}/{cid}/{cid}
|
|
177
176
|
if (!historyExists) {
|
|
178
|
-
for (let i =
|
|
177
|
+
for (let i = 0; i <= 30; i++) {
|
|
179
178
|
const checkDate = new Date();
|
|
180
179
|
checkDate.setDate(checkDate.getDate() - i);
|
|
181
180
|
const dateStr = checkDate.toISOString().split('T')[0];
|
|
182
181
|
|
|
183
182
|
try {
|
|
184
|
-
const
|
|
185
|
-
.doc(CANARY_BLOCK_ID)
|
|
186
|
-
.collection('snapshots')
|
|
183
|
+
const rootHistoryRef = db.collection('SignedInUserTradeHistoryData')
|
|
187
184
|
.doc(dateStr)
|
|
188
|
-
.collection(
|
|
185
|
+
.collection(String(userCid))
|
|
186
|
+
.doc(String(userCid));
|
|
189
187
|
|
|
190
|
-
const
|
|
188
|
+
const rootHistoryDoc = await rootHistoryRef.get();
|
|
189
|
+
if (rootHistoryDoc.exists) {
|
|
190
|
+
historyExists = true;
|
|
191
|
+
historyDate = dateStr;
|
|
192
|
+
isHistoryFallback = dateStr !== today;
|
|
193
|
+
logger.log('INFO', `[getUserDataStatus] Found history data in root data collection: ${historyDate}`);
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
} catch (error) {
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// If still not found, check legacy path for backward compatibility
|
|
203
|
+
if (!historyExists) {
|
|
204
|
+
const historyCollection = signedInHistoryCollection || 'signed_in_user_history';
|
|
205
|
+
|
|
206
|
+
// Check today first
|
|
207
|
+
const todayHistoryRef = db.collection(historyCollection)
|
|
208
|
+
.doc(CANARY_BLOCK_ID)
|
|
209
|
+
.collection('snapshots')
|
|
210
|
+
.doc(today)
|
|
211
|
+
.collection('parts');
|
|
212
|
+
|
|
213
|
+
const todayHistorySnapshot = await todayHistoryRef.get();
|
|
214
|
+
|
|
215
|
+
if (!todayHistorySnapshot.empty) {
|
|
216
|
+
for (const partDoc of todayHistorySnapshot.docs) {
|
|
217
|
+
const partData = partDoc.data();
|
|
218
|
+
if (partData && partData[String(userCid)]) {
|
|
219
|
+
historyExists = true;
|
|
220
|
+
historyDate = today;
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// If not found today, search backwards
|
|
227
|
+
if (!historyExists) {
|
|
228
|
+
for (let i = 1; i <= 30; i++) {
|
|
229
|
+
const checkDate = new Date();
|
|
230
|
+
checkDate.setDate(checkDate.getDate() - i);
|
|
231
|
+
const dateStr = checkDate.toISOString().split('T')[0];
|
|
191
232
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
233
|
+
try {
|
|
234
|
+
const historyPartsRef = db.collection(historyCollection)
|
|
235
|
+
.doc(CANARY_BLOCK_ID)
|
|
236
|
+
.collection('snapshots')
|
|
237
|
+
.doc(dateStr)
|
|
238
|
+
.collection('parts');
|
|
239
|
+
|
|
240
|
+
const historyPartsSnapshot = await historyPartsRef.get();
|
|
241
|
+
|
|
242
|
+
if (!historyPartsSnapshot.empty) {
|
|
243
|
+
for (const partDoc of historyPartsSnapshot.docs) {
|
|
244
|
+
const partData = partDoc.data();
|
|
245
|
+
if (partData && partData[String(userCid)]) {
|
|
246
|
+
historyExists = true;
|
|
247
|
+
historyDate = dateStr;
|
|
248
|
+
isHistoryFallback = true;
|
|
249
|
+
logger.log('INFO', `[getUserDataStatus] Found history data in legacy fallback date ${dateStr}`);
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
201
252
|
}
|
|
202
253
|
}
|
|
254
|
+
|
|
255
|
+
if (historyExists) break;
|
|
256
|
+
} catch (error) {
|
|
257
|
+
continue;
|
|
203
258
|
}
|
|
204
|
-
|
|
205
|
-
if (historyExists) break;
|
|
206
|
-
} catch (error) {
|
|
207
|
-
continue;
|
|
208
259
|
}
|
|
209
260
|
}
|
|
210
261
|
}
|