import 'dotenv/config'; import express from 'express'; import cors from 'cors'; import session from 'express-session'; import ConnectPgSimple from 'connect-pg-simple'; import pool from './db/pool.js'; import { errorHandler } from './middleware/errors.js'; // Routes import authRouter from './routes/auth.js'; import assetsRouter from './routes/assets.js'; import projectsRouter from './routes/projects.js'; import binsRouter from './routes/bins.js'; import jobsRouter from './routes/jobs.js'; import captureRouter from './routes/capture.js'; import uploadRouter from './routes/upload.js'; import recordersRouter from './routes/recorders.js'; import settingsRouter from './routes/settings.js'; import amppRouter from './routes/ampp.js'; import usersRouter from './routes/users.js'; import groupsRouter from './routes/groups.js'; import tokensRouter from './routes/tokens.js'; import sequencesRouter from './routes/sequences.js'; const app = express(); const PORT = process.env.PORT || 3000; // ── Middleware ──────────────────────────────────────────────────────────────── app.use(cors({ origin: true, credentials: true })); app.use(express.json({ limit: '50mb' })); const PgSession = ConnectPgSimple(session); app.use( session({ store: new PgSession({ pool, tableName: 'sessions', // Prune expired sessions every hour pruneSessionInterval: 3600, }), secret: process.env.SESSION_SECRET || 'change-me-in-production', resave: false, saveUninitialized: false, cookie: { secure: process.env.NODE_ENV === 'production', httpOnly: true, maxAge: 1000 * 60 * 60 * 24, // 24 h }, }) ); // ── Health (no auth) ────────────────────────────────────────────────────────── app.get('/health', (_req, res) => res.json({ status: 'ok' })); // ── API Routes ──────────────────────────────────────────────────────────────── // Auth routes are always open (login/logout don't require a session) app.use('/api/v1/auth', authRouter); // All other routes are gated by requireAuth (no-op unless AUTH_ENABLED=true) app.use('/api/v1/assets', assetsRouter); app.use('/api/v1/projects', projectsRouter); app.use('/api/v1/bins', binsRouter); app.use('/api/v1/jobs', jobsRouter); app.use('/api/v1/capture', captureRouter); app.use('/api/v1/upload', uploadRouter); app.use('/api/v1/recorders', recordersRouter); app.use('/api/v1/settings', settingsRouter); app.use('/api/v1/ampp', amppRouter); app.use('/api/v1/users', usersRouter); app.use('/api/v1/groups', groupsRouter); app.use('/api/v1/tokens', tokensRouter); app.use('/api/v1/sequences', sequencesRouter); // ── Error handler ───────────────────────────────────────────────────────────── app.use(errorHandler); // ── Start ──────────────────────────────────────────────────────────────────── import { readdirSync, readFileSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import { dirname, join } from 'node:path'; const __dirnameMig = dirname(fileURLToPath(import.meta.url)); async function runMigrations() { const dir = join(__dirnameMig, 'db', 'migrations'); let files = []; try { files = readdirSync(dir).filter(f => f.endsWith('.sql')).sort(); } catch { return; } for (const f of files) { const sql = readFileSync(join(dir, f), 'utf8'); try { await pool.query(sql); console.log('[migration] applied ' + f); } catch (err) { console.error('[migration] failed ' + f, err.message); } } } await runMigrations(); app.listen(PORT, () => { const authMode = process.env.AUTH_ENABLED === 'true' ? 'ENABLED' : 'DISABLED (set AUTH_ENABLED=true for production)'; console.log(`MAM API listening on port ${PORT}`); console.log(`Authentication: ${authMode}`); });