← back to Rentv Adintel
server.js
71 lines
'use strict';
/**
* RENTV Advertiser Intelligence Viewer — Express entry point (house stack).
* Mounts page routes + /api/v1. Simple View is the default executive UX (§25).
* One-line module mount pattern (matches the DW/rentv fleet convention).
*/
require('./lib/env'); // loads .env into process.env (no dependency)
const path = require('path');
const express = require('express');
const app = express();
app.disable('x-powered-by');
app.use(express.json({ limit: '5mb' }));
app.use(express.urlencoded({ extended: true, limit: '5mb' }));
// Secure headers + a conservative CSP (§32). Vanilla pages only, no CDNs.
app.use((req, res, next) => {
res.set('X-Content-Type-Options', 'nosniff');
res.set('X-Frame-Options', 'SAMEORIGIN');
res.set('Referrer-Policy', 'strict-origin-when-cross-origin');
res.set(
'Content-Security-Policy',
"default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self'; base-uri 'self'; form-action 'self'"
);
next();
});
// Static assets (thumbnails, seed flyers, css, js).
app.use('/assets', express.static(path.join(__dirname, 'data', 'assets')));
app.use('/seed', express.static(path.join(__dirname, 'public', 'seed')));
app.use('/css', express.static(path.join(__dirname, 'public', 'css')));
app.use('/js', express.static(path.join(__dirname, 'public', 'js')));
app.get('/healthz', (req, res) => res.json({ ok: true, service: 'rentv-adintel', ts: Date.now() }));
// ---- Route modules (each guards its own existence so the app boots even
// ---- before a parallel worker has landed its file — graceful degradation). ----
function mountOptional(mountPath, modulePath) {
try {
const router = require(modulePath);
app.use(mountPath, router);
// eslint-disable-next-line no-console
console.log(`[mount] ${mountPath} -> ${modulePath}`);
} catch (e) {
if (e.code === 'MODULE_NOT_FOUND' && e.message.includes(modulePath.replace('./', ''))) {
console.warn(`[mount] SKIP ${mountPath} (${modulePath} not present yet)`);
} else {
throw e; // a real error inside the module — surface it
}
}
}
mountOptional('/api/v1', './src/routes/api');
mountOptional('/', './src/routes/pages');
// Fallback landing so a fresh clone shows something before pages/ lands.
app.get('/', (req, res, next) => {
if (res.headersSent) return next();
res
.type('html')
.send('<h1>RENTV Advertiser Intelligence</h1><p>App is booting. Run <code>npm run db:migrate && npm run db:seed</code>, then open <a href="/advertisers">/advertisers</a>.</p>');
});
const PORT = process.env.PORT || 9814;
if (require.main === module) {
app.listen(PORT, () => console.log(`[rentv-adintel] listening on http://localhost:${PORT}`));
}
module.exports = app;