Files
SignageHTML/server.js
kyle 1fdb3e48c7 Items 10-15: ES modules, inline style cleanup, template modal, code modernization
- Item 10: Convert to ES modules with import/export, single module entry point
- Item 11: Replace inline styles with CSS classes (background overlay, card
  animations, highlight effect, config modal form elements)
- Item 12: Move ConfigManager modal HTML from JS template literal to
  <template> element in index.html
- Item 13: Replace deprecated url.parse() with new URL() in server.js
  and update route handlers to use searchParams
- Item 14: Replace JSON.parse/stringify deep clone with structuredClone()
- Item 15: Remove dead JSON-fixing regex code from departures.js route

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 14:30:03 +01:00

159 lines
5.0 KiB
JavaScript

// Load environment variables
require('dotenv').config();
const http = require('http');
const fs = require('fs');
const path = require('path');
// Route handlers
const departuresRouter = require('./server/routes/departures');
const sitesRouter = require('./server/routes/sites');
const configRouter = require('./server/routes/config');
const PORT = process.env.PORT || 3002;
// Default configuration
let config = {
sites: [
{
id: '1411',
name: 'Ambassaderna',
enabled: true
}
]
};
// Function to load configuration from file
function loadSitesConfig() {
try {
const fs = require('fs');
const configPath = path.join('config', 'sites.json');
if (fs.existsSync(configPath)) {
const configData = fs.readFileSync(configPath, 'utf8');
const loadedConfig = JSON.parse(configData);
// Handle old format (array of sites)
if (Array.isArray(loadedConfig)) {
config.sites = loadedConfig;
} else {
config = loadedConfig;
}
console.log('Loaded configuration:', config);
}
} catch (error) {
console.error('Error loading configuration:', error);
}
}
// Load configuration on startup
loadSitesConfig();
// Create HTTP server
const server = http.createServer(async (req, res) => {
const parsedUrl = new URL(req.url, `http://${req.headers.host || 'localhost'}`);
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
if (req.method === 'OPTIONS') {
res.writeHead(204);
res.end();
return;
}
// Handle API endpoints - use route handlers
if (parsedUrl.pathname === '/api/departures') {
await departuresRouter.handleDepartures(req, res, config);
}
else if (parsedUrl.pathname === '/api/sites/search') {
sitesRouter.handleSiteSearch(req, res, parsedUrl);
}
else if (parsedUrl.pathname === '/api/sites/nearby') {
await sitesRouter.handleNearbySites(req, res, parsedUrl);
}
else if (parsedUrl.pathname === '/api/config') {
configRouter.handleGetConfig(req, res, config);
}
else if (parsedUrl.pathname === '/api/config/update' && req.method === 'POST') {
configRouter.handleUpdateConfig(req, res, config);
}
else if (parsedUrl.pathname === '/api/config/client') {
configRouter.handleClientConfig(req, res);
}
// Serve static files
else if (parsedUrl.pathname === '/' || parsedUrl.pathname === '/index.html') {
fs.readFile('index.html', 'utf8', (err, data) => {
if (err) {
res.writeHead(500, { 'Content-Type': 'text/plain' });
res.end('Error loading index.html');
return;
}
// Always inject API key and config into HTML (even if empty, so window vars exist)
const apiKey = process.env.OPENWEATHERMAP_API_KEY || '';
if (!data.includes('window.OPENWEATHERMAP_API_KEY')) {
// Inject before closing </head> tag
const configScript = `
<script>
window.OPENWEATHERMAP_API_KEY = '${apiKey.replace(/'/g, "\\'")}';
window.DEFAULT_LOCATION = {
latitude: ${parseFloat(process.env.DEFAULT_LATITUDE) || 59.3293},
longitude: ${parseFloat(process.env.DEFAULT_LONGITUDE) || 18.0686},
name: '${(process.env.DEFAULT_LOCATION_NAME || 'Stockholm').replace(/'/g, "\\'")}'
};
</script>`;
data = data.replace('</head>', configScript + '\n</head>');
}
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(data);
});
}
// Serve static files from public directory or root
else if (/\.(js|css|png|jpg|jpeg|gif|ico|svg|json)$/.test(parsedUrl.pathname)) {
let filePath = parsedUrl.pathname.substring(1); // Remove leading /
// Try public directory first, then root directory for backward compatibility
const publicPath = path.join('public', filePath);
const rootPath = filePath;
// Check if file exists in public directory first
fs.access(publicPath, fs.constants.F_OK, (publicErr) => {
const targetPath = publicErr ? rootPath : publicPath;
fs.readFile(targetPath, (err, data) => {
if (err) {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('File not found');
return;
}
const ext = parsedUrl.pathname.split('.').pop();
const contentType = {
'js': 'text/javascript',
'css': 'text/css',
'png': 'image/png',
'jpg': 'image/jpeg',
'jpeg': 'image/jpeg',
'gif': 'image/gif',
'ico': 'image/x-icon',
'svg': 'image/svg+xml',
'json': 'application/json'
}[ext] || 'text/plain';
res.writeHead(200, { 'Content-Type': contentType });
res.end(data);
});
});
}
else {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not Found');
}
});
// Start the server
server.listen(PORT, () => {
console.log(`Server running at http://localhost:${PORT}/`);
});