bulltrackers-module 1.0.178 → 1.0.179
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.
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* (REFACTORED: Removed all concurrency from `handleUpdate` and `lookupUsernames`)
|
|
4
4
|
* (REFACTORED: Added node-fetch fallback for all API calls)
|
|
5
5
|
* (REFACTORED: Added verbose, user-centric logging for all operations)
|
|
6
|
-
* (FIXED: Resolved ReferenceError 'instId is not defined' in final timestamp loops)
|
|
7
6
|
*/
|
|
8
7
|
|
|
9
8
|
const { FieldValue } = require('@google-cloud/firestore');
|
|
@@ -16,83 +15,54 @@ async function lookupUsernames(cids, { logger, headerManager, proxyManager }, co
|
|
|
16
15
|
if (!cids?.length) return [];
|
|
17
16
|
logger.log('INFO', `[lookupUsernames] Looking up usernames for ${cids.length} CIDs.`);
|
|
18
17
|
|
|
19
|
-
// --- Set concurrency to 1
|
|
18
|
+
// --- Set concurrency to 1 because appscript gets really fucked up with undocumented rate limits if we try spam it concurrently, a shame but that's life. DO NOT CHANGE THIS
|
|
20
19
|
const limit = pLimit(1);
|
|
21
20
|
const { USERNAME_LOOKUP_BATCH_SIZE, ETORO_API_RANKINGS_URL } = config;
|
|
22
21
|
const batches = [];
|
|
23
|
-
for (let i = 0; i < cids.length; i += USERNAME_LOOKUP_BATCH_SIZE) {
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const batchPromises = batches.map((batch, index) => limit(async () => {
|
|
28
|
-
const batchId = `batch-${index + 1}`;
|
|
22
|
+
for (let i = 0; i < cids.length; i += USERNAME_LOOKUP_BATCH_SIZE) { batches.push(cids.slice(i, i + USERNAME_LOOKUP_BATCH_SIZE).map(Number)); }
|
|
23
|
+
const batchPromises = batches.map((batch, index) => limit(async () => { const batchId = `batch-${index + 1}`;
|
|
29
24
|
logger.log('INFO', `[lookupUsernames/${batchId}] Processing batch of ${batch.length} CIDs...`);
|
|
30
25
|
const header = await headerManager.selectHeader();
|
|
31
26
|
if (!header) { logger.log('ERROR', `[lookupUsernames/${batchId}] Could not select a header.`); return null; }
|
|
32
|
-
|
|
33
27
|
let wasSuccess = false;
|
|
34
28
|
let proxyUsed = true;
|
|
35
29
|
let response;
|
|
36
30
|
const url = `${ETORO_API_RANKINGS_URL}?Period=LastTwoYears`;
|
|
37
31
|
const options = { method: 'POST', headers: { ...header.header, 'Content-Type': 'application/json' }, body: JSON.stringify(batch) };
|
|
38
|
-
|
|
39
32
|
try {
|
|
40
33
|
logger.log('TRACE', `[lookupUsernames/${batchId}] Attempting fetch via AppScript proxy...`);
|
|
41
34
|
response = await proxyManager.fetch(url, options);
|
|
42
35
|
if (!response.ok) throw new Error(`AppScript proxy failed with status ${response.status}`);
|
|
43
|
-
wasSuccess = true;
|
|
36
|
+
wasSuccess = true; // Yay we win
|
|
44
37
|
logger.log('INFO', `[lookupUsernames/${batchId}] AppScript proxy fetch successful.`);
|
|
45
|
-
} catch (proxyError) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
throw new Error(`Direct fetch failed with status ${response.status}. Response: ${errorText.substring(0, 200)}`);
|
|
53
|
-
}
|
|
54
|
-
logger.log('INFO', `[lookupUsernames/${batchId}] Direct node-fetch fallback successful.`);
|
|
55
|
-
} catch (fallbackError) {
|
|
56
|
-
logger.log('ERROR', `[lookupUsernames/${batchId}] Direct node-fetch fallback FAILED. Giving up on this batch.`, { error: fallbackError.message, source: 'eToro/Network' });
|
|
57
|
-
return null;
|
|
38
|
+
} catch (proxyError) { logger.log('WARN', `[lookupUsernames/${batchId}] AppScript proxy fetch FAILED. Error: ${proxyError.message}. Attempting direct node-fetch fallback.`, { error: proxyError.message, source: 'AppScript' }); // SHIT we failed...
|
|
39
|
+
proxyUsed = false; // Don't penalize header for proxy failure
|
|
40
|
+
try { response = await fetch(url, options); // Ok let's try again with node, using GCP IP pools
|
|
41
|
+
if (!response.ok) { const errorText = await response.text(); throw new Error(`Direct fetch failed with status ${response.status}. Response: ${errorText.substring(0, 200)}`); }
|
|
42
|
+
logger.log('INFO', `[lookupUsernames/${batchId}] Direct node-fetch fallback successful.`); // Yay we win
|
|
43
|
+
} catch (fallbackError) { logger.log('ERROR', `[lookupUsernames/${batchId}] Direct node-fetch fallback FAILED. Giving up on this batch.`, { error: fallbackError.message, source: 'eToro/Network' }); // SHIT, we failed here too
|
|
44
|
+
return null; // Give up on this batch
|
|
58
45
|
}
|
|
59
|
-
} finally {
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
try {
|
|
64
|
-
const data = await response.json();
|
|
65
|
-
return data;
|
|
66
|
-
} catch (parseError) {
|
|
67
|
-
logger.log('ERROR', `[lookupUsernames/${batchId}] Failed to parse JSON response.`, { error: parseError.message });
|
|
68
|
-
return null;
|
|
69
|
-
}
|
|
70
|
-
}));
|
|
46
|
+
} finally { if (proxyUsed) { headerManager.updatePerformance(header.id, wasSuccess); } } // If we used Appscript IP Pool and not GCP IP Pool, record performance
|
|
47
|
+
try { const data = await response.json(); return data;
|
|
48
|
+
} catch (parseError) { logger.log('ERROR', `[lookupUsernames/${batchId}] Failed to parse JSON response.`, { error: parseError.message }); return null; } }));
|
|
71
49
|
|
|
72
50
|
const results = await Promise.allSettled(batchPromises);
|
|
73
|
-
const allUsers = results
|
|
74
|
-
.filter(r => r.status === 'fulfilled' && r.value && Array.isArray(r.value))
|
|
75
|
-
.flatMap(r => r.value);
|
|
76
|
-
|
|
51
|
+
const allUsers = results .filter(r => r.status === 'fulfilled' && r.value && Array.isArray(r.value)) .flatMap(r => r.value);
|
|
77
52
|
logger.log('INFO', `[lookupUsernames] Found ${allUsers.length} public users out of ${cids.length}.`);
|
|
78
53
|
return allUsers;
|
|
79
54
|
}
|
|
80
55
|
|
|
81
56
|
|
|
82
57
|
/**
|
|
83
|
-
* (REFACTORED: Fully sequential, verbose logging, node-fetch fallback
|
|
58
|
+
* (REFACTORED: Fully sequential, verbose logging, node-fetch fallback)
|
|
84
59
|
*/
|
|
85
60
|
async function handleUpdate(task, taskId, { logger, headerManager, proxyManager, db, batchManager }, config, username) {
|
|
86
61
|
const { userId, instruments, instrumentId, userType } = task;
|
|
87
|
-
|
|
88
|
-
// For normal users, we pass [undefined] so the loop runs exactly once.
|
|
89
|
-
// For speculators, we pass the list of instruments to fetch individually.
|
|
90
|
-
const instrumentsToProcess = userType === 'speculator' ? (instruments || [instrumentId]) : [undefined];
|
|
91
|
-
|
|
62
|
+
const instrumentsToProcess = userType === 'speculator' ? (instruments || [instrumentId]) : [undefined];
|
|
92
63
|
const today = new Date().toISOString().slice(0, 10);
|
|
93
64
|
const portfolioBlockId = `${Math.floor(parseInt(userId) / 1000000)}M`;
|
|
94
65
|
let isPrivate = false;
|
|
95
|
-
|
|
96
66
|
logger.log('INFO', `[handleUpdate/${userId}] Starting update task. Type: ${userType}. Instruments: ${instrumentsToProcess.join(', ')}`);
|
|
97
67
|
|
|
98
68
|
// --- 1. Process History Fetch (Sequentially) ---
|
|
@@ -104,70 +74,52 @@ async function handleUpdate(task, taskId, { logger, headerManager, proxyManager,
|
|
|
104
74
|
if (!batchManager.checkAndSetHistoryFetched(userId)) {
|
|
105
75
|
logger.log('INFO', `[handleUpdate/${userId}] Attempting history fetch.`);
|
|
106
76
|
historyHeader = await headerManager.selectHeader();
|
|
107
|
-
if (!historyHeader) {
|
|
108
|
-
logger.log('WARN', `[handleUpdate/${userId}] Could not select history header. Skipping history.`);
|
|
77
|
+
if (!historyHeader) { logger.log('WARN', `[handleUpdate/${userId}] Could not select history header. Skipping history.`);
|
|
109
78
|
} else {
|
|
110
79
|
const historyUrl = `${config.ETORO_API_USERSTATS_URL}${username}/trades/oneYearAgo?CopyAsAsset=true`;
|
|
111
80
|
const options = { headers: historyHeader.header };
|
|
112
81
|
let response;
|
|
113
|
-
|
|
114
|
-
try {
|
|
115
|
-
logger.log('TRACE', `[handleUpdate/${userId}] Attempting history fetch via AppScript proxy...`);
|
|
82
|
+
try { logger.log('TRACE', `[handleUpdate/${userId}] Attempting history fetch via AppScript proxy...`);
|
|
116
83
|
response = await proxyManager.fetch(historyUrl, options);
|
|
117
|
-
if (!response.ok) throw new Error(`AppScript proxy failed with status ${response.status}`);
|
|
118
|
-
wasHistorySuccess = true;
|
|
84
|
+
if (!response.ok) throw new Error(`AppScript proxy failed with status ${response.status}`); // SHIT we failed here
|
|
85
|
+
wasHistorySuccess = true; // Appscript worked, we are very smart
|
|
86
|
+
|
|
119
87
|
} catch (proxyError) {
|
|
120
|
-
logger.log('WARN', `[handleUpdate/${userId}] History fetch via AppScript proxy FAILED. Error: ${proxyError.message}. Attempting direct node-fetch fallback.`, { error: proxyError.message, source: 'AppScript' });
|
|
88
|
+
logger.log('WARN', `[handleUpdate/${userId}] History fetch via AppScript proxy FAILED. Error: ${proxyError.message}. Attempting direct node-fetch fallback.`, { error: proxyError.message, source: 'AppScript' }); // SHIT we failed here
|
|
121
89
|
proxyUsedForHistory = false;
|
|
122
|
-
try {
|
|
123
|
-
response = await
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
wasHistorySuccess =
|
|
129
|
-
} catch (fallbackError) {
|
|
130
|
-
logger.log('ERROR', `[handleUpdate/${userId}] History fetch direct fallback FAILED.`, { error: fallbackError.message, source: 'eToro/Network' });
|
|
131
|
-
wasHistorySuccess = false;
|
|
90
|
+
try { response = await fetch(historyUrl, options);
|
|
91
|
+
if (!response.ok) { const errorText = await response.text();
|
|
92
|
+
throw new Error(`Direct fetch failed with status ${response.status}. Response: ${errorText.substring(0, 200)}`); } // SHIT we failed here too
|
|
93
|
+
wasHistorySuccess = true; // Fallback succeeded, we are so smart
|
|
94
|
+
|
|
95
|
+
} catch (fallbackError) { logger.log('ERROR', `[handleUpdate/${userId}] History fetch direct fallback FAILED.`, { error: fallbackError.message, source: 'eToro/Network' }); // We are dumb, everything failed
|
|
96
|
+
wasHistorySuccess = false; // Nope we are dumb....
|
|
132
97
|
}
|
|
133
98
|
}
|
|
134
99
|
|
|
135
|
-
if (wasHistorySuccess) {
|
|
136
|
-
logger.log('INFO', `[handleUpdate/${userId}] History fetch successful.`);
|
|
100
|
+
if (wasHistorySuccess) { logger.log('INFO', `[handleUpdate/${userId}] History fetch successful.`); // Some method worked, we are very smart
|
|
137
101
|
const data = await response.json();
|
|
138
|
-
await batchManager.addToTradingHistoryBatch(userId, portfolioBlockId, today, data, userType);
|
|
139
|
-
}
|
|
102
|
+
await batchManager.addToTradingHistoryBatch(userId, portfolioBlockId, today, data, userType); }
|
|
140
103
|
}
|
|
141
|
-
} else {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
} catch (err) {
|
|
145
|
-
logger.log('ERROR', `[handleUpdate/${userId}] Unhandled error during history processing.`, { error: err.message });
|
|
146
|
-
wasHistorySuccess = false;
|
|
147
|
-
} finally {
|
|
148
|
-
if (historyHeader && proxyUsedForHistory) { headerManager.updatePerformance(historyHeader.id, wasHistorySuccess); }
|
|
149
|
-
}
|
|
104
|
+
} else { logger.log('TRACE', `[handleUpdate/${userId}] History fetch skipped (already fetched by this instance).`); }
|
|
105
|
+
} catch (err) { logger.log('ERROR', `[handleUpdate/${userId}] Unhandled error during history processing.`, { error: err.message }); wasHistorySuccess = false; // We fucked up.
|
|
106
|
+
} finally { if (historyHeader && proxyUsedForHistory) { headerManager.updatePerformance(historyHeader.id, wasHistorySuccess); } } // If we used appscript proxy, record performance, otherwise fuck off.
|
|
150
107
|
|
|
151
108
|
// --- 2. Process Portfolio Fetches (Sequentially) ---
|
|
152
109
|
logger.log('INFO', `[handleUpdate/${userId}] Starting ${instrumentsToProcess.length} sequential portfolio fetches.`);
|
|
153
110
|
|
|
154
|
-
|
|
155
|
-
for (const currentInstrumentId of instrumentsToProcess) {
|
|
111
|
+
for (const instId of instrumentsToProcess) {
|
|
156
112
|
if (isPrivate) {
|
|
157
113
|
logger.log('INFO', `[handleUpdate/${userId}] Skipping remaining instruments because user was marked as private.`);
|
|
158
114
|
break;
|
|
159
115
|
}
|
|
160
116
|
|
|
161
117
|
const portfolioHeader = await headerManager.selectHeader();
|
|
162
|
-
if (!portfolioHeader) {
|
|
163
|
-
logger.log('ERROR', `[handleUpdate/${userId}] Could not select portfolio header. Skipping instrument.`);
|
|
118
|
+
if (!portfolioHeader) { logger.log('ERROR', `[handleUpdate/${userId}] Could not select portfolio header for instId ${instId}. Skipping this instrument.`);
|
|
164
119
|
continue;
|
|
165
120
|
}
|
|
166
121
|
|
|
167
|
-
|
|
168
|
-
const portfolioUrl = userType === 'speculator' && currentInstrumentId
|
|
169
|
-
? `${config.ETORO_API_POSITIONS_URL}?cid=${userId}&InstrumentID=${currentInstrumentId}`
|
|
170
|
-
: `${config.ETORO_API_PORTFOLIO_URL}?cid=${userId}`;
|
|
122
|
+
const portfolioUrl = userType === 'speculator' ? `${config.ETORO_API_POSITIONS_URL}?cid=${userId}&InstrumentID=${instId}` : `${config.ETORO_API_PORTFOLIO_URL}?cid=${userId}`;
|
|
171
123
|
|
|
172
124
|
const options = { headers: portfolioHeader.header };
|
|
173
125
|
let response;
|
|
@@ -175,54 +127,48 @@ async function handleUpdate(task, taskId, { logger, headerManager, proxyManager,
|
|
|
175
127
|
let proxyUsedForPortfolio = true;
|
|
176
128
|
|
|
177
129
|
try {
|
|
178
|
-
|
|
130
|
+
// --- REFACTOR 3: ADD FALLBACK ---
|
|
131
|
+
logger.log('TRACE', `[handleUpdate/${userId}] Attempting portfolio fetch for instId ${instId} via AppScript proxy...`);
|
|
179
132
|
response = await proxyManager.fetch(portfolioUrl, options);
|
|
180
|
-
if (!response.ok) throw new Error(`AppScript proxy failed with status ${response.status}`);
|
|
181
|
-
wasPortfolioSuccess = true;
|
|
133
|
+
if (!response.ok) throw new Error(`AppScript proxy failed with status ${response.status}`); // SHIT we failed here
|
|
134
|
+
wasPortfolioSuccess = true; // Oh we are smart, worked first time.
|
|
182
135
|
|
|
183
|
-
} catch (proxyError) {
|
|
184
|
-
logger.log('WARN', `[handleUpdate/${userId}] Portfolio fetch via AppScript proxy FAILED. Error: ${proxyError.message}. Attempting direct node-fetch fallback.`, { error: proxyError.message, source: 'AppScript' });
|
|
185
|
-
proxyUsedForPortfolio = false;
|
|
136
|
+
} catch (proxyError) { // try fallback with local node fetch using GCP IP Pools
|
|
137
|
+
logger.log('WARN', `[handleUpdate/${userId}] Portfolio fetch for instId ${instId} via AppScript proxy FAILED. Error: ${proxyError.message}. Attempting direct node-fetch fallback.`, { error: proxyError.message, source: 'AppScript' });
|
|
138
|
+
proxyUsedForPortfolio = false; // We are not using Appscript proxy here as fallback is GCP based, so false
|
|
186
139
|
|
|
187
140
|
try {
|
|
188
|
-
response = await fetch(portfolioUrl, options);
|
|
141
|
+
response = await fetch(portfolioUrl, options); // Direct node-fetch
|
|
189
142
|
if (!response.ok) {
|
|
190
143
|
const errorText = await response.text();
|
|
191
|
-
throw new Error(`Direct fetch failed with status ${response.status}. Response: ${errorText.substring(0, 200)}`);
|
|
144
|
+
throw new Error(`Direct fetch failed with status ${response.status}. Response: ${errorText.substring(0, 200)}`); // SHIT we failed here
|
|
192
145
|
}
|
|
193
|
-
wasPortfolioSuccess = true;
|
|
146
|
+
wasPortfolioSuccess = true; // Fallback succeeded we are so smart
|
|
147
|
+
|
|
194
148
|
} catch (fallbackError) {
|
|
195
|
-
logger.log('ERROR', `[handleUpdate/${userId}] Portfolio fetch direct fallback FAILED.`, { error: fallbackError.message, source: 'eToro/Network' });
|
|
149
|
+
logger.log('ERROR', `[handleUpdate/${userId}] Portfolio fetch for instId ${instId} direct fallback FAILED.`, { error: fallbackError.message, source: 'eToro/Network' });
|
|
196
150
|
wasPortfolioSuccess = false;
|
|
197
151
|
}
|
|
198
152
|
}
|
|
199
153
|
|
|
154
|
+
// --- 4. Process Portfolio Result (with verbose, raw logging) ---
|
|
200
155
|
if (wasPortfolioSuccess) {
|
|
201
156
|
const body = await response.text();
|
|
202
|
-
if (body.includes("user is PRIVATE")) {
|
|
203
|
-
|
|
204
|
-
logger.log('WARN', `[handleUpdate/${userId}] User is PRIVATE. Marking for removal.`);
|
|
205
|
-
break;
|
|
157
|
+
if (body.includes("user is PRIVATE")) { isPrivate = true; logger.log('WARN', `[handleUpdate/${userId}] User is PRIVATE. Marking for removal.`);
|
|
158
|
+
break; // Stop processing more portfolios for this private user
|
|
206
159
|
}
|
|
207
160
|
|
|
208
161
|
try {
|
|
209
162
|
const portfolioJson = JSON.parse(body);
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
if (userType === 'speculator') {
|
|
214
|
-
logger.log('INFO', `[handleUpdate/${userId}] Successfully processed portfolio for instId ${currentInstrumentId}.`);
|
|
215
|
-
} else {
|
|
216
|
-
logger.log('INFO', `[handleUpdate/${userId}] Successfully processed full portfolio (normal user).`);
|
|
217
|
-
}
|
|
163
|
+
await batchManager.addToPortfolioBatch(userId, portfolioBlockId, today, portfolioJson, userType, instId);
|
|
164
|
+
if (userType === 'speculator') { logger.log('INFO', `[handleUpdate/${userId}] Successfully processed portfolio for instId ${instId}.`); // Only speculators have an instid, so this is conditional
|
|
165
|
+
} else { logger.log('INFO', `[handleUpdate/${userId}] Successfully processed full portfolio (normal user).`); } // Normal users
|
|
218
166
|
|
|
219
|
-
} catch (parseError) {
|
|
220
|
-
wasPortfolioSuccess = false;
|
|
221
|
-
logger.log('ERROR', `[handleUpdate/${userId}] FAILED TO PARSE JSON RESPONSE. RAW BODY:`, { url: portfolioUrl, parseErrorMessage: parseError.message, rawResponseText: body });
|
|
167
|
+
} catch (parseError) { // Idk why this would happen, but if it does....log.
|
|
168
|
+
wasPortfolioSuccess = false; // Mark as failure
|
|
169
|
+
logger.log('ERROR', `[handleUpdate/${userId}] FAILED TO PARSE JSON RESPONSE. RAW BODY:`, { url: portfolioUrl, parseErrorMessage: parseError.message, rawResponseText: body }); // Return full response
|
|
222
170
|
}
|
|
223
|
-
} else {
|
|
224
|
-
logger.log('WARN', `[handleUpdate/${userId}] Portfolio fetch failed. No response to process.`);
|
|
225
|
-
}
|
|
171
|
+
} else { logger.log('WARN', `[handleUpdate/${userId}] Portfolio fetch failed for instId ${instId}. No response to process.`); }
|
|
226
172
|
|
|
227
173
|
if (proxyUsedForPortfolio) { headerManager.updatePerformance(portfolioHeader.id, wasPortfolioSuccess); }
|
|
228
174
|
}
|
|
@@ -230,32 +176,21 @@ async function handleUpdate(task, taskId, { logger, headerManager, proxyManager,
|
|
|
230
176
|
// --- 5. Handle Private Users & Timestamps ---
|
|
231
177
|
if (isPrivate) {
|
|
232
178
|
logger.log('WARN', `[handleUpdate/${userId}] Removing private user from updates.`);
|
|
233
|
-
|
|
234
|
-
for (const currentInstrumentId of instrumentsToProcess) {
|
|
235
|
-
await batchManager.deleteFromTimestampBatch(userId, userType, currentInstrumentId);
|
|
236
|
-
}
|
|
237
|
-
|
|
179
|
+
for (const instrumentId of instrumentsToProcess) { await batchManager.deleteFromTimestampBatch(userId, userType, instId); }
|
|
238
180
|
const blockCountsRef = db.doc(config.FIRESTORE_DOC_SPECULATOR_BLOCK_COUNTS);
|
|
239
|
-
for (const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
const incrementField = `counts.${currentInstrumentId}_${Math.floor(userId/1e6)*1e6}`;
|
|
243
|
-
await blockCountsRef.set({ [incrementField]: FieldValue.increment(-1) }, { merge: true });
|
|
244
|
-
}
|
|
181
|
+
for (const instrumentId of instrumentsToProcess) {
|
|
182
|
+
const incrementField = `counts.${instrumentId}_${Math.floor(userId/1e6)*1e6}`;
|
|
183
|
+
await blockCountsRef.set({ [incrementField]: FieldValue.increment(-1) }, { merge: true });
|
|
245
184
|
}
|
|
246
185
|
return;
|
|
247
186
|
}
|
|
248
187
|
|
|
249
188
|
// If not private, update all timestamps
|
|
250
|
-
for (const
|
|
251
|
-
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
if (userType === 'speculator') {
|
|
255
|
-
await batchManager.addSpeculatorTimestampFix(userId, String(Math.floor(userId/1e6)*1e6));
|
|
256
|
-
}
|
|
189
|
+
for (const instrumentId of instrumentsToProcess) { await batchManager.updateUserTimestamp(userId, userType, instId); }
|
|
190
|
+
if (userType === 'speculator') { await batchManager.addSpeculatorTimestampFix(userId, String(Math.floor(userId/1e6)*1e6)); }
|
|
257
191
|
|
|
258
192
|
logger.log('INFO', `[handleUpdate/${userId}] Update task finished successfully.`);
|
|
193
|
+
// 'finally' block for header flushing is handled by the main handler_creator.js
|
|
259
194
|
}
|
|
260
195
|
|
|
261
196
|
module.exports = { handleUpdate, lookupUsernames };
|