bulltrackers-module 1.0.31 → 1.0.32
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.
- package/functions/generic-api/index.js +87 -23
- package/package.json +1 -1
|
@@ -5,8 +5,50 @@
|
|
|
5
5
|
|
|
6
6
|
const express = require('express');
|
|
7
7
|
const cors = require('cors');
|
|
8
|
+
// FieldPath is needed to query by document ID
|
|
9
|
+
const { FieldPath } = require('@google-cloud/firestore');
|
|
8
10
|
const { buildCalculationMap, createApiHandler } = require('./helpers/api_helpers.js');
|
|
9
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Recursively creates a snippet of a data object, limiting arrays and large maps.
|
|
14
|
+
* @param {*} data - The data to snippet.
|
|
15
|
+
* @param {number} [maxKeys=20] - Max keys before an object is considered a "large map".
|
|
16
|
+
* @returns {*} - The snippet.
|
|
17
|
+
*/
|
|
18
|
+
function createStructureSnippet(data, maxKeys = 20) {
|
|
19
|
+
// Handle primitives and null
|
|
20
|
+
if (data === null || typeof data !== 'object') {
|
|
21
|
+
return data;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Handle arrays: return a snippet of the first element
|
|
25
|
+
if (Array.isArray(data)) {
|
|
26
|
+
if (data.length === 0) {
|
|
27
|
+
return "<empty array>";
|
|
28
|
+
}
|
|
29
|
+
// Recursively snippet the first item
|
|
30
|
+
return [ createStructureSnippet(data[0], maxKeys) ];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Handle objects
|
|
34
|
+
const newObj = {};
|
|
35
|
+
const keys = Object.keys(data);
|
|
36
|
+
|
|
37
|
+
// If it's a large map (e.g., user IDs), just show one example
|
|
38
|
+
if (keys.length > maxKeys) {
|
|
39
|
+
const firstKey = keys[0];
|
|
40
|
+
newObj[firstKey] = createStructureSnippet(data[firstKey], maxKeys);
|
|
41
|
+
newObj[`... (${keys.length - 1} more keys)`] = "<object>";
|
|
42
|
+
} else {
|
|
43
|
+
// Otherwise, it's a structural object, so map all keys
|
|
44
|
+
for (const key of keys) {
|
|
45
|
+
newObj[key] = createStructureSnippet(data[key], maxKeys);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return newObj;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
10
52
|
/**
|
|
11
53
|
* Creates and configures the Express app for the Generic API.
|
|
12
54
|
* @param {object} config - The Generic API V2 configuration object.
|
|
@@ -19,7 +61,6 @@ function createApiApp(config, dependencies, unifiedCalculations) {
|
|
|
19
61
|
const { firestore, logger } = dependencies; // Extract dependencies
|
|
20
62
|
|
|
21
63
|
// --- Pre-compute Calculation Map ---
|
|
22
|
-
// This doesn't need config directly
|
|
23
64
|
const calcMap = buildCalculationMap(unifiedCalculations);
|
|
24
65
|
|
|
25
66
|
// --- Middleware ---
|
|
@@ -27,7 +68,6 @@ function createApiApp(config, dependencies, unifiedCalculations) {
|
|
|
27
68
|
app.use(express.json());
|
|
28
69
|
|
|
29
70
|
// --- Main API Endpoint ---
|
|
30
|
-
// The handler is created with its dependencies and config injected
|
|
31
71
|
app.get('/', createApiHandler(config, firestore, logger, calcMap));
|
|
32
72
|
|
|
33
73
|
// --- Health Check Endpoint ---
|
|
@@ -35,7 +75,7 @@ function createApiApp(config, dependencies, unifiedCalculations) {
|
|
|
35
75
|
res.status(200).send('OK');
|
|
36
76
|
});
|
|
37
77
|
|
|
38
|
-
// ---
|
|
78
|
+
// --- Debug Endpoint to list all computation keys ---
|
|
39
79
|
app.get('/list-computations', (req, res) => {
|
|
40
80
|
try {
|
|
41
81
|
const computationKeys = Object.keys(calcMap);
|
|
@@ -50,38 +90,62 @@ function createApiApp(config, dependencies, unifiedCalculations) {
|
|
|
50
90
|
}
|
|
51
91
|
});
|
|
52
92
|
|
|
53
|
-
// ---
|
|
54
|
-
app.get('/structure/:computationName', (req, res) => {
|
|
93
|
+
// --- REVISED: Debug Endpoint to get the structure from Firestore ---
|
|
94
|
+
app.get('/structure/:computationName', async (req, res) => {
|
|
55
95
|
const { computationName } = req.params;
|
|
56
96
|
try {
|
|
97
|
+
// 1. Find the computation category
|
|
57
98
|
const pathInfo = calcMap[computationName];
|
|
58
99
|
if (!pathInfo) {
|
|
59
|
-
return res.status(404).send({ status: 'error', message: `Computation '${computationName}' not found.` });
|
|
100
|
+
return res.status(404).send({ status: 'error', message: `Computation '${computationName}' not found in calculation map.` });
|
|
60
101
|
}
|
|
61
|
-
|
|
62
102
|
const { category } = pathInfo;
|
|
63
|
-
const CalculationClass = unifiedCalculations[category]?.[computationName];
|
|
64
103
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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 document in Firestore
|
|
110
|
+
const dateQuery = firestore.collection(insightsCollection)
|
|
111
|
+
.orderBy(FieldPath.documentId(), 'desc')
|
|
112
|
+
.limit(1);
|
|
113
|
+
|
|
114
|
+
const dateSnapshot = await dateQuery.get();
|
|
115
|
+
|
|
116
|
+
if (dateSnapshot.empty) {
|
|
117
|
+
return res.status(404).send({ status: 'error', message: `No data found in insights collection '${insightsCollection}'.` });
|
|
68
118
|
}
|
|
119
|
+
|
|
120
|
+
const recentDate = dateSnapshot.docs[0].id;
|
|
121
|
+
|
|
122
|
+
// 4. Construct the path to the actual computation document
|
|
123
|
+
const docRef = firestore.collection(insightsCollection).doc(recentDate)
|
|
124
|
+
.collection(resultsSub).doc(category)
|
|
125
|
+
.collection(compsSub).doc(computationName);
|
|
126
|
+
|
|
127
|
+
const doc = await docRef.get();
|
|
69
128
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
status: 'success',
|
|
76
|
-
computation: computationName,
|
|
77
|
-
category: category,
|
|
78
|
-
structure: structure || {}, // Ensure it returns at least an empty object
|
|
129
|
+
if (!doc.exists) {
|
|
130
|
+
return res.status(404).send({
|
|
131
|
+
status: 'error',
|
|
132
|
+
message: `Data for '${computationName}' not found for the most recent date (${recentDate}).`,
|
|
133
|
+
checkedPath: docRef.path
|
|
79
134
|
});
|
|
80
|
-
} else {
|
|
81
|
-
logger.log('WARN', `API /structure: Class ${category}/${computationName} has no getResult() method.`);
|
|
82
|
-
return res.status(501).send({ status: 'error', message: `Calculation '${computationName}' exists but does not have a getResult() method to determine structure.` });
|
|
83
135
|
}
|
|
84
136
|
|
|
137
|
+
// 5. Generate and return the snippet
|
|
138
|
+
const fullData = doc.data();
|
|
139
|
+
const snippet = createStructureSnippet(fullData);
|
|
140
|
+
|
|
141
|
+
res.status(200).send({
|
|
142
|
+
status: 'success',
|
|
143
|
+
computation: computationName,
|
|
144
|
+
category: category,
|
|
145
|
+
exampleDate: recentDate,
|
|
146
|
+
structureSnippet: snippet,
|
|
147
|
+
});
|
|
148
|
+
|
|
85
149
|
} catch (error) {
|
|
86
150
|
logger.log('ERROR', `API /structure/${computationName} failed.`, { errorMessage: error.message, stack: error.stack });
|
|
87
151
|
res.status(500).send({ status: 'error', message: `An internal error occurred while processing '${computationName}'.` });
|