bulltrackers-module 1.0.33 → 1.0.35
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.
|
@@ -48,6 +48,73 @@ function createStructureSnippet(data, maxKeys = 20) {
|
|
|
48
48
|
return newObj;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Reusable helper to fetch the structure for a single computation.
|
|
53
|
+
* @param {string} computationName
|
|
54
|
+
* @param {object} calcMap
|
|
55
|
+
* @param {object} config
|
|
56
|
+
* @param {Firestore} firestore
|
|
57
|
+
* @param {Logger} logger
|
|
58
|
+
* @returns {Promise<object>} A result object with status and data or error.
|
|
59
|
+
*/
|
|
60
|
+
async function getComputationStructure(computationName, calcMap, config, firestore, logger) {
|
|
61
|
+
try {
|
|
62
|
+
// 1. Find the computation category
|
|
63
|
+
const pathInfo = calcMap[computationName];
|
|
64
|
+
if (!pathInfo) {
|
|
65
|
+
return { status: 'error', computation: computationName, message: `Computation not found in calculation map.` };
|
|
66
|
+
}
|
|
67
|
+
const { category } = pathInfo;
|
|
68
|
+
|
|
69
|
+
// 2. Get collection names from config
|
|
70
|
+
const insightsCollection = config.unifiedInsightsCollection || 'unified_insights';
|
|
71
|
+
const resultsSub = config.resultsSubcollection || 'results';
|
|
72
|
+
const compsSub = config.computationsSubcollection || 'computations';
|
|
73
|
+
|
|
74
|
+
// 3. Find the most recent date *that contains this computation*
|
|
75
|
+
const computationQueryPath = `${category}.${computationName}`;
|
|
76
|
+
const dateQuery = firestore.collection(insightsCollection)
|
|
77
|
+
.where(computationQueryPath, '==', true)
|
|
78
|
+
.orderBy(FieldPath.documentId(), 'desc')
|
|
79
|
+
.limit(1);
|
|
80
|
+
|
|
81
|
+
const dateSnapshot = await dateQuery.get();
|
|
82
|
+
|
|
83
|
+
if (dateSnapshot.empty) {
|
|
84
|
+
return { status: 'error', computation: computationName, message: `No computed data found. (Query path: ${computationQueryPath})` };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const latestStoredDate = dateSnapshot.docs[0].id;
|
|
88
|
+
|
|
89
|
+
// 4. Construct the path to the actual computation document
|
|
90
|
+
const docRef = firestore.collection(insightsCollection).doc(latestStoredDate)
|
|
91
|
+
.collection(resultsSub).doc(category)
|
|
92
|
+
.collection(compsSub).doc(computationName);
|
|
93
|
+
|
|
94
|
+
const doc = await docRef.get();
|
|
95
|
+
|
|
96
|
+
if (!doc.exists) {
|
|
97
|
+
return { status: 'error', computation: computationName, message: `Summary flag was present for ${latestStoredDate} but doc is missing.` };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// 5. Generate and return the snippet
|
|
101
|
+
const fullData = doc.data();
|
|
102
|
+
const structureSnippet = createStructureSnippet(fullData);
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
status: 'success',
|
|
106
|
+
computation: computationName,
|
|
107
|
+
category: category,
|
|
108
|
+
latestStoredDate: latestStoredDate,
|
|
109
|
+
structureSnippet: structureSnippet,
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
} catch (error) {
|
|
113
|
+
logger.log('ERROR', `API /structure/${computationName} helper failed.`, { errorMessage: error.message });
|
|
114
|
+
return { status: 'error', computation: computationName, message: error.message };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
51
118
|
|
|
52
119
|
/**
|
|
53
120
|
* Creates and configures the Express app for the Generic API.
|
|
@@ -90,81 +157,80 @@ function createApiApp(config, dependencies, unifiedCalculations) {
|
|
|
90
157
|
}
|
|
91
158
|
});
|
|
92
159
|
|
|
93
|
-
// --- REVISED
|
|
160
|
+
// --- REVISED: Debug Endpoint to get the structure from Firestore ---
|
|
94
161
|
app.get('/structure/:computationName', async (req, res) => {
|
|
95
162
|
const { computationName } = req.params;
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const pathInfo = calcMap[computationName];
|
|
99
|
-
if (!pathInfo) {
|
|
100
|
-
return res.status(404).send({ status: 'error', message: `Computation '${computationName}' not found in calculation map.` });
|
|
101
|
-
}
|
|
102
|
-
const { category } = pathInfo;
|
|
103
|
-
|
|
104
|
-
// 2. Get collection names from config
|
|
105
|
-
const insightsCollection = config.unifiedInsightsCollection || 'unified_insights';
|
|
106
|
-
const resultsSub = config.resultsSubcollection || 'results';
|
|
107
|
-
const compsSub = config.computationsSubcollection || 'computations';
|
|
108
|
-
|
|
109
|
-
// 3. Find the most recent date *that contains this computation*
|
|
110
|
-
// This relies on the summary flags set by orchestration_helpers.js
|
|
111
|
-
const computationQueryPath = `${category}.${computationName}`;
|
|
112
|
-
const dateQuery = firestore.collection(insightsCollection)
|
|
113
|
-
.where(computationQueryPath, '==', true) // Find docs with the summary flag
|
|
114
|
-
.orderBy(FieldPath.documentId(), 'desc') // Get the latest date
|
|
115
|
-
.limit(1);
|
|
116
|
-
|
|
117
|
-
const dateSnapshot = await dateQuery.get();
|
|
118
|
-
|
|
119
|
-
if (dateSnapshot.empty) {
|
|
120
|
-
return res.status(404).send({
|
|
121
|
-
status: 'error',
|
|
122
|
-
message: `No computed data found for '${computationName}'. (Query path: ${computationQueryPath})`
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const latestDateForThisComp = dateSnapshot.docs[0].id;
|
|
127
|
-
|
|
128
|
-
// 4. Construct the path to the actual computation document
|
|
129
|
-
const docRef = firestore.collection(insightsCollection).doc(latestDateForThisComp)
|
|
130
|
-
.collection(resultsSub).doc(category)
|
|
131
|
-
.collection(compsSub).doc(computationName);
|
|
163
|
+
|
|
164
|
+
const result = await getComputationStructure(computationName, calcMap, config, firestore, logger);
|
|
132
165
|
|
|
133
|
-
|
|
166
|
+
if (result.status === 'error') {
|
|
167
|
+
const statusCode = result.message.includes('not found') ? 404 : 500;
|
|
168
|
+
return res.status(statusCode).send(result);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
res.status(200).send(result);
|
|
172
|
+
});
|
|
134
173
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
174
|
+
// --- NEW: Debug Endpoint to generate the full structure manifest ---
|
|
175
|
+
app.get('/manifest', async (req, res) => {
|
|
176
|
+
try {
|
|
177
|
+
const allKeys = Object.keys(calcMap).sort();
|
|
178
|
+
|
|
179
|
+
// Run all structure fetches in parallel
|
|
180
|
+
const promises = allKeys.map(key =>
|
|
181
|
+
getComputationStructure(key, calcMap, config, firestore, logger)
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
const results = await Promise.allSettled(promises);
|
|
185
|
+
|
|
186
|
+
const manifest = {};
|
|
187
|
+
const errors = [];
|
|
188
|
+
let successCount = 0;
|
|
189
|
+
|
|
190
|
+
results.forEach(result => {
|
|
191
|
+
if (result.status === 'fulfilled' && result.value.status === 'success') {
|
|
192
|
+
const data = result.value;
|
|
193
|
+
manifest[data.computation] = {
|
|
194
|
+
category: data.category,
|
|
195
|
+
latestStoredDate: data.latestStoredDate,
|
|
196
|
+
structureSnippet: data.structureSnippet,
|
|
197
|
+
};
|
|
198
|
+
successCount++;
|
|
199
|
+
} else if (result.status === 'fulfilled') {
|
|
200
|
+
// Handled error from our helper
|
|
201
|
+
const errorData = result.value;
|
|
202
|
+
errors.push({
|
|
203
|
+
computation: errorData.computation,
|
|
204
|
+
message: errorData.message,
|
|
205
|
+
});
|
|
206
|
+
} else {
|
|
207
|
+
// Promise rejected (unexpected error)
|
|
208
|
+
errors.push({
|
|
209
|
+
computation: "Unknown (Promise rejected)",
|
|
210
|
+
message: result.reason.message,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
});
|
|
143
214
|
|
|
144
|
-
|
|
145
|
-
const fullData = doc.data();
|
|
146
|
-
const snippet = createStructureSnippet(fullData);
|
|
215
|
+
logger.log('INFO', `API /manifest complete. ${successCount}/${allKeys.length} computations successful.`);
|
|
147
216
|
|
|
148
217
|
res.status(200).send({
|
|
149
218
|
status: 'success',
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
219
|
+
summary: {
|
|
220
|
+
totalComputations: allKeys.length,
|
|
221
|
+
successful: successCount,
|
|
222
|
+
failed: errors.length,
|
|
223
|
+
},
|
|
224
|
+
manifest,
|
|
225
|
+
errors,
|
|
154
226
|
});
|
|
155
227
|
|
|
156
228
|
} catch (error) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
logger.log('ERROR', `API /structure/${computationName} failed, likely due to invalid query path.`, { queryPath: `${category}.${computationName}`, stack: error.stack });
|
|
160
|
-
return res.status(500).send({ status: 'error', message: `Internal query error. The path '${category}.${computationName}' might be invalid.` });
|
|
161
|
-
}
|
|
162
|
-
logger.log('ERROR', `API /structure/${computationName} failed.`, { errorMessage: error.message, stack: error.stack });
|
|
163
|
-
res.status(500).send({ status: 'error', message: `An internal error occurred while processing '${computationName}'.` });
|
|
229
|
+
logger.log('ERROR', `API /manifest failed unexpectedly.`, { errorMessage: error.message, stack: error.stack });
|
|
230
|
+
res.status(500).send({ status: 'error', message: `An internal error occurred while building the manifest.` });
|
|
164
231
|
}
|
|
165
232
|
});
|
|
166
233
|
|
|
167
|
-
|
|
168
234
|
return app;
|
|
169
235
|
}
|
|
170
236
|
|
|
@@ -24,6 +24,15 @@ async function handleUpdate(task, taskId, clients, config) {
|
|
|
24
24
|
|
|
25
25
|
logger.log('INFO', `[UPDATE] Fetching portfolio for user ${userId} (${userType} with url ${url})`);
|
|
26
26
|
const { response } = await clients.proxyManager.fetch(url, { headers: selectedHeader.headers });
|
|
27
|
+
// Add this check
|
|
28
|
+
if (!response || typeof response.text !== 'function') {
|
|
29
|
+
// Log the problematic response object for debugging
|
|
30
|
+
logger.log('ERROR', `[UPDATE] Invalid or incomplete response received for user ${userId}`, { response });
|
|
31
|
+
// Decide how to handle this - maybe throw an error or return early
|
|
32
|
+
throw new Error(`Invalid response structure received from proxy for user ${userId}.`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Now it's safer to call .text()
|
|
27
36
|
const responseBody = await response.text();
|
|
28
37
|
|
|
29
38
|
if (responseBody.includes("user is PRIVATE")) {
|