bulltrackers-module 1.0.419 → 1.0.421

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.
@@ -835,55 +835,70 @@ async function autoGenerateWatchlist(req, res, dependencies, config) {
835
835
  try {
836
836
  let copiedPIs = [];
837
837
  let dataSource = 'unknown';
838
+ let isDevOverride = false;
838
839
  const today = new Date().toISOString().split('T')[0];
839
840
 
840
- // === PRIMARY: Try to fetch from computation (cheaper/faster) ===
841
- const insightsCollection = config.unifiedInsightsCollection || 'unified_insights';
842
- const category = 'signed_in_user';
843
- const computationName = 'SignedInUserCopiedPIs';
844
-
845
- // Try to find latest computation date (with fallback)
846
- const resultsSub = config.resultsSubcollection || 'results';
847
- const compsSub = config.computationsSubcollection || 'computations';
848
- const computationDate = await findLatestComputationDate(
849
- db,
850
- insightsCollection,
851
- resultsSub,
852
- compsSub,
853
- category,
854
- computationName,
855
- userCid,
856
- 30
857
- );
841
+ // === DEV OVERRIDE CHECK (for developer accounts) ===
842
+ const { getCopiedPIsWithDevOverride } = require('./dev_helpers');
843
+ const devResult = await getCopiedPIsWithDevOverride(db, userCid, config, logger);
844
+ if (devResult.isDevOverride) {
845
+ copiedPIs = devResult.copiedPIs;
846
+ dataSource = devResult.dataSource;
847
+ isDevOverride = true;
848
+ logger.log('INFO', `[autoGenerateWatchlist] Using DEV OVERRIDE for user ${userCid}, found ${copiedPIs.length} fake copied PIs`);
849
+ }
858
850
 
859
- if (computationDate) {
860
- const computationRef = db.collection(insightsCollection)
861
- .doc(computationDate)
862
- .collection('results')
863
- .doc(category)
864
- .collection('computations')
865
- .doc(computationName);
851
+ // === PRIMARY: Try to fetch from computation (cheaper/faster) ===
852
+ // Only if dev override is not active
853
+ if (!isDevOverride) {
854
+ const insightsCollection = config.unifiedInsightsCollection || 'unified_insights';
855
+ const category = 'signed_in_user';
856
+ const computationName = 'SignedInUserCopiedPIs';
866
857
 
867
- const computationDoc = await computationRef.get();
858
+ // Try to find latest computation date (with fallback)
859
+ const resultsSub = config.resultsSubcollection || 'results';
860
+ const compsSub = config.computationsSubcollection || 'computations';
861
+ const computationDate = await findLatestComputationDate(
862
+ db,
863
+ insightsCollection,
864
+ resultsSub,
865
+ compsSub,
866
+ category,
867
+ computationName,
868
+ userCid,
869
+ 30
870
+ );
868
871
 
869
- if (computationDoc.exists) {
870
- const computationData = computationDoc.data();
871
- const userResult = computationData[String(userCid)];
872
+ if (computationDate) {
873
+ const computationRef = db.collection(insightsCollection)
874
+ .doc(computationDate)
875
+ .collection('results')
876
+ .doc(category)
877
+ .collection('computations')
878
+ .doc(computationName);
872
879
 
873
- if (userResult && userResult.current && userResult.current.length > 0) {
874
- // Convert computation result to our format
875
- copiedPIs = userResult.current.map(cid => ({
876
- cid: Number(cid),
877
- username: 'Unknown' // Username not in computation, will get from rankings
878
- }));
879
- dataSource = 'computation';
880
- logger.log('INFO', `[autoGenerateWatchlist] Using computation data (date: ${computationDate}) for user ${userCid}, found ${copiedPIs.length} copied PIs`);
880
+ const computationDoc = await computationRef.get();
881
+
882
+ if (computationDoc.exists) {
883
+ const computationData = computationDoc.data();
884
+ const userResult = computationData[String(userCid)];
885
+
886
+ if (userResult && userResult.current && userResult.current.length > 0) {
887
+ // Convert computation result to our format
888
+ copiedPIs = userResult.current.map(cid => ({
889
+ cid: Number(cid),
890
+ username: 'Unknown' // Username not in computation, will get from rankings
891
+ }));
892
+ dataSource = 'computation';
893
+ logger.log('INFO', `[autoGenerateWatchlist] Using computation data (date: ${computationDate}) for user ${userCid}, found ${copiedPIs.length} copied PIs`);
894
+ }
881
895
  }
882
896
  }
883
897
  }
884
898
 
885
899
  // === FALLBACK: Read portfolio data directly if computation not available ===
886
- if (copiedPIs.length === 0) {
900
+ // Only if dev override is not active and no computation data found
901
+ if (!isDevOverride && copiedPIs.length === 0) {
887
902
  logger.log('INFO', `[autoGenerateWatchlist] Computation not available, falling back to direct portfolio read for user ${userCid}`);
888
903
 
889
904
  const { signedInUsersCollection } = config;
@@ -0,0 +1,260 @@
1
+ /**
2
+ * @fileoverview Developer Mode Helpers
3
+ * Allows specific developer accounts to override copied PI data for testing
4
+ * SECURITY: Only works for whitelisted developer CIDs
5
+ */
6
+
7
+ const { FieldValue } = require('@google-cloud/firestore');
8
+
9
+ // Whitelist of developer account CIDs that can use dev mode
10
+ const DEVELOPER_CIDS = [
11
+ 29312236 // Aiden Hawkins - marau2021
12
+ ];
13
+
14
+ /**
15
+ * Check if a user CID is a developer account
16
+ */
17
+ function isDeveloperAccount(userCid) {
18
+ return DEVELOPER_CIDS.includes(Number(userCid));
19
+ }
20
+
21
+ /**
22
+ * Get developer override data for a user
23
+ * Auto-creates default document if it doesn't exist (for developer accounts only)
24
+ * Returns null if not a developer
25
+ */
26
+ async function getDevOverride(db, userCid, config, logger = null) {
27
+ if (!isDeveloperAccount(userCid)) {
28
+ return null;
29
+ }
30
+
31
+ try {
32
+ const devOverridesCollection = config.devOverridesCollection || 'dev_overrides';
33
+ const overrideRef = db.collection(devOverridesCollection).doc(String(userCid));
34
+ const overrideDoc = await overrideRef.get();
35
+
36
+ // Auto-create default document if it doesn't exist
37
+ if (!overrideDoc.exists) {
38
+ const defaultData = {
39
+ userCid: Number(userCid),
40
+ enabled: false,
41
+ fakeCopiedPIs: [],
42
+ createdAt: FieldValue.serverTimestamp(),
43
+ lastUpdated: FieldValue.serverTimestamp()
44
+ };
45
+
46
+ await overrideRef.set(defaultData);
47
+
48
+ const logMsg = `[getDevOverride] Auto-created default dev override document for developer account ${userCid}`;
49
+ if (logger && logger.log) {
50
+ logger.log('INFO', logMsg);
51
+ } else {
52
+ console.log(logMsg);
53
+ }
54
+
55
+ // Return the default (disabled) override
56
+ return {
57
+ enabled: false,
58
+ fakeCopiedPIs: [],
59
+ lastUpdated: null,
60
+ wasAutoCreated: true
61
+ };
62
+ }
63
+
64
+ const data = overrideDoc.data();
65
+
66
+ // Return override data (even if disabled, so caller knows it exists)
67
+ return {
68
+ enabled: data.enabled === true,
69
+ fakeCopiedPIs: data.fakeCopiedPIs || [],
70
+ lastUpdated: data.lastUpdated,
71
+ wasAutoCreated: false
72
+ };
73
+ } catch (error) {
74
+ console.error('[getDevOverride] Error fetching/creating dev override:', error);
75
+ return null;
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Get the list of copied PIs for a user, with dev override support
81
+ * Returns { copiedPIs: Array, isDevOverride: boolean, dataSource: string }
82
+ */
83
+ async function getCopiedPIsWithDevOverride(db, userCid, config, logger) {
84
+ const userCidNum = Number(userCid);
85
+
86
+ // Check for dev override first (this will auto-create default if needed)
87
+ const devOverride = await getDevOverride(db, userCid, config, logger);
88
+
89
+ if (devOverride && devOverride.enabled && devOverride.fakeCopiedPIs.length > 0) {
90
+ if (logger && logger.log) {
91
+ logger.log('INFO', `[getCopiedPIsWithDevOverride] Using DEV OVERRIDE for user ${userCid}: ${devOverride.fakeCopiedPIs.length} fake copied PIs`);
92
+ } else {
93
+ console.log(`[getCopiedPIsWithDevOverride] Using DEV OVERRIDE for user ${userCid}: ${devOverride.fakeCopiedPIs.length} fake copied PIs`);
94
+ }
95
+
96
+ return {
97
+ copiedPIs: devOverride.fakeCopiedPIs.map(cid => ({
98
+ cid: Number(cid),
99
+ username: 'Dev Override' // Will be resolved from rankings later
100
+ })),
101
+ isDevOverride: true,
102
+ dataSource: 'dev_override'
103
+ };
104
+ }
105
+
106
+ // Otherwise, use normal logic (computation or portfolio)
107
+ // This will be handled by the calling function
108
+ return {
109
+ copiedPIs: [],
110
+ isDevOverride: false,
111
+ dataSource: 'normal'
112
+ };
113
+ }
114
+
115
+ /**
116
+ * Check if a user has copied a specific PI, with dev override support
117
+ */
118
+ async function hasUserCopiedWithDevOverride(db, userCid, piCid, config, logger) {
119
+ const userCidNum = Number(userCid);
120
+ const piCidNum = Number(piCid);
121
+
122
+ // Check for dev override first (this will auto-create default if needed)
123
+ const devOverride = await getDevOverride(db, userCid, config, logger);
124
+
125
+ if (devOverride && devOverride.enabled) {
126
+ const hasCopied = devOverride.fakeCopiedPIs.includes(piCidNum);
127
+ if (hasCopied) {
128
+ if (logger && logger.log) {
129
+ logger.log('INFO', `[hasUserCopiedWithDevOverride] DEV OVERRIDE: User ${userCid} "copies" PI ${piCid}`);
130
+ } else {
131
+ console.log(`[hasUserCopiedWithDevOverride] DEV OVERRIDE: User ${userCid} "copies" PI ${piCid}`);
132
+ }
133
+ return true;
134
+ }
135
+ // If dev override is enabled but PI not in list, return false (don't check real data)
136
+ if (logger && logger.log) {
137
+ logger.log('INFO', `[hasUserCopiedWithDevOverride] DEV OVERRIDE: User ${userCid} does NOT "copy" PI ${piCid} (override enabled, PI not in fake list)`);
138
+ } else {
139
+ console.log(`[hasUserCopiedWithDevOverride] DEV OVERRIDE: User ${userCid} does NOT "copy" PI ${piCid} (override enabled, PI not in fake list)`);
140
+ }
141
+ return false;
142
+ }
143
+
144
+ // Otherwise, use normal logic (will be handled by calling function)
145
+ return null; // null means "not a dev override, use normal logic"
146
+ }
147
+
148
+ /**
149
+ * Create or update dev override for a developer account
150
+ * SECURITY: Only works for whitelisted developer CIDs
151
+ */
152
+ async function setDevOverride(req, res, dependencies, config) {
153
+ const { db, logger } = dependencies;
154
+ const { userCid, enabled, fakeCopiedPIs } = req.body;
155
+
156
+ if (!userCid) {
157
+ return res.status(400).json({ error: "Missing userCid" });
158
+ }
159
+
160
+ // SECURITY CHECK: Only allow whitelisted developer accounts
161
+ if (!isDeveloperAccount(userCid)) {
162
+ logger.log('WARN', `[setDevOverride] Unauthorized attempt to set dev override for user ${userCid}`);
163
+ return res.status(403).json({
164
+ error: "Forbidden",
165
+ message: "Developer mode is only available for authorized developer accounts"
166
+ });
167
+ }
168
+
169
+ // Validate fakeCopiedPIs
170
+ if (enabled && (!Array.isArray(fakeCopiedPIs) || fakeCopiedPIs.length === 0)) {
171
+ return res.status(400).json({
172
+ error: "Invalid request",
173
+ message: "When enabled, fakeCopiedPIs must be a non-empty array"
174
+ });
175
+ }
176
+
177
+ // Ensure all CIDs are numbers
178
+ const validatedPIs = enabled ? fakeCopiedPIs.map(cid => Number(cid)).filter(cid => !isNaN(cid) && cid > 0) : [];
179
+
180
+ try {
181
+ const devOverridesCollection = config.devOverridesCollection || 'dev_overrides';
182
+ const overrideRef = db.collection(devOverridesCollection).doc(String(userCid));
183
+
184
+ const overrideData = {
185
+ userCid: Number(userCid),
186
+ enabled: enabled === true,
187
+ fakeCopiedPIs: validatedPIs,
188
+ lastUpdated: FieldValue.serverTimestamp()
189
+ };
190
+
191
+ await overrideRef.set(overrideData, { merge: true });
192
+
193
+ logger.log('SUCCESS', `[setDevOverride] Updated dev override for user ${userCid}: enabled=${enabled}, ${validatedPIs.length} fake PIs`);
194
+
195
+ return res.status(200).json({
196
+ success: true,
197
+ message: "Developer override updated",
198
+ override: overrideData
199
+ });
200
+
201
+ } catch (error) {
202
+ logger.log('ERROR', `[setDevOverride] Error updating dev override:`, error);
203
+ return res.status(500).json({ error: error.message });
204
+ }
205
+ }
206
+
207
+ /**
208
+ * Get current dev override status for a developer account
209
+ */
210
+ async function getDevOverrideStatus(req, res, dependencies, config) {
211
+ const { db, logger } = dependencies;
212
+ const { userCid } = req.query;
213
+
214
+ if (!userCid) {
215
+ return res.status(400).json({ error: "Missing userCid" });
216
+ }
217
+
218
+ // SECURITY CHECK: Only allow whitelisted developer accounts
219
+ if (!isDeveloperAccount(userCid)) {
220
+ return res.status(403).json({
221
+ error: "Forbidden",
222
+ message: "Developer mode is only available for authorized developer accounts"
223
+ });
224
+ }
225
+
226
+ try {
227
+ // This will auto-create default document if it doesn't exist
228
+ const devOverride = await getDevOverride(db, userCid, config, logger);
229
+
230
+ if (!devOverride) {
231
+ // Should not happen for developer accounts, but handle gracefully
232
+ return res.status(200).json({
233
+ enabled: false,
234
+ fakeCopiedPIs: [],
235
+ message: "No dev override configured"
236
+ });
237
+ }
238
+
239
+ return res.status(200).json({
240
+ enabled: devOverride.enabled,
241
+ fakeCopiedPIs: devOverride.fakeCopiedPIs,
242
+ lastUpdated: devOverride.lastUpdated,
243
+ wasAutoCreated: devOverride.wasAutoCreated || false
244
+ });
245
+
246
+ } catch (error) {
247
+ logger.log('ERROR', `[getDevOverrideStatus] Error fetching dev override:`, error);
248
+ return res.status(500).json({ error: error.message });
249
+ }
250
+ }
251
+
252
+ module.exports = {
253
+ isDeveloperAccount,
254
+ getDevOverride,
255
+ getCopiedPIsWithDevOverride,
256
+ hasUserCopiedWithDevOverride,
257
+ setDevOverride,
258
+ getDevOverrideStatus
259
+ };
260
+
@@ -70,7 +70,7 @@ async function findLatestComputationDate(db, insightsCollection, resultsSub, com
70
70
 
71
71
  /**
72
72
  * Checks if a Signed-In User has ever copied a specific PI.
73
- * Uses SignedInUserCopiedPIs computation (primary) with fallback to direct portfolio check.
73
+ * Uses dev override (if enabled) > SignedInUserCopiedPIs computation > direct portfolio check.
74
74
  */
75
75
  async function hasUserCopied(db, userCid, piCid, config) {
76
76
  const { signedInUsersCollection } = config;
@@ -78,6 +78,15 @@ async function hasUserCopied(db, userCid, piCid, config) {
78
78
  const userCidNum = Number(userCid);
79
79
 
80
80
  try {
81
+ // === DEV OVERRIDE CHECK (for developer accounts) ===
82
+ const { hasUserCopiedWithDevOverride } = require('./dev_helpers');
83
+ const devResult = await hasUserCopiedWithDevOverride(db, userCid, piCid, config, console);
84
+ if (devResult !== null) {
85
+ // devResult is true/false (not null), meaning dev override is active
86
+ return devResult;
87
+ }
88
+ // devResult is null, meaning no dev override, continue with normal logic
89
+
81
90
  // === PRIMARY: Try to fetch from SignedInUserCopiedPIs computation ===
82
91
  const insightsCollection = config.unifiedInsightsCollection || 'unified_insights';
83
92
  const category = 'signed_in_user';
@@ -8,6 +8,7 @@ const { getPiAnalytics, getUserRecommendations, getWatchlist, updateWatchlist, a
8
8
  const { initiateVerification, finalizeVerification } = require('./helpers/verification_helpers');
9
9
  const { getUserWatchlists, getWatchlist: getWatchlistById, createWatchlist, updateWatchlist: updateWatchlistById, deleteWatchlist, copyWatchlist, getPublicWatchlists } = require('./helpers/watchlist_helpers');
10
10
  const { subscribeToAlerts, updateSubscription, unsubscribeFromAlerts, getUserSubscriptions, subscribeToWatchlist } = require('./helpers/subscription_helpers');
11
+ const { setDevOverride, getDevOverrideStatus } = require('./helpers/dev_helpers');
11
12
 
12
13
  module.exports = (dependencies, config) => {
13
14
  const router = express.Router();
@@ -67,5 +68,9 @@ module.exports = (dependencies, config) => {
67
68
  router.get('/me/watchlists/:id/trigger-counts', (req, res) => getWatchlistTriggerCounts(req, res, dependencies, config));
68
69
  router.get('/me/watchlists/:id/rankings-check', (req, res) => checkPisInRankings(req, res, dependencies, config));
69
70
 
71
+ // --- Developer Mode (only for whitelisted developer accounts) ---
72
+ router.post('/dev/override', (req, res) => setDevOverride(req, res, dependencies, config));
73
+ router.get('/dev/override', (req, res) => getDevOverrideStatus(req, res, dependencies, config));
74
+
70
75
  return router;
71
76
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.419",
3
+ "version": "1.0.421",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [