Add server.js

This commit is contained in:
Zac Gaetano 2026-03-31 15:29:52 -04:00
parent a69e7abff8
commit ccde43ce37

166
server.js Normal file
View file

@ -0,0 +1,166 @@
const express = require('express');
const cors = require('cors');
const axios = require('axios');
const path = require('path');
const app = express();
const PORT = process.env.PORT || 3000;
app.use(cors());
app.use(express.json());
app.use(express.static('public'));
// Proxy endpoint for getting token
app.post('/api/token', async (req, res) => {
try {
const { apiKey, baseUrl } = req.body;
const response = await axios.post(
`${baseUrl}/identity/connect/token`,
'grant_type=client_credentials&scope=platform',
{
headers: {
'Authorization': `Basic ${apiKey}`,
'Content-Type': 'application/x-www-form-urlencoded'
},
timeout: 10000
}
);
res.json(response.data);
} catch (error) {
console.error('Token error:', error.message);
res.status(error.response?.status || 500).json({
error: error.message,
details: error.response?.data
});
}
});
// Proxy endpoint for fetching RUNNING recorders
// Strategy: use events as the primary source (they reflect what's actually running),
// then enrich with channel data for editing (source:text, destinationId:int)
app.get('/api/channels', async (req, res) => {
try {
const { token, baseUrl } = req.query;
if (!token || !baseUrl) {
return res.status(400).json({ error: 'Missing token or baseUrl' });
}
const headers = { 'Authorization': `Bearer ${token}` };
// Fetch channels and events in parallel
const [channelsRes, eventsRes] = await Promise.allSettled([
axios.get(`${baseUrl}/api/store/channel/v1/channels`, { headers, timeout: 10000 }),
axios.get(`${baseUrl}/api/v2/store/schedule/events`, { headers, timeout: 10000 })
]);
const channelsData = channelsRes.status === 'fulfilled'
? (Array.isArray(channelsRes.value.data) ? channelsRes.value.data : [])
: [];
const eventsData = eventsRes.status === 'fulfilled'
? (Array.isArray(eventsRes.value.data) ? eventsRes.value.data : [])
: [];
console.log(`Fetched ${channelsData.length} channels, ${eventsData.length} events`);
if (eventsData.length > 0) {
console.log('Sample event keys:', Object.keys(eventsData[0]));
console.log('Sample event:', JSON.stringify(eventsData[0], null, 2).substring(0, 500));
}
// Build a channel lookup map by URI
const channelByUri = {};
channelsData.forEach(ch => {
if (ch['uri:text']) channelByUri[ch['uri:text']] = ch;
if (ch.uri) channelByUri[ch.uri] = ch;
});
// Filter events to only those currently running
const runningEvents = eventsData.filter(event => {
const status = (event['status:enum'] || '').toLowerCase();
return status === 'running';
});
console.log(`Running events: ${runningEvents.length}`);
// Enrich each running event with its channel data
const result = runningEvents.map(event => {
const channelUri = event['source']?.['channel:text'];
const channel = channelUri ? channelByUri[channelUri] : null;
return {
// Clean up name - remove leading "undefined" or "null" prefixes
const rawName = event['name:text'] || channel?.['name:text'] || 'Unknown';
const cleanName = rawName.replace(/^(undefined|null)\s*/i, '').trim() || rawName;
// Event fields
_eventId: event['scheduleEvent:id'] || event['id'],
_health: event['health:enum'],
_status: event['status:enum'],
_healthCondition: event['healthCondition:enum'],
_channelUri: channelUri,
// Channel fields (for editing)
'channel:id': channel?.['channel:id'] || null,
'name:text': cleanName,
'source:text': channel?.['source:text'] || null,
'destinationId:int': channel?.['destinationId:int'] || null,
'elasticRecorder': channel?.['elasticRecorder'] || null,
};
});
res.json(result);
} catch (error) {
console.error('Channels error:', error.message);
res.status(error.response?.status || 500).json({
error: error.message,
details: error.response?.data
});
}
});
// Proxy endpoint for updating channel
app.patch('/api/channels/:id', async (req, res) => {
try {
const { token, baseUrl } = req.query;
const { id } = req.params;
const data = req.body;
if (!token || !baseUrl) {
return res.status(400).json({ error: 'Missing token or baseUrl' });
}
const response = await axios.patch(
`${baseUrl}/api/store/channel/v1/channels/${id}`,
data,
{
headers: { 'Authorization': `Bearer ${token}` },
timeout: 10000
}
);
res.json(response.data);
} catch (error) {
console.error('Update error:', error.message);
res.status(error.response?.status || 500).json({
error: error.message,
details: error.response?.data
});
}
});
// Health check
app.get('/health', (req, res) => {
res.json({ status: 'ok' });
});
// Serve index
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});
app.listen(PORT, () => {
console.log(`✅ Dashboard running on http://localhost:${PORT}`);
});