bulltrackers-module 1.0.11 → 1.0.12

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.
@@ -0,0 +1,156 @@
1
+ /**
2
+ * @fileoverview Core helper logic for the Generic API.
3
+ * Contains validation, data fetching, and request handling.
4
+ */
5
+
6
+ const { FieldPath } = require('@google-cloud/firestore');
7
+ const MAX_DATE_RANGE = 100; // Limit queries to a 100-day range
8
+
9
+ /**
10
+ * Validates request parameters.
11
+ * @param {object} query - The request query object.
12
+ * @returns {string|null} An error message if validation fails, otherwise null.
13
+ */
14
+ const validateRequest = (query) => {
15
+ if (!query.computations) return "Missing 'computations' parameter.";
16
+ if (!query.startDate || !/^\d{4}-\d{2}-\d{2}$/.test(query.startDate)) return "Missing or invalid 'startDate'.";
17
+ if (!query.endDate || !/^\d{4}-\d{2}-\d{2}$/.test(query.endDate)) return "Missing or invalid 'endDate'.";
18
+
19
+ const start = new Date(query.startDate);
20
+ const end = new Date(query.endDate);
21
+ if (end < start) return "'endDate' must be after 'startDate'.";
22
+
23
+ const diffTime = Math.abs(end - start);
24
+ const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)) + 1;
25
+ if (diffDays > MAX_DATE_RANGE) return `Date range cannot exceed ${MAX_DATE_RANGE} days.`;
26
+
27
+ return null;
28
+ };
29
+
30
+ /**
31
+ * Flattens the calculation manifest into a simple lookup map.
32
+ * @param {Object} unifiedCalculations - The imported calculations object.
33
+ * @returns {Object} A map of { [calcName]: { category, path } }.
34
+ */
35
+ const buildCalculationMap = (unifiedCalculations) => {
36
+ const calcMap = {};
37
+ for (const category in unifiedCalculations) {
38
+ for (const calcName in unifiedCalculations[category]) {
39
+ calcMap[calcName] = {
40
+ category: category,
41
+ path: `unified_insights/{date}/results/${category}/computations/${calcName}`
42
+ };
43
+ }
44
+ }
45
+ return calcMap;
46
+ };
47
+
48
+ /**
49
+ * Generates an array of YYYY-MM-DD date strings between two dates.
50
+ * @param {string} startDate - The start date (YYYY-MM-DD).
51
+ * @param {string} endDate - The end date (YYYY-MM-DD).
52
+ * @returns {string[]} An array of date strings.
53
+ */
54
+ const getDateStringsInRange = (startDate, endDate) => {
55
+ const dates = [];
56
+ const current = new Date(startDate + 'T00:00:00Z');
57
+ const end = new Date(endDate + 'T00:00:00Z');
58
+
59
+ while (current <= end) {
60
+ dates.push(current.toISOString().slice(0, 10));
61
+ current.setUTCDate(current.getUTCDate() + 1);
62
+ }
63
+ return dates;
64
+ };
65
+
66
+ /**
67
+ * Fetches all requested computation data from Firestore for the given date range.
68
+ * @param {Firestore} firestore - A Firestore client instance.
69
+ * @param {Logger} logger - A logger instance.
70
+ * @param {string[]} calcKeys - Array of computation keys to fetch.
71
+ * @param {string[]} dateStrings - Array of dates to fetch for.
72
+ * @param {Object} calcMap - The pre-built calculation lookup map.
73
+ * @returns {Promise<Object>} A nested object of [date][computationKey] = data.
74
+ */
75
+ const fetchUnifiedData = async (firestore, logger, calcKeys, dateStrings, calcMap) => {
76
+ const response = {};
77
+ try {
78
+ for (const date of dateStrings) {
79
+ response[date] = {};
80
+ const docRefs = [];
81
+ const keyPaths = [];
82
+
83
+ for (const key of calcKeys) {
84
+ const pathInfo = calcMap[key];
85
+ if (pathInfo) {
86
+ const docRef = firestore.collection('unified_insights').doc(date)
87
+ .collection('results').doc(pathInfo.category)
88
+ .collection('computations').doc(key);
89
+
90
+ docRefs.push(docRef);
91
+ keyPaths.push(key);
92
+ } else {
93
+ logger.log('WARN', `[${date}] No path found for computation key: ${key}`);
94
+ }
95
+ }
96
+
97
+ if (docRefs.length === 0) continue;
98
+
99
+ const snapshots = await firestore.getAll(...docRefs);
100
+ snapshots.forEach((doc, i) => {
101
+ const key = keyPaths[i];
102
+ if (doc.exists) {
103
+ response[date][key] = doc.data();
104
+ } else {
105
+ response[date][key] = null; // Indicate missing data
106
+ }
107
+ });
108
+ }
109
+ } catch (error) {
110
+ logger.log('ERROR', 'API: Error fetching data from Firestore.', { errorMessage: error.message });
111
+ throw new Error('Failed to retrieve computation data.');
112
+ }
113
+ return response;
114
+ };
115
+
116
+ /**
117
+ * Creates the main Express request handler for the API.
118
+ * @param {Firestore} firestore - A Firestore client instance.
119
+ * @param {Logger} logger - A logger instance.
120
+ * @param {Object} calcMap - The pre-built calculation lookup map.
121
+ * @returns {Function} An async Express request handler.
122
+ */
123
+ const createApiHandler = (firestore, logger, calcMap) => {
124
+ return async (req, res) => {
125
+ const validationError = validateRequest(req.query);
126
+ if (validationError) {
127
+ logger.log('WARN', 'Bad Request', { error: validationError });
128
+ return res.status(400).send({ status: 'error', message: validationError });
129
+ }
130
+
131
+ try {
132
+ const computationKeys = req.query.computations.split(',');
133
+ const dateStrings = getDateStringsInRange(req.query.startDate, req.query.endDate);
134
+
135
+ const data = await fetchUnifiedData(firestore, logger, computationKeys, dateStrings, calcMap);
136
+
137
+ res.status(200).send({
138
+ status: 'success',
139
+ metadata: {
140
+ computations: computationKeys,
141
+ startDate: req.query.startDate,
142
+ endDate: req.query.endDate,
143
+ },
144
+ data,
145
+ });
146
+ } catch (error) {
147
+ logger.log('ERROR', 'API processing failed.', { errorMessage: error.message, stack: error.stack });
148
+ res.status(500).send({ status: 'error', message: 'An internal error occurred.' });
149
+ }
150
+ };
151
+ };
152
+
153
+ module.exports = {
154
+ buildCalculationMap,
155
+ createApiHandler,
156
+ };
@@ -0,0 +1,41 @@
1
+ /**
2
+ * @fileoverview Main entry point for the Generic API module.
3
+ * Exports a function to create the fully configured Express app.
4
+ */
5
+
6
+ const express = require('express');
7
+ const cors = require('cors');
8
+ const { buildCalculationMap, createApiHandler } = require('./helpers/api_helpers.js');
9
+
10
+ /**
11
+ * Creates and configures the Express app for the Generic API.
12
+ * @param {Firestore} firestore A Firestore client instance.
13
+ * @param {Logger} logger A logger instance.
14
+ * @param {Object} unifiedCalculations The calculations manifest.
15
+ * @returns {express.Application} The configured Express app.
16
+ */
17
+ function createApiApp(firestore, logger, unifiedCalculations) {
18
+ const app = express();
19
+
20
+ // --- Pre-compute Calculation Map ---
21
+ const calcMap = buildCalculationMap(unifiedCalculations);
22
+
23
+ // --- Middleware ---
24
+ app.use(cors({ origin: true }));
25
+ app.use(express.json());
26
+
27
+ // --- Main API Endpoint ---
28
+ // The handler is created with its dependencies injected
29
+ app.get('/', createApiHandler(firestore, logger, calcMap));
30
+
31
+ // --- Health Check Endpoint ---
32
+ app.get('/health', (req, res) => {
33
+ res.status(200).send('OK');
34
+ });
35
+
36
+ return app;
37
+ }
38
+
39
+ module.exports = {
40
+ createApiApp,
41
+ };
package/index.js CHANGED
@@ -7,11 +7,13 @@
7
7
  const core = require('./functions/core');
8
8
  const Orchestrator = require('./functions/orchestrator');
9
9
  const TaskEngine = require('./functions/task-engine');
10
- const ComputationSystem = require('./functions/computation-system'); // Add this
10
+ const ComputationSystem = require('./functions/computation-system');
11
+ const GenericAPI = require('./functions/generic-api'); // <-- ADD THIS
11
12
 
12
13
  module.exports = {
13
14
  core,
14
15
  Orchestrator,
15
16
  TaskEngine,
16
- ComputationSystem, // Add this
17
+ ComputationSystem,
18
+ GenericAPI, // <-- AND ADD THIS
17
19
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.11",
3
+ "version": "1.0.12",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -8,7 +8,8 @@
8
8
  "functions/orchestrator/",
9
9
  "functions/task-engine/",
10
10
  "functions/core/",
11
- "functions/computation-system/"
11
+ "functions/computation-system/",
12
+ "functions/generic-api/"
12
13
  ],
13
14
  "scripts": {
14
15
  "test": "echo \"Error: no test specified\" && exit 1"
@@ -25,7 +26,9 @@
25
26
  "sharedsetup": "latest",
26
27
  "require-all": "^3.0.0",
27
28
  "aiden-shared-calculations-unified": "1.0.0",
28
- "@google-cloud/pubsub": "latest"
29
+ "@google-cloud/pubsub": "latest",
30
+ "express": "^4.19.2",
31
+ "cors": "^2.8.5"
29
32
  },
30
33
  "engines": {
31
34
  "node": ">=20"