bulltrackers-module 1.0.428 → 1.0.429
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.
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
* @fileoverview Handles saving computation results with observability and Smart Cleanup.
|
|
3
3
|
* UPDATED: Tracks specific Firestore Ops (Writes/Deletes) for cost analysis.
|
|
4
4
|
* UPDATED: Added Dynamic Circuit Breaker Bypass for Price-Only calculations.
|
|
5
|
+
* UPDATED: Integrated Alert System Pub/Sub triggers for marked computations.
|
|
5
6
|
*/
|
|
6
7
|
const { commitBatchInChunks, generateDataHash } = require('../utils/utils');
|
|
7
8
|
const { updateComputationStatus } = require('./StatusRepository');
|
|
8
9
|
const { batchStoreSchemas } = require('../utils/schema_capture');
|
|
9
10
|
const { generateProcessId, PROCESS_TYPES } = require('../logger/logger');
|
|
10
11
|
const { HeuristicValidator } = require('./ResultsValidator');
|
|
12
|
+
const { PubSubUtils } = require('../../core/utils/pubsub_utils');
|
|
11
13
|
const ContractValidator = require('./ContractValidator');
|
|
12
14
|
const validationOverrides = require('../config/validation_overrides');
|
|
13
15
|
const pLimit = require('p-limit');
|
|
@@ -22,6 +24,7 @@ async function commitResults(stateObj, dStr, passName, config, deps, skipStatusW
|
|
|
22
24
|
const failureReport = [];
|
|
23
25
|
const schemas = [];
|
|
24
26
|
const cleanupTasks = [];
|
|
27
|
+
const alertTriggers = []; // [NEW] Collect alert triggers
|
|
25
28
|
const { logger, db } = deps;
|
|
26
29
|
const pid = generateProcessId(PROCESS_TYPES.STORAGE, passName, dStr);
|
|
27
30
|
|
|
@@ -30,6 +33,7 @@ async function commitResults(stateObj, dStr, passName, config, deps, skipStatusW
|
|
|
30
33
|
const shardIndexes = options.shardIndexes || {};
|
|
31
34
|
const nextShardIndexes = {};
|
|
32
35
|
const fanOutLimit = pLimit(10);
|
|
36
|
+
const pubSubUtils = new PubSubUtils(deps);
|
|
33
37
|
|
|
34
38
|
// 1. [BATCH OPTIMIZATION] Fetch all SimHashes and Contracts upfront
|
|
35
39
|
const calcNames = Object.keys(stateObj);
|
|
@@ -61,14 +65,11 @@ async function commitResults(stateObj, dStr, passName, config, deps, skipStatusW
|
|
|
61
65
|
const isPriceOnly = (dataDeps.length === 1 && dataDeps[0] === 'price');
|
|
62
66
|
let effectiveOverrides = { ...configOverrides };
|
|
63
67
|
|
|
64
|
-
// If a calculation relies solely on price, we strictly bypass the circuit breaker.
|
|
65
|
-
// This handles Holidays (Weekday 0s), Weekends (Scheduler Gaps), and Crypto/Stock mix.
|
|
66
68
|
if (isPriceOnly) {
|
|
67
69
|
effectiveOverrides.maxZeroPct = 100;
|
|
68
70
|
effectiveOverrides.maxFlatlinePct = 100;
|
|
69
71
|
effectiveOverrides.maxNullPct = 100;
|
|
70
72
|
effectiveOverrides.maxNanPct = 100;
|
|
71
|
-
// Remove specific weekend config to ensure these permissive global limits apply 7 days/week
|
|
72
73
|
delete effectiveOverrides.weekend;
|
|
73
74
|
}
|
|
74
75
|
// --- [DYNAMIC OVERRIDE LOGIC END] ---
|
|
@@ -99,7 +100,6 @@ async function commitResults(stateObj, dStr, passName, config, deps, skipStatusW
|
|
|
99
100
|
const isEmpty = !result || (typeof result === 'object' && Object.keys(result).length === 0);
|
|
100
101
|
const resultHash = isEmpty ? 'empty' : generateDataHash(result);
|
|
101
102
|
|
|
102
|
-
// [NEW] Use pre-fetched SimHash
|
|
103
103
|
const simHash = (flushMode !== 'INTERMEDIATE') ? (simHashMap[calc.manifest.hash] || null) : null;
|
|
104
104
|
|
|
105
105
|
if (isEmpty) {
|
|
@@ -119,6 +119,9 @@ async function commitResults(stateObj, dStr, passName, config, deps, skipStatusW
|
|
|
119
119
|
const resultKeys = Object.keys(result || {});
|
|
120
120
|
const isMultiDate = resultKeys.length > 0 && resultKeys.every(k => /^\d{4}-\d{2}-\d{2}$/.test(k));
|
|
121
121
|
|
|
122
|
+
// [NEW] Check metadata for alert flag (defaults to false)
|
|
123
|
+
const isAlertComputation = calc.manifest.isAlertComputation === true;
|
|
124
|
+
|
|
122
125
|
if (isMultiDate) {
|
|
123
126
|
const datePromises = resultKeys.map((historicalDate) => fanOutLimit(async () => {
|
|
124
127
|
const dailyData = result[historicalDate];
|
|
@@ -127,6 +130,15 @@ async function commitResults(stateObj, dStr, passName, config, deps, skipStatusW
|
|
|
127
130
|
const stats = await writeSingleResult(dailyData, historicalDocRef, name, historicalDate, logger, config, deps, 0, 'STANDARD', false);
|
|
128
131
|
runMetrics.io.writes += stats.opCounts.writes;
|
|
129
132
|
runMetrics.io.deletes += stats.opCounts.deletes;
|
|
133
|
+
|
|
134
|
+
// [NEW] Queue alert for historical date if applicable
|
|
135
|
+
if (isAlertComputation && flushMode !== 'INTERMEDIATE') {
|
|
136
|
+
alertTriggers.push({
|
|
137
|
+
date: historicalDate,
|
|
138
|
+
computationName: name,
|
|
139
|
+
documentPath: historicalDocRef.path
|
|
140
|
+
});
|
|
141
|
+
}
|
|
130
142
|
}));
|
|
131
143
|
await Promise.all(datePromises);
|
|
132
144
|
|
|
@@ -143,11 +155,19 @@ async function commitResults(stateObj, dStr, passName, config, deps, skipStatusW
|
|
|
143
155
|
|
|
144
156
|
nextShardIndexes[name] = writeStats.nextShardIndex;
|
|
145
157
|
if (calc.manifest.hash) { successUpdates[name] = { hash: calc.manifest.hash, simHash, resultHash, dependencyResultHashes: calc.manifest.dependencyResultHashes || {}, category: calc.manifest.category, composition: calc.manifest.composition, metrics: runMetrics }; }
|
|
158
|
+
|
|
159
|
+
// [NEW] Queue alert for standard write
|
|
160
|
+
if (isAlertComputation && flushMode !== 'INTERMEDIATE') {
|
|
161
|
+
alertTriggers.push({
|
|
162
|
+
date: dStr,
|
|
163
|
+
computationName: name,
|
|
164
|
+
documentPath: mainDocRef.path
|
|
165
|
+
});
|
|
166
|
+
}
|
|
146
167
|
}
|
|
147
168
|
|
|
148
169
|
if (calc.manifest.class.getSchema && flushMode !== 'INTERMEDIATE') {
|
|
149
170
|
const { class: _cls, ...safeMetadata } = calc.manifest;
|
|
150
|
-
// Remove undefined values to prevent Firestore errors
|
|
151
171
|
const cleanedMetadata = {};
|
|
152
172
|
for (const [key, value] of Object.entries(safeMetadata)) {
|
|
153
173
|
if (value !== undefined) {
|
|
@@ -172,6 +192,18 @@ async function commitResults(stateObj, dStr, passName, config, deps, skipStatusW
|
|
|
172
192
|
if (!skipStatusWrite && Object.keys(successUpdates).length > 0 && flushMode !== 'INTERMEDIATE') {
|
|
173
193
|
await updateComputationStatus(dStr, successUpdates, config, deps);
|
|
174
194
|
}
|
|
195
|
+
|
|
196
|
+
// [NEW] Flush Alert Triggers to Pub/Sub
|
|
197
|
+
if (alertTriggers.length > 0) {
|
|
198
|
+
const topicName = config.alertTopicName || 'alert-trigger';
|
|
199
|
+
try {
|
|
200
|
+
await pubSubUtils.publishMessageBatch(topicName, alertTriggers);
|
|
201
|
+
logger.log('INFO', `[Alert System] Triggered ${alertTriggers.length} alerts to ${topicName}`);
|
|
202
|
+
} catch (alertErr) {
|
|
203
|
+
logger.log('ERROR', `[Alert System] Failed to publish alerts: ${alertErr.message}`);
|
|
204
|
+
// Non-blocking: We don't fail the computation if alerts fail to publish, but we log strictly.
|
|
205
|
+
}
|
|
206
|
+
}
|
|
175
207
|
|
|
176
208
|
return { successUpdates, failureReport, shardIndexes: nextShardIndexes };
|
|
177
209
|
}
|
|
@@ -207,8 +239,6 @@ async function writeSingleResult(result, docRef, name, dateContext, logger, conf
|
|
|
207
239
|
if (isInitialWrite) {
|
|
208
240
|
try {
|
|
209
241
|
const currentSnap = await docRef.get();
|
|
210
|
-
// Note: Reads tracked implicitly by calling code or approximated here if needed.
|
|
211
|
-
// We focus on writes/deletes here.
|
|
212
242
|
if (currentSnap.exists) {
|
|
213
243
|
const d = currentSnap.data();
|
|
214
244
|
wasSharded = (d._sharded === true);
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Alert Management API Helpers
|
|
3
|
+
* Handles fetching and managing user alerts
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { FieldValue } = require('@google-cloud/firestore');
|
|
7
|
+
const { getAllAlertTypes, getAlertType } = require('../../../alert-system/helpers/alert_type_registry');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* GET /user/me/alert-types
|
|
11
|
+
* Get all available alert types
|
|
12
|
+
*/
|
|
13
|
+
async function getAlertTypes(req, res, dependencies, config) {
|
|
14
|
+
try {
|
|
15
|
+
const alertTypes = getAllAlertTypes();
|
|
16
|
+
|
|
17
|
+
return res.status(200).json({
|
|
18
|
+
success: true,
|
|
19
|
+
alertTypes: alertTypes.map(type => ({
|
|
20
|
+
id: type.id,
|
|
21
|
+
name: type.name,
|
|
22
|
+
description: type.description,
|
|
23
|
+
severity: type.severity
|
|
24
|
+
}))
|
|
25
|
+
});
|
|
26
|
+
} catch (error) {
|
|
27
|
+
const { logger } = dependencies;
|
|
28
|
+
logger.log('ERROR', '[getAlertTypes] Error fetching alert types', error);
|
|
29
|
+
return res.status(500).json({ error: error.message });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* GET /user/me/alerts
|
|
35
|
+
* Get user's alerts (paginated)
|
|
36
|
+
*/
|
|
37
|
+
async function getUserAlerts(req, res, dependencies, config) {
|
|
38
|
+
const { db, logger } = dependencies;
|
|
39
|
+
const { userCid } = req.query;
|
|
40
|
+
const { unreadOnly, limit = 50, offset = 0, alertType } = req.query;
|
|
41
|
+
|
|
42
|
+
if (!userCid) {
|
|
43
|
+
return res.status(400).json({ error: "Missing userCid" });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const alertsRef = db.collection('user_alerts')
|
|
48
|
+
.doc(String(userCid))
|
|
49
|
+
.collection('alerts');
|
|
50
|
+
|
|
51
|
+
let query = alertsRef.orderBy('createdAt', 'desc');
|
|
52
|
+
|
|
53
|
+
// Filter by read status
|
|
54
|
+
if (unreadOnly === 'true') {
|
|
55
|
+
query = query.where('read', '==', false);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Filter by alert type
|
|
59
|
+
if (alertType) {
|
|
60
|
+
query = query.where('alertType', '==', alertType);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Apply pagination
|
|
64
|
+
query = query.limit(Number(limit)).offset(Number(offset));
|
|
65
|
+
|
|
66
|
+
const snapshot = await query.get();
|
|
67
|
+
const alerts = [];
|
|
68
|
+
|
|
69
|
+
snapshot.forEach(doc => {
|
|
70
|
+
const data = doc.data();
|
|
71
|
+
alerts.push({
|
|
72
|
+
id: doc.id,
|
|
73
|
+
...data,
|
|
74
|
+
createdAt: data.createdAt?.toDate?.() || data.createdAt,
|
|
75
|
+
readAt: data.readAt?.toDate?.() || data.readAt
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
return res.status(200).json({
|
|
80
|
+
success: true,
|
|
81
|
+
alerts: alerts,
|
|
82
|
+
count: alerts.length,
|
|
83
|
+
limit: Number(limit),
|
|
84
|
+
offset: Number(offset)
|
|
85
|
+
});
|
|
86
|
+
} catch (error) {
|
|
87
|
+
logger.log('ERROR', `[getUserAlerts] Error fetching alerts for user ${userCid}`, error);
|
|
88
|
+
return res.status(500).json({ error: error.message });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* GET /user/me/alerts/count
|
|
94
|
+
* Get unread alert count
|
|
95
|
+
*/
|
|
96
|
+
async function getAlertCount(req, res, dependencies, config) {
|
|
97
|
+
const { db, logger } = dependencies;
|
|
98
|
+
const { userCid } = req.query;
|
|
99
|
+
|
|
100
|
+
if (!userCid) {
|
|
101
|
+
return res.status(400).json({ error: "Missing userCid" });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
// Get today's counter
|
|
106
|
+
const today = new Date().toISOString().split('T')[0];
|
|
107
|
+
const counterRef = db.collection('user_alerts')
|
|
108
|
+
.doc(String(userCid))
|
|
109
|
+
.collection('counters')
|
|
110
|
+
.doc(today);
|
|
111
|
+
|
|
112
|
+
const counterDoc = await counterRef.get();
|
|
113
|
+
|
|
114
|
+
if (counterDoc.exists) {
|
|
115
|
+
const data = counterDoc.data();
|
|
116
|
+
return res.status(200).json({
|
|
117
|
+
success: true,
|
|
118
|
+
unreadCount: data.unreadCount || 0,
|
|
119
|
+
totalCount: data.totalCount || 0,
|
|
120
|
+
byType: data.byType || {}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// If no counter exists, count unread alerts directly
|
|
125
|
+
const unreadSnapshot = await db.collection('user_alerts')
|
|
126
|
+
.doc(String(userCid))
|
|
127
|
+
.collection('alerts')
|
|
128
|
+
.where('read', '==', false)
|
|
129
|
+
.get();
|
|
130
|
+
|
|
131
|
+
return res.status(200).json({
|
|
132
|
+
success: true,
|
|
133
|
+
unreadCount: unreadSnapshot.size,
|
|
134
|
+
totalCount: unreadSnapshot.size,
|
|
135
|
+
byType: {}
|
|
136
|
+
});
|
|
137
|
+
} catch (error) {
|
|
138
|
+
logger.log('ERROR', `[getAlertCount] Error fetching alert count for user ${userCid}`, error);
|
|
139
|
+
return res.status(500).json({ error: error.message });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* PUT /user/me/alerts/:alertId/read
|
|
145
|
+
* Mark alert as read
|
|
146
|
+
*/
|
|
147
|
+
async function markAlertRead(req, res, dependencies, config) {
|
|
148
|
+
const { db, logger } = dependencies;
|
|
149
|
+
const { userCid } = req.query;
|
|
150
|
+
const { alertId } = req.params;
|
|
151
|
+
|
|
152
|
+
if (!userCid || !alertId) {
|
|
153
|
+
return res.status(400).json({ error: "Missing userCid or alertId" });
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
const alertRef = db.collection('user_alerts')
|
|
158
|
+
.doc(String(userCid))
|
|
159
|
+
.collection('alerts')
|
|
160
|
+
.doc(alertId);
|
|
161
|
+
|
|
162
|
+
const alertDoc = await alertRef.get();
|
|
163
|
+
|
|
164
|
+
if (!alertDoc.exists) {
|
|
165
|
+
return res.status(404).json({ error: "Alert not found" });
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const alertData = alertDoc.data();
|
|
169
|
+
|
|
170
|
+
// Only update if not already read
|
|
171
|
+
if (!alertData.read) {
|
|
172
|
+
await alertRef.update({
|
|
173
|
+
read: true,
|
|
174
|
+
readAt: FieldValue.serverTimestamp()
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Update counter
|
|
178
|
+
const today = new Date().toISOString().split('T')[0];
|
|
179
|
+
const counterRef = db.collection('user_alerts')
|
|
180
|
+
.doc(String(userCid))
|
|
181
|
+
.collection('counters')
|
|
182
|
+
.doc(today);
|
|
183
|
+
|
|
184
|
+
await counterRef.set({
|
|
185
|
+
unreadCount: FieldValue.increment(-1),
|
|
186
|
+
lastUpdated: FieldValue.serverTimestamp()
|
|
187
|
+
}, { merge: true });
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return res.status(200).json({
|
|
191
|
+
success: true,
|
|
192
|
+
message: "Alert marked as read"
|
|
193
|
+
});
|
|
194
|
+
} catch (error) {
|
|
195
|
+
logger.log('ERROR', `[markAlertRead] Error marking alert ${alertId} as read`, error);
|
|
196
|
+
return res.status(500).json({ error: error.message });
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* PUT /user/me/alerts/read-all
|
|
202
|
+
* Mark all alerts as read
|
|
203
|
+
*/
|
|
204
|
+
async function markAllAlertsRead(req, res, dependencies, config) {
|
|
205
|
+
const { db, logger } = dependencies;
|
|
206
|
+
const { userCid } = req.query;
|
|
207
|
+
|
|
208
|
+
if (!userCid) {
|
|
209
|
+
return res.status(400).json({ error: "Missing userCid" });
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
try {
|
|
213
|
+
const alertsRef = db.collection('user_alerts')
|
|
214
|
+
.doc(String(userCid))
|
|
215
|
+
.collection('alerts');
|
|
216
|
+
|
|
217
|
+
const unreadSnapshot = await alertsRef
|
|
218
|
+
.where('read', '==', false)
|
|
219
|
+
.get();
|
|
220
|
+
|
|
221
|
+
if (unreadSnapshot.empty) {
|
|
222
|
+
return res.status(200).json({
|
|
223
|
+
success: true,
|
|
224
|
+
message: "No unread alerts",
|
|
225
|
+
updated: 0
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const batch = db.batch();
|
|
230
|
+
let count = 0;
|
|
231
|
+
|
|
232
|
+
unreadSnapshot.forEach(doc => {
|
|
233
|
+
batch.update(doc.ref, {
|
|
234
|
+
read: true,
|
|
235
|
+
readAt: FieldValue.serverTimestamp()
|
|
236
|
+
});
|
|
237
|
+
count++;
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
await batch.commit();
|
|
241
|
+
|
|
242
|
+
// Reset counter
|
|
243
|
+
const today = new Date().toISOString().split('T')[0];
|
|
244
|
+
const counterRef = db.collection('user_alerts')
|
|
245
|
+
.doc(String(userCid))
|
|
246
|
+
.collection('counters')
|
|
247
|
+
.doc(today);
|
|
248
|
+
|
|
249
|
+
await counterRef.set({
|
|
250
|
+
unreadCount: 0,
|
|
251
|
+
lastUpdated: FieldValue.serverTimestamp()
|
|
252
|
+
}, { merge: true });
|
|
253
|
+
|
|
254
|
+
return res.status(200).json({
|
|
255
|
+
success: true,
|
|
256
|
+
message: "All alerts marked as read",
|
|
257
|
+
updated: count
|
|
258
|
+
});
|
|
259
|
+
} catch (error) {
|
|
260
|
+
logger.log('ERROR', `[markAllAlertsRead] Error marking all alerts as read for user ${userCid}`, error);
|
|
261
|
+
return res.status(500).json({ error: error.message });
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* DELETE /user/me/alerts/:alertId
|
|
267
|
+
* Delete specific alert
|
|
268
|
+
*/
|
|
269
|
+
async function deleteAlert(req, res, dependencies, config) {
|
|
270
|
+
const { db, logger } = dependencies;
|
|
271
|
+
const { userCid } = req.query;
|
|
272
|
+
const { alertId } = req.params;
|
|
273
|
+
|
|
274
|
+
if (!userCid || !alertId) {
|
|
275
|
+
return res.status(400).json({ error: "Missing userCid or alertId" });
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
try {
|
|
279
|
+
const alertRef = db.collection('user_alerts')
|
|
280
|
+
.doc(String(userCid))
|
|
281
|
+
.collection('alerts')
|
|
282
|
+
.doc(alertId);
|
|
283
|
+
|
|
284
|
+
const alertDoc = await alertRef.get();
|
|
285
|
+
|
|
286
|
+
if (!alertDoc.exists) {
|
|
287
|
+
return res.status(404).json({ error: "Alert not found" });
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
await alertRef.delete();
|
|
291
|
+
|
|
292
|
+
// Update counter if alert was unread
|
|
293
|
+
const alertData = alertDoc.data();
|
|
294
|
+
if (!alertData.read) {
|
|
295
|
+
const today = new Date().toISOString().split('T')[0];
|
|
296
|
+
const counterRef = db.collection('user_alerts')
|
|
297
|
+
.doc(String(userCid))
|
|
298
|
+
.collection('counters')
|
|
299
|
+
.doc(today);
|
|
300
|
+
|
|
301
|
+
await counterRef.set({
|
|
302
|
+
unreadCount: FieldValue.increment(-1),
|
|
303
|
+
totalCount: FieldValue.increment(-1),
|
|
304
|
+
lastUpdated: FieldValue.serverTimestamp()
|
|
305
|
+
}, { merge: true });
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return res.status(200).json({
|
|
309
|
+
success: true,
|
|
310
|
+
message: "Alert deleted"
|
|
311
|
+
});
|
|
312
|
+
} catch (error) {
|
|
313
|
+
logger.log('ERROR', `[deleteAlert] Error deleting alert ${alertId}`, error);
|
|
314
|
+
return res.status(500).json({ error: error.message });
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
module.exports = {
|
|
319
|
+
getAlertTypes,
|
|
320
|
+
getUserAlerts,
|
|
321
|
+
getAlertCount,
|
|
322
|
+
markAlertRead,
|
|
323
|
+
markAllAlertsRead,
|
|
324
|
+
deleteAlert
|
|
325
|
+
};
|
|
326
|
+
|
|
@@ -9,6 +9,7 @@ const { initiateVerification, finalizeVerification } = require('./helpers/verifi
|
|
|
9
9
|
const { getUserWatchlists, getWatchlist: getWatchlistById, createWatchlist, updateWatchlist: updateWatchlistById, deleteWatchlist, copyWatchlist, getPublicWatchlists } = require('./helpers/watchlist_helpers');
|
|
10
10
|
const { subscribeToAlerts, updateSubscription, unsubscribeFromAlerts, getUserSubscriptions, subscribeToWatchlist } = require('./helpers/subscription_helpers');
|
|
11
11
|
const { setDevOverride, getDevOverrideStatus } = require('./helpers/dev_helpers');
|
|
12
|
+
const { getAlertTypes, getUserAlerts, getAlertCount, markAlertRead, markAllAlertsRead, deleteAlert } = require('./helpers/alert_helpers');
|
|
12
13
|
|
|
13
14
|
module.exports = (dependencies, config) => {
|
|
14
15
|
const router = express.Router();
|
|
@@ -75,5 +76,13 @@ module.exports = (dependencies, config) => {
|
|
|
75
76
|
router.post('/dev/override', (req, res) => setDevOverride(req, res, dependencies, config));
|
|
76
77
|
router.get('/dev/override', (req, res) => getDevOverrideStatus(req, res, dependencies, config));
|
|
77
78
|
|
|
79
|
+
// --- Alert Management ---
|
|
80
|
+
router.get('/me/alert-types', (req, res) => getAlertTypes(req, res, dependencies, config));
|
|
81
|
+
router.get('/me/alerts', (req, res) => getUserAlerts(req, res, dependencies, config));
|
|
82
|
+
router.get('/me/alerts/count', (req, res) => getAlertCount(req, res, dependencies, config));
|
|
83
|
+
router.put('/me/alerts/:alertId/read', (req, res) => markAlertRead(req, res, dependencies, config));
|
|
84
|
+
router.put('/me/alerts/read-all', (req, res) => markAllAlertsRead(req, res, dependencies, config));
|
|
85
|
+
router.delete('/me/alerts/:alertId', (req, res) => deleteAlert(req, res, dependencies, config));
|
|
86
|
+
|
|
78
87
|
return router;
|
|
79
88
|
};
|
package/index.js
CHANGED
|
@@ -56,6 +56,9 @@ const { runRootDataIndexer } = require('./functions
|
|
|
56
56
|
// [NEW] Popular Investor Fetcher
|
|
57
57
|
const { runPopularInvestorFetch } = require('./functions/fetch-popular-investors/index');
|
|
58
58
|
|
|
59
|
+
// Alert System
|
|
60
|
+
const { handleAlertTrigger, handleComputationResultWrite } = require('./functions/alert-system/index');
|
|
61
|
+
|
|
59
62
|
// Proxy
|
|
60
63
|
const { handlePost } = require('./functions/appscript-api/index');
|
|
61
64
|
|
|
@@ -122,6 +125,11 @@ const maintenance = {
|
|
|
122
125
|
|
|
123
126
|
const proxy = { handlePost };
|
|
124
127
|
|
|
128
|
+
const alertSystem = {
|
|
129
|
+
handleAlertTrigger,
|
|
130
|
+
handleComputationResultWrite
|
|
131
|
+
};
|
|
132
|
+
|
|
125
133
|
module.exports = {
|
|
126
|
-
pipe: { core, orchestrator, dispatcher, taskEngine, computationSystem, api, maintenance, proxy },
|
|
134
|
+
pipe: { core, orchestrator, dispatcher, taskEngine, computationSystem, api, maintenance, proxy, alertSystem },
|
|
127
135
|
};
|