import express from 'express'; import cookieSession from 'cookie-session'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { openDb } from './db.js'; import { requireAuth } from './auth.js'; import authRoutes from './routes/auth.js'; import tripsRoutes from './routes/trips.js'; import entriesRoutes from './routes/entries.js'; import geocodeRoutes from './routes/geocode.js'; import airportsRoutes from './routes/airports.js'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const PUBLIC_DIR = path.join(__dirname, '..', '..', 'public'); // Build the Express app. Accepts either an options object // { dbPath, sessionSecret } or a bare dbPath string (per API.md). export function createApp(options = {}) { const opts = typeof options === 'string' ? { dbPath: options } : options; const dbPath = opts.dbPath || ':memory:'; const sessionSecret = opts.sessionSecret || 'dev-secret'; const db = openDb(dbPath); const app = express(); app.disable('x-powered-by'); app.use(express.json()); app.use( cookieSession({ name: 'trip_session', secret: sessionSecret, httpOnly: true, sameSite: 'lax', maxAge: 30 * 24 * 60 * 60 * 1000, }) ); // no-cache (not no-store): browsers must revalidate, ETags turn unchanged // files into 304s. Without this, upgraded deployments keep serving a stale // cached frontend against the new API. app.use( express.static(PUBLIC_DIR, { etag: true, lastModified: true, setHeaders: (res) => res.setHeader('Cache-Control', 'no-cache'), }) ); app.use('/api/auth', authRoutes(db)); app.use('/api/trips', requireAuth, tripsRoutes(db)); app.use('/api', requireAuth, entriesRoutes(db)); // /trips/:id/entries + /entries/:id app.use('/api/geocode', requireAuth, geocodeRoutes(db)); app.use('/api/airports', requireAuth, airportsRoutes()); // JSON 404 for any unmatched /api route. app.use('/api', (req, res) => { res.status(404).json({ error: 'not found' }); }); // JSON error handler (malformed body, unexpected failures). // eslint-disable-next-line no-unused-vars app.use((err, req, res, next) => { if (err && err.type === 'entity.parse.failed') { return res.status(400).json({ error: 'invalid JSON body' }); } console.error(err); res.status(500).json({ error: 'internal server error' }); }); app.locals.db = db; return app; } export default createApp;