Phase 1: lock cron, reload chaud, argon2, providers, IMDb lookup, cache LRU, /health, /metrics, rate limit, UI dark, biome
This commit is contained in:
78
server.js
78
server.js
@@ -1,54 +1,84 @@
|
||||
import Fastify from 'fastify';
|
||||
import { join } from 'node:path';
|
||||
import formbody from '@fastify/formbody';
|
||||
import rateLimit from '@fastify/rate-limit';
|
||||
import secureSession from '@fastify/secure-session';
|
||||
import fastifyStatic from '@fastify/static';
|
||||
import { join } from 'node:path';
|
||||
import { ROOT, PORT, HOST, SESSION_SECRET } from './config.js';
|
||||
import indexRoutes from './routes/index.js';
|
||||
import apiRoutes from './routes/api.js';
|
||||
import searchRoutes from './routes/search.js';
|
||||
import Fastify from 'fastify';
|
||||
import { HOST, PORT, RATE_LIMIT_PER_SEC, ROOT, SESSION_SECRET } from './config.js';
|
||||
import { getRatings } from './lib/imdbRatings.js';
|
||||
import { httpDuration, httpRequests, imdbRatingsCount, searchWorkers } from './lib/metrics.js';
|
||||
import { getPool } from './lib/searchEngine.js';
|
||||
import adminRoutes from './routes/admin.js';
|
||||
import apiRoutes from './routes/api.js';
|
||||
import healthRoutes from './routes/health.js';
|
||||
|
||||
const fastify = Fastify({ logger: true, trustProxy: true });
|
||||
|
||||
await fastify.register(rateLimit, {
|
||||
max: RATE_LIMIT_PER_SEC,
|
||||
timeWindow: '1 second',
|
||||
// Skip rate limiting for /health and /metrics so monitoring is never throttled
|
||||
skipOnError: true,
|
||||
allowList: (req) => req.url === '/health' || req.url === '/metrics',
|
||||
});
|
||||
|
||||
await fastify.register(formbody);
|
||||
|
||||
await fastify.register(secureSession, {
|
||||
// 32 bytes minimum. Use SESSION_SECRET env var in production.
|
||||
secret: SESSION_SECRET.padEnd(32, '0').slice(0, 32),
|
||||
salt: 'proxytmdb-salt-1',
|
||||
cookieName: 'session',
|
||||
cookie: {
|
||||
path: '/',
|
||||
httpOnly: true,
|
||||
sameSite: 'lax',
|
||||
secure: 'auto',
|
||||
},
|
||||
cookie: { path: '/', httpOnly: true, sameSite: 'lax', secure: 'auto' },
|
||||
});
|
||||
|
||||
await fastify.register(indexRoutes);
|
||||
await fastify.register(apiRoutes);
|
||||
await fastify.register(searchRoutes);
|
||||
// Per-request timing for Prometheus.
|
||||
fastify.addHook('onRequest', (_req, reply, done) => {
|
||||
reply.startTime = process.hrtime.bigint();
|
||||
done();
|
||||
});
|
||||
fastify.addHook('onResponse', (req, reply, done) => {
|
||||
const route = req.routeOptions?.url || req.url.split('?')[0] || 'unknown';
|
||||
const labels = { method: req.method, route, status: String(reply.statusCode) };
|
||||
httpRequests.inc(labels);
|
||||
if (reply.startTime) {
|
||||
const seconds = Number(process.hrtime.bigint() - reply.startTime) / 1e9;
|
||||
httpDuration.observe(labels, seconds);
|
||||
}
|
||||
done();
|
||||
});
|
||||
|
||||
// Serve any other path as a static file from the project root, so that the
|
||||
// "directory listing" links keep working exactly as they did under Apache.
|
||||
// Public static UI at /
|
||||
await fastify.register(fastifyStatic, {
|
||||
root: join(ROOT, 'public'),
|
||||
serve: true,
|
||||
index: 'index.html',
|
||||
prefix: '/',
|
||||
});
|
||||
|
||||
await fastify.register(adminRoutes);
|
||||
await fastify.register(apiRoutes);
|
||||
await fastify.register(healthRoutes);
|
||||
|
||||
// Serve raw project files only under /admin/files (still session-protected
|
||||
// at the listing level because the listing page itself requires auth).
|
||||
await fastify.register(fastifyStatic, {
|
||||
root: ROOT,
|
||||
serve: true,
|
||||
index: false,
|
||||
list: false,
|
||||
decorateReply: false,
|
||||
prefix: '/',
|
||||
prefix: '/admin/files/',
|
||||
});
|
||||
|
||||
// Warm up: load IMDb ratings and spawn search workers eagerly.
|
||||
fastify.ready().then(async () => {
|
||||
try {
|
||||
await getRatings();
|
||||
getPool('movie');
|
||||
getPool('tv');
|
||||
fastify.log.info('Warmup complete');
|
||||
const ratings = await getRatings();
|
||||
imdbRatingsCount.set(ratings.size);
|
||||
const movie = getPool('movie');
|
||||
const tv = getPool('tv');
|
||||
searchWorkers.set({ type: 'movie' }, movie.workers.length);
|
||||
searchWorkers.set({ type: 'tv' }, tv.workers.length);
|
||||
fastify.log.info({ ratings: ratings.size }, 'Warmup complete');
|
||||
} catch (err) {
|
||||
fastify.log.warn({ err }, 'Warmup failed');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user