-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathindex.js
More file actions
119 lines (97 loc) · 3.68 KB
/
Copy pathindex.js
File metadata and controls
119 lines (97 loc) · 3.68 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
const axios = require('axios');
const fs = require('fs');
const s3Helper = require('./s3Helper');
const interval = 15000; // ms
const ALERT_THRESHOLD_MS = 5 * 60 * 1000; // 5 minutes
const configPath = process.env.ORION_CONFIG_PATH;
const configJson = process.env.ORION_CONFIG_JSON;
if (!configJson && !configPath) {
throw new Error("Missing ORION_CONFIG_JSON or ORION_CONFIG_PATH environment variable");
}
let config;
if (configJson) {
console.log("reading config from ORION_CONFIG_JSON");
config = JSON.parse(configJson);
} else {
console.log("reading config from " + configPath);
config = JSON.parse(fs.readFileSync(configPath));
}
if (!config || !config.agencies || !config.agencies.length) {
throw new Error("No agencies specified in config.");
}
if (!config.s3_bucket) {
throw new Error("No s3_bucket specified in config.");
}
const providerNames = [
'nextbus',
'marin',
'gtfs-realtime',
];
const s3Bucket = config.s3_bucket;
console.log("S3 bucket: " + s3Bucket);
// Track last successful S3 write per agency
const lastSuccessfulWrite = {};
var agenciesInfo = config.agencies.map((agencyConfig) => {
const providerName = agencyConfig.provider;
if (!providerNames.includes(providerName)) {
throw new Error("Invalid provider: " + providerName);
}
const provider = require('./providers/' + providerName);
const agencyId = agencyConfig.id;
if (!agencyId) {
throw new Error("Agency missing id");
}
console.log("Agency: " + agencyId + " (" + providerName + ")");
// Initialize timestamp so alert doesn't fire immediately on startup
lastSuccessfulWrite[agencyId] = Date.now();
return {
provider: provider,
id: agencyId,
config: agencyConfig
};
});
// Alert function — uses Slack webhook if configured, else logs to stderr
function sendAlert(agencyId) {
const message = `Orion Alert: No vehicle states written for agency "${agencyId}" in the last ${ALERT_THRESHOLD_MS / 60000} minutes. Provider may be down.`;
console.error(`ALERT: ${message}`);
const webhookUrl = process.env.ALERT_WEBHOOK_URL;
if (webhookUrl) {
axios.post(webhookUrl, { text: `🚨 ${message}` })
.catch((err) => console.error(`Failed to send alert webhook for ${agencyId}:`, err.message));
}
}
// Periodically check if any agency has stopped writing states
setInterval(() => {
const now = Date.now();
agenciesInfo.forEach((agencyInfo) => {
const last = lastSuccessfulWrite[agencyInfo.id];
if (!last || (now - last) > ALERT_THRESHOLD_MS) {
sendAlert(agencyInfo.id);
}
});
}, ALERT_THRESHOLD_MS);
// Wait until the next multiple of 15 seconds
setTimeout(function() {
setInterval(saveVehicles, interval);
saveVehicles();
}, interval - Date.now() % interval);
function saveVehicles() {
const currentTime = Date.now();
const promises = agenciesInfo.map((agencyInfo) => {
return agencyInfo.provider.getVehicles(agencyInfo.config)
.then((vehicles) => {
return s3Helper.writeToS3(s3Bucket, agencyInfo.id, currentTime, vehicles)
.then((result) => {
// ✅ Update timestamp on successful write
lastSuccessfulWrite[agencyInfo.id] = Date.now();
return result;
});
})
.catch((err) => {
// Log error but don't update lastSuccessfulWrite so alert can trigger
console.error(`Error saving vehicles for agency "${agencyInfo.id}":`, err.message || err);
});
});
// Return the Promise so errors are traceable
return Promise.all(promises);
}