bulltrackers-module 1.0.105 → 1.0.107
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/README.MD +222 -222
- package/functions/appscript-api/helpers/errors.js +19 -19
- package/functions/appscript-api/index.js +58 -58
- package/functions/computation-system/helpers/orchestration_helpers.js +667 -113
- package/functions/computation-system/utils/data_loader.js +191 -191
- package/functions/computation-system/utils/utils.js +149 -254
- package/functions/core/utils/firestore_utils.js +433 -433
- package/functions/core/utils/pubsub_utils.js +53 -53
- package/functions/dispatcher/helpers/dispatch_helpers.js +47 -47
- package/functions/dispatcher/index.js +52 -52
- package/functions/etoro-price-fetcher/helpers/handler_helpers.js +124 -124
- package/functions/fetch-insights/helpers/handler_helpers.js +91 -91
- package/functions/generic-api/helpers/api_helpers.js +379 -379
- package/functions/generic-api/index.js +150 -150
- package/functions/invalid-speculator-handler/helpers/handler_helpers.js +75 -75
- package/functions/orchestrator/helpers/discovery_helpers.js +226 -226
- package/functions/orchestrator/helpers/update_helpers.js +92 -92
- package/functions/orchestrator/index.js +147 -147
- package/functions/price-backfill/helpers/handler_helpers.js +116 -123
- package/functions/social-orchestrator/helpers/orchestrator_helpers.js +61 -61
- package/functions/social-task-handler/helpers/handler_helpers.js +288 -288
- package/functions/task-engine/handler_creator.js +78 -78
- package/functions/task-engine/helpers/discover_helpers.js +125 -125
- package/functions/task-engine/helpers/update_helpers.js +118 -118
- package/functions/task-engine/helpers/verify_helpers.js +162 -162
- package/functions/task-engine/utils/firestore_batch_manager.js +258 -258
- package/index.js +105 -113
- package/package.json +45 -45
- package/functions/computation-system/computation_dependencies.json +0 -120
- package/functions/computation-system/helpers/worker_helpers.js +0 -340
- package/functions/computation-system/utils/computation_state_manager.js +0 -178
- package/functions/computation-system/utils/dependency_graph.js +0 -191
- package/functions/speculator-cleanup-orchestrator/helpers/cleanup_helpers.js +0 -160
|
@@ -1,151 +1,151 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Main entry point for the Generic API module.
|
|
3
|
-
* Exports the 'createApiApp' main pipe function.
|
|
4
|
-
* REFACTORED: /manifest endpoint now uses a dynamic "test harness"
|
|
5
|
-
* to generate the schema without modifying calculation files.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const express = require('express');
|
|
9
|
-
const cors = require('cors');
|
|
10
|
-
const { FieldPath } = require('@google-cloud/firestore');
|
|
11
|
-
const {
|
|
12
|
-
buildCalculationMap,
|
|
13
|
-
createApiHandler,
|
|
14
|
-
getComputationStructure,
|
|
15
|
-
getDynamicSchema // <-- IMPORT NEW HELPER
|
|
16
|
-
} = require('./helpers/api_helpers.js');
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Main pipe: pipe.api.createApiApp
|
|
20
|
-
* Creates and configures the Express app for the Generic API.
|
|
21
|
-
* @param {object} config - The Generic API V2 configuration object.
|
|
22
|
-
* @param {object} dependencies - Shared dependencies { db, logger }.
|
|
23
|
-
* @param {Object} unifiedCalculations - The calculations manifest from 'aiden-shared-calculations-unified'.
|
|
24
|
-
* @returns {express.Application} The configured Express app.
|
|
25
|
-
*/
|
|
26
|
-
function createApiApp(config, dependencies, unifiedCalculations) {
|
|
27
|
-
const app = express();
|
|
28
|
-
const { logger } = dependencies; // db is in dependencies
|
|
29
|
-
|
|
30
|
-
// --- Pre-compute Calculation Map ---
|
|
31
|
-
const calcMap = buildCalculationMap(unifiedCalculations);
|
|
32
|
-
|
|
33
|
-
// --- Middleware ---
|
|
34
|
-
app.use(cors({ origin: true }));
|
|
35
|
-
app.use(express.json());
|
|
36
|
-
|
|
37
|
-
// --- Main API Endpoint ---
|
|
38
|
-
app.get('/', createApiHandler(config, dependencies, calcMap));
|
|
39
|
-
|
|
40
|
-
// --- Health Check Endpoint ---
|
|
41
|
-
app.get('/health', (req, res) => {
|
|
42
|
-
res.status(200).send('OK');
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
// --- Debug Endpoint to list all computation keys ---
|
|
46
|
-
app.get('/list-computations', (req, res) => {
|
|
47
|
-
try {
|
|
48
|
-
const computationKeys = Object.keys(calcMap);
|
|
49
|
-
res.status(200).send({
|
|
50
|
-
status: 'success',
|
|
51
|
-
count: computationKeys.length,
|
|
52
|
-
computations: computationKeys.sort(),
|
|
53
|
-
});
|
|
54
|
-
} catch (error) {
|
|
55
|
-
logger.log('ERROR', 'API /list-computations failed.', { errorMessage: error.message });
|
|
56
|
-
res.status(500).send({ status: 'error', message: 'An internal error occurred.' });
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
// --- Debug Endpoint to get *stored* structure from Firestore ---
|
|
61
|
-
app.get('/structure/:computationName', async (req, res) => {
|
|
62
|
-
const { computationName } = req.params;
|
|
63
|
-
|
|
64
|
-
// Call sub-pipe, passing dependencies
|
|
65
|
-
const result = await getComputationStructure(computationName, calcMap, config, dependencies);
|
|
66
|
-
|
|
67
|
-
if (result.status === 'error') {
|
|
68
|
-
const statusCode = result.message.includes('not found') ? 404 : 500;
|
|
69
|
-
return res.status(statusCode).send(result);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
res.status(200).send(result);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
// --- NEW: Fully Refactored Manifest Endpoint (Dynamic Test Run) ---
|
|
76
|
-
app.get('/manifest', async (req, res) => {
|
|
77
|
-
logger.log('INFO', 'API /manifest dynamic generation starting...');
|
|
78
|
-
try {
|
|
79
|
-
const manifest = {};
|
|
80
|
-
let successCount = 0;
|
|
81
|
-
const errors = [];
|
|
82
|
-
|
|
83
|
-
// This logic iterates through the calculation module structure
|
|
84
|
-
for (const category in unifiedCalculations) {
|
|
85
|
-
for (const subKey in unifiedCalculations[category]) {
|
|
86
|
-
const item = unifiedCalculations[category][subKey];
|
|
87
|
-
let calcName = null;
|
|
88
|
-
let CalcClass = null;
|
|
89
|
-
|
|
90
|
-
// Handle nested 'historical' directory
|
|
91
|
-
if (subKey === 'historical' && typeof item === 'object') {
|
|
92
|
-
for (const name in item) {
|
|
93
|
-
calcName = name;
|
|
94
|
-
CalcClass = item[name];
|
|
95
|
-
if (CalcClass && typeof CalcClass === 'function') {
|
|
96
|
-
try {
|
|
97
|
-
manifest[calcName] = {
|
|
98
|
-
category: category,
|
|
99
|
-
structure: await getDynamicSchema(CalcClass, calcName) // <-- DYNAMIC CALL
|
|
100
|
-
};
|
|
101
|
-
successCount++;
|
|
102
|
-
} catch (e) { errors.push(`${category}/${calcName}: ${e.message}`); }
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
// Handle regular calc at root of category
|
|
107
|
-
else if (typeof item === 'function') {
|
|
108
|
-
calcName = subKey;
|
|
109
|
-
CalcClass = item;
|
|
110
|
-
if (CalcClass && typeof CalcClass === 'function') {
|
|
111
|
-
try {
|
|
112
|
-
manifest[calcName] = {
|
|
113
|
-
category: category,
|
|
114
|
-
structure: await getDynamicSchema(CalcClass, calcName) // <-- DYNAMIC CALL
|
|
115
|
-
};
|
|
116
|
-
successCount++;
|
|
117
|
-
} catch (e) { errors.push(`${category}/${calcName}: ${e.message}`); }
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const totalComputations = Object.keys(calcMap).length;
|
|
124
|
-
logger.log('INFO', `API /manifest complete. Generated schema for ${successCount}/${totalComputations} computations.`);
|
|
125
|
-
|
|
126
|
-
res.status(200).send({
|
|
127
|
-
status: 'success',
|
|
128
|
-
summary: {
|
|
129
|
-
source: 'computation_module_dynamic_test',
|
|
130
|
-
totalComputations: totalComputations,
|
|
131
|
-
schemasGenerated: successCount,
|
|
132
|
-
schemasFailed: errors.length,
|
|
133
|
-
},
|
|
134
|
-
manifest: manifest,
|
|
135
|
-
errors: errors.length > 0 ? errors : undefined,
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
} catch (error) {
|
|
139
|
-
logger.log('ERROR', `API /manifest failed unexpectedly.`, { errorMessage: error.message, stack: error.stack });
|
|
140
|
-
res.status(500).send({ status: 'error', message: `An internal error occurred while building the manifest.` });
|
|
141
|
-
}
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
return app;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
module.exports = {
|
|
148
|
-
createApiApp,
|
|
149
|
-
// Exporting helpers so they can be part of the pipe.api.helpers object
|
|
150
|
-
helpers: require('./helpers/api_helpers'),
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Main entry point for the Generic API module.
|
|
3
|
+
* Exports the 'createApiApp' main pipe function.
|
|
4
|
+
* REFACTORED: /manifest endpoint now uses a dynamic "test harness"
|
|
5
|
+
* to generate the schema without modifying calculation files.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const express = require('express');
|
|
9
|
+
const cors = require('cors');
|
|
10
|
+
const { FieldPath } = require('@google-cloud/firestore');
|
|
11
|
+
const {
|
|
12
|
+
buildCalculationMap,
|
|
13
|
+
createApiHandler,
|
|
14
|
+
getComputationStructure,
|
|
15
|
+
getDynamicSchema // <-- IMPORT NEW HELPER
|
|
16
|
+
} = require('./helpers/api_helpers.js');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Main pipe: pipe.api.createApiApp
|
|
20
|
+
* Creates and configures the Express app for the Generic API.
|
|
21
|
+
* @param {object} config - The Generic API V2 configuration object.
|
|
22
|
+
* @param {object} dependencies - Shared dependencies { db, logger }.
|
|
23
|
+
* @param {Object} unifiedCalculations - The calculations manifest from 'aiden-shared-calculations-unified'.
|
|
24
|
+
* @returns {express.Application} The configured Express app.
|
|
25
|
+
*/
|
|
26
|
+
function createApiApp(config, dependencies, unifiedCalculations) {
|
|
27
|
+
const app = express();
|
|
28
|
+
const { logger } = dependencies; // db is in dependencies
|
|
29
|
+
|
|
30
|
+
// --- Pre-compute Calculation Map ---
|
|
31
|
+
const calcMap = buildCalculationMap(unifiedCalculations);
|
|
32
|
+
|
|
33
|
+
// --- Middleware ---
|
|
34
|
+
app.use(cors({ origin: true }));
|
|
35
|
+
app.use(express.json());
|
|
36
|
+
|
|
37
|
+
// --- Main API Endpoint ---
|
|
38
|
+
app.get('/', createApiHandler(config, dependencies, calcMap));
|
|
39
|
+
|
|
40
|
+
// --- Health Check Endpoint ---
|
|
41
|
+
app.get('/health', (req, res) => {
|
|
42
|
+
res.status(200).send('OK');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// --- Debug Endpoint to list all computation keys ---
|
|
46
|
+
app.get('/list-computations', (req, res) => {
|
|
47
|
+
try {
|
|
48
|
+
const computationKeys = Object.keys(calcMap);
|
|
49
|
+
res.status(200).send({
|
|
50
|
+
status: 'success',
|
|
51
|
+
count: computationKeys.length,
|
|
52
|
+
computations: computationKeys.sort(),
|
|
53
|
+
});
|
|
54
|
+
} catch (error) {
|
|
55
|
+
logger.log('ERROR', 'API /list-computations failed.', { errorMessage: error.message });
|
|
56
|
+
res.status(500).send({ status: 'error', message: 'An internal error occurred.' });
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// --- Debug Endpoint to get *stored* structure from Firestore ---
|
|
61
|
+
app.get('/structure/:computationName', async (req, res) => {
|
|
62
|
+
const { computationName } = req.params;
|
|
63
|
+
|
|
64
|
+
// Call sub-pipe, passing dependencies
|
|
65
|
+
const result = await getComputationStructure(computationName, calcMap, config, dependencies);
|
|
66
|
+
|
|
67
|
+
if (result.status === 'error') {
|
|
68
|
+
const statusCode = result.message.includes('not found') ? 404 : 500;
|
|
69
|
+
return res.status(statusCode).send(result);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
res.status(200).send(result);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// --- NEW: Fully Refactored Manifest Endpoint (Dynamic Test Run) ---
|
|
76
|
+
app.get('/manifest', async (req, res) => {
|
|
77
|
+
logger.log('INFO', 'API /manifest dynamic generation starting...');
|
|
78
|
+
try {
|
|
79
|
+
const manifest = {};
|
|
80
|
+
let successCount = 0;
|
|
81
|
+
const errors = [];
|
|
82
|
+
|
|
83
|
+
// This logic iterates through the calculation module structure
|
|
84
|
+
for (const category in unifiedCalculations) {
|
|
85
|
+
for (const subKey in unifiedCalculations[category]) {
|
|
86
|
+
const item = unifiedCalculations[category][subKey];
|
|
87
|
+
let calcName = null;
|
|
88
|
+
let CalcClass = null;
|
|
89
|
+
|
|
90
|
+
// Handle nested 'historical' directory
|
|
91
|
+
if (subKey === 'historical' && typeof item === 'object') {
|
|
92
|
+
for (const name in item) {
|
|
93
|
+
calcName = name;
|
|
94
|
+
CalcClass = item[name];
|
|
95
|
+
if (CalcClass && typeof CalcClass === 'function') {
|
|
96
|
+
try {
|
|
97
|
+
manifest[calcName] = {
|
|
98
|
+
category: category,
|
|
99
|
+
structure: await getDynamicSchema(CalcClass, calcName) // <-- DYNAMIC CALL
|
|
100
|
+
};
|
|
101
|
+
successCount++;
|
|
102
|
+
} catch (e) { errors.push(`${category}/${calcName}: ${e.message}`); }
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// Handle regular calc at root of category
|
|
107
|
+
else if (typeof item === 'function') {
|
|
108
|
+
calcName = subKey;
|
|
109
|
+
CalcClass = item;
|
|
110
|
+
if (CalcClass && typeof CalcClass === 'function') {
|
|
111
|
+
try {
|
|
112
|
+
manifest[calcName] = {
|
|
113
|
+
category: category,
|
|
114
|
+
structure: await getDynamicSchema(CalcClass, calcName) // <-- DYNAMIC CALL
|
|
115
|
+
};
|
|
116
|
+
successCount++;
|
|
117
|
+
} catch (e) { errors.push(`${category}/${calcName}: ${e.message}`); }
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const totalComputations = Object.keys(calcMap).length;
|
|
124
|
+
logger.log('INFO', `API /manifest complete. Generated schema for ${successCount}/${totalComputations} computations.`);
|
|
125
|
+
|
|
126
|
+
res.status(200).send({
|
|
127
|
+
status: 'success',
|
|
128
|
+
summary: {
|
|
129
|
+
source: 'computation_module_dynamic_test',
|
|
130
|
+
totalComputations: totalComputations,
|
|
131
|
+
schemasGenerated: successCount,
|
|
132
|
+
schemasFailed: errors.length,
|
|
133
|
+
},
|
|
134
|
+
manifest: manifest,
|
|
135
|
+
errors: errors.length > 0 ? errors : undefined,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
} catch (error) {
|
|
139
|
+
logger.log('ERROR', `API /manifest failed unexpectedly.`, { errorMessage: error.message, stack: error.stack });
|
|
140
|
+
res.status(500).send({ status: 'error', message: `An internal error occurred while building the manifest.` });
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
return app;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
module.exports = {
|
|
148
|
+
createApiApp,
|
|
149
|
+
// Exporting helpers so they can be part of the pipe.api.helpers object
|
|
150
|
+
helpers: require('./helpers/api_helpers'),
|
|
151
151
|
};
|
|
@@ -1,75 +1,75 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Main pipe: pipe.maintenance.handleInvalidSpeculator
|
|
3
|
-
* REFACTORED: Now stateless and receives dependencies.
|
|
4
|
-
*/
|
|
5
|
-
const { FieldValue } = require('@google-cloud/firestore');
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Main pipe: pipe.maintenance.handleInvalidSpeculator
|
|
9
|
-
* @param {object} message The Pub/Sub message.
|
|
10
|
-
* @param {object} context The message context.
|
|
11
|
-
* @param {object} config The configuration object.
|
|
12
|
-
* @param {object} dependencies - Contains db, logger.
|
|
13
|
-
*/
|
|
14
|
-
exports.handleInvalidSpeculator = async (message, context, config, dependencies) => {
|
|
15
|
-
const { db, logger } = dependencies;
|
|
16
|
-
try {
|
|
17
|
-
const { invalidCids } = JSON.parse(Buffer.from(message.data, 'base64').toString());
|
|
18
|
-
|
|
19
|
-
if (!invalidCids || invalidCids.length === 0) {
|
|
20
|
-
logger.log('WARN', 'Received message with no invalid CIDs. Nothing to do.');
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const collectionRef = db.collection(config.invalidSpeculatorsCollectionName); // Use db
|
|
25
|
-
const maxPerDoc = config.maxInvalidUsersPerDoc;
|
|
26
|
-
|
|
27
|
-
const querySnapshot = await collectionRef
|
|
28
|
-
.where('userCount', '<', maxPerDoc)
|
|
29
|
-
.limit(10)
|
|
30
|
-
.get();
|
|
31
|
-
|
|
32
|
-
let targetDocRef;
|
|
33
|
-
|
|
34
|
-
if (!querySnapshot.empty) {
|
|
35
|
-
const randomIndex = Math.floor(Math.random() * querySnapshot.docs.length);
|
|
36
|
-
targetDocRef = querySnapshot.docs[randomIndex].ref;
|
|
37
|
-
} else {
|
|
38
|
-
targetDocRef = collectionRef.doc();
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Use db from dependencies
|
|
42
|
-
await db.runTransaction(async (transaction) => {
|
|
43
|
-
const doc = await transaction.get(targetDocRef);
|
|
44
|
-
|
|
45
|
-
const updates = {};
|
|
46
|
-
let newUsersCount = 0;
|
|
47
|
-
|
|
48
|
-
if (!doc.exists) {
|
|
49
|
-
updates.userCount = 0;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
for (const cid of invalidCids) {
|
|
53
|
-
const fieldPath = `users.${cid}`;
|
|
54
|
-
if (!doc.exists || !doc.data().users || !doc.data().users[cid]) {
|
|
55
|
-
updates[fieldPath] = true;
|
|
56
|
-
newUsersCount++;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (newUsersCount > 0) {
|
|
61
|
-
updates.userCount = FieldValue.increment(newUsersCount);
|
|
62
|
-
transaction.set(targetDocRef, updates, { merge: true });
|
|
63
|
-
}
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
logger.log('SUCCESS', `Successfully stored ${invalidCids.length} invalid speculator IDs in document ${targetDocRef.id}.`);
|
|
67
|
-
|
|
68
|
-
} catch (error) {
|
|
69
|
-
logger.log('ERROR', 'FATAL Error in Invalid Speculator Handler', {
|
|
70
|
-
errorMessage: error.message,
|
|
71
|
-
errorStack: error.stack
|
|
72
|
-
});
|
|
73
|
-
throw error;
|
|
74
|
-
}
|
|
75
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Main pipe: pipe.maintenance.handleInvalidSpeculator
|
|
3
|
+
* REFACTORED: Now stateless and receives dependencies.
|
|
4
|
+
*/
|
|
5
|
+
const { FieldValue } = require('@google-cloud/firestore');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Main pipe: pipe.maintenance.handleInvalidSpeculator
|
|
9
|
+
* @param {object} message The Pub/Sub message.
|
|
10
|
+
* @param {object} context The message context.
|
|
11
|
+
* @param {object} config The configuration object.
|
|
12
|
+
* @param {object} dependencies - Contains db, logger.
|
|
13
|
+
*/
|
|
14
|
+
exports.handleInvalidSpeculator = async (message, context, config, dependencies) => {
|
|
15
|
+
const { db, logger } = dependencies;
|
|
16
|
+
try {
|
|
17
|
+
const { invalidCids } = JSON.parse(Buffer.from(message.data, 'base64').toString());
|
|
18
|
+
|
|
19
|
+
if (!invalidCids || invalidCids.length === 0) {
|
|
20
|
+
logger.log('WARN', 'Received message with no invalid CIDs. Nothing to do.');
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const collectionRef = db.collection(config.invalidSpeculatorsCollectionName); // Use db
|
|
25
|
+
const maxPerDoc = config.maxInvalidUsersPerDoc;
|
|
26
|
+
|
|
27
|
+
const querySnapshot = await collectionRef
|
|
28
|
+
.where('userCount', '<', maxPerDoc)
|
|
29
|
+
.limit(10)
|
|
30
|
+
.get();
|
|
31
|
+
|
|
32
|
+
let targetDocRef;
|
|
33
|
+
|
|
34
|
+
if (!querySnapshot.empty) {
|
|
35
|
+
const randomIndex = Math.floor(Math.random() * querySnapshot.docs.length);
|
|
36
|
+
targetDocRef = querySnapshot.docs[randomIndex].ref;
|
|
37
|
+
} else {
|
|
38
|
+
targetDocRef = collectionRef.doc();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Use db from dependencies
|
|
42
|
+
await db.runTransaction(async (transaction) => {
|
|
43
|
+
const doc = await transaction.get(targetDocRef);
|
|
44
|
+
|
|
45
|
+
const updates = {};
|
|
46
|
+
let newUsersCount = 0;
|
|
47
|
+
|
|
48
|
+
if (!doc.exists) {
|
|
49
|
+
updates.userCount = 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
for (const cid of invalidCids) {
|
|
53
|
+
const fieldPath = `users.${cid}`;
|
|
54
|
+
if (!doc.exists || !doc.data().users || !doc.data().users[cid]) {
|
|
55
|
+
updates[fieldPath] = true;
|
|
56
|
+
newUsersCount++;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (newUsersCount > 0) {
|
|
61
|
+
updates.userCount = FieldValue.increment(newUsersCount);
|
|
62
|
+
transaction.set(targetDocRef, updates, { merge: true });
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
logger.log('SUCCESS', `Successfully stored ${invalidCids.length} invalid speculator IDs in document ${targetDocRef.id}.`);
|
|
67
|
+
|
|
68
|
+
} catch (error) {
|
|
69
|
+
logger.log('ERROR', 'FATAL Error in Invalid Speculator Handler', {
|
|
70
|
+
errorMessage: error.message,
|
|
71
|
+
errorStack: error.stack
|
|
72
|
+
});
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
};
|