bowler-chart/server.js

78 lines
2.2 KiB
JavaScript

'use strict';
require('dotenv').config();
const path = require('path');
const express = require('express');
const helmet = require('helmet');
const session = require('express-session');
const SqliteStore = require('better-sqlite3-session-store')(session);
const db = require('./db'); // applies schema + first-run seed
const app = express();
const PORT = Number(process.env.PORT) || 3000;
const PROD = process.env.NODE_ENV === 'production';
app.set('trust proxy', 1);
// Security headers. CSP is relaxed for inline bootstrapping but blocks remote
// scripts; all libraries are served from our own /vendor.
app.use(
helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", 'data:'],
fontSrc: ["'self'", 'data:'],
connectSrc: ["'self'"],
},
},
})
);
app.use(express.json({ limit: '256kb' }));
app.use(
session({
store: new SqliteStore({ client: db, expired: { clear: true, intervalMs: 900000 } }),
secret: process.env.SESSION_SECRET || 'dev-insecure-secret-change-me',
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
sameSite: 'lax',
secure: PROD,
maxAge: 1000 * 60 * 60 * 24 * 7, // 7 days
},
})
);
// API routes
app.use('/api', require('./src/routes/auth.routes'));
app.use('/api/users', require('./src/routes/users.routes'));
app.use('/api/schemes', require('./src/routes/schemes.routes'));
app.use('/api/chart', require('./src/routes/chart.routes'));
app.use('/api/projects', require('./src/routes/projects.routes'));
app.use('/api/kpis', require('./src/routes/kpis.routes'));
app.use('/api/entries', require('./src/routes/entries.routes'));
// Static frontend
app.use(express.static(path.join(__dirname, 'public')));
// JSON 404 for unmatched API routes
app.use('/api', (req, res) => res.status(404).json({ error: 'Not found' }));
// Centralized error handler
// eslint-disable-next-line no-unused-vars
app.use((err, req, res, next) => {
console.error(err);
res.status(500).json({ error: 'Internal server error' });
});
app.listen(PORT, () => {
console.log(`Bowler Chart running at http://localhost:${PORT}`);
});