import express from 'express'; import helmet from 'helmet'; import { fileURLToPath } from 'url'; import { dirname, join } from 'path'; import { router } from './routes.js'; import { restartJobs } from './jobs.js'; const __dirname = dirname(fileURLToPath(import.meta.url)); const PORT = process.env.PORT ?? 3000; export const app = express(); app.use(helmet({ contentSecurityPolicy: { useDefaults: false, // explicit — upgrade-insecure-requests breaks HTTP deployments directives: { 'default-src': ["'self'"], 'base-uri': ["'self'"], 'font-src': ["'self'", 'https://fonts.gstatic.com'], 'form-action': ["'self'"], 'frame-ancestors': ["'self'"], 'img-src': ["'self'", 'data:'], 'object-src': ["'none'"], 'script-src': ["'self'"], 'script-src-attr': ["'unsafe-inline'"], // allow onclick handlers 'style-src': ["'self'", 'https://fonts.googleapis.com'], }, }, })); app.use(express.json()); // API app.use('/api', router); // Static files app.use(express.static(join(__dirname, '..'))); // SPA fallback — all non-API, non-asset routes serve index.html app.get('*', (req, res) => { res.sendFile(join(__dirname, '../index.html')); }); // Error handler app.use((err, _req, res, _next) => { console.error(err); res.status(500).json({ error: 'internal server error' }); }); // Boot — only when run directly, not when imported by tests if (process.argv[1] === fileURLToPath(import.meta.url)) { restartJobs(); app.listen(PORT, () => console.log(`catalyst on :${PORT}`)); }