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 V3: Debug Endpoint to get the structure from Firestore ---
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
- try {
97
- // 1. Find the computation category
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
- const doc = await docRef.get();
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
- if (!doc.exists) {
136
- // This is unlikely if the summary flag is true, but good to check
137
- return res.status(404).send({
138
- status: 'error',
139
- message: `Data for '${computationName}' not found for its latest date (${latestDateForThisComp}). Summary flag was present but doc is missing.`,
140
- checkedPath: docRef.path
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
- // 5. Generate and return the snippet
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
- computation: computationName,
151
- category: category,
152
- latestStoredDate: latestDateForThisComp,
153
- structureSnippet: snippet,
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
- // Handle errors, including bad query paths
158
- if (error.message && error.message.includes('Invalid argument')) {
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")) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.33",
3
+ "version": "1.0.35",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [