fase(7): api server refactor with composition root
This commit is contained in:
89
src/api/server.ts
Normal file
89
src/api/server.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* ABE API Server — Express app factory.
|
||||
* Middleware order matters: requestId → helmet → cors → rateLimit → body → routes → notFound → errorHandler
|
||||
*/
|
||||
import express, { Express, Request, Response } from 'express';
|
||||
import cors from 'cors';
|
||||
import helmet from 'helmet';
|
||||
import rateLimit from 'express-rate-limit';
|
||||
import { Kysely } from 'kysely';
|
||||
import { AppConfig } from '../shared/infrastructure/Config';
|
||||
import { Logger } from '../shared/infrastructure/Logger';
|
||||
import { Database } from '../shared/infrastructure/DatabaseConnection';
|
||||
import { createRequestIdMiddleware } from './middleware/requestId';
|
||||
import { notFoundMiddleware } from './middleware/notFound';
|
||||
import { globalErrorHandler } from './middleware/errorHandler';
|
||||
import { createRouter } from './router';
|
||||
import { CrawlingControllerDeps } from '../modules/crawling/infrastructure/http/CrawlingController';
|
||||
import { FindingsControllerDeps } from '../modules/findings/infrastructure/http/FindingsController';
|
||||
import { FuzzingControllerDeps } from '../modules/fuzzing/infrastructure/http/FuzzingController';
|
||||
|
||||
export interface ServerDependencies {
|
||||
config: AppConfig;
|
||||
logger: Logger;
|
||||
db: Kysely<Database>;
|
||||
crawlingDeps: CrawlingControllerDeps;
|
||||
findingsDeps: FindingsControllerDeps;
|
||||
fuzzingDeps: FuzzingControllerDeps;
|
||||
}
|
||||
|
||||
export function createServer(deps: ServerDependencies): Express {
|
||||
const app = express();
|
||||
|
||||
// 1. Request ID — must be first so all logs have requestId
|
||||
app.use(createRequestIdMiddleware(deps.logger));
|
||||
|
||||
// 2. Security headers
|
||||
app.use(
|
||||
helmet({
|
||||
contentSecurityPolicy: {
|
||||
directives: {
|
||||
defaultSrc: ["'self'"],
|
||||
connectSrc: ["'self'", 'ws:', 'wss:'],
|
||||
scriptSrc: ["'self'", "'unsafe-inline'"],
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
// 3. CORS
|
||||
app.use(cors({ origin: deps.config.cors.origin, credentials: true }));
|
||||
|
||||
// 4. Rate limiting
|
||||
app.use(
|
||||
rateLimit({
|
||||
windowMs: deps.config.api.rateLimitWindowMs,
|
||||
max: deps.config.api.rateLimitMax,
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
}),
|
||||
);
|
||||
|
||||
// 5. Body parsing
|
||||
app.use(express.json({ limit: '10mb' }));
|
||||
|
||||
// 6. Health endpoints — no auth required
|
||||
app.get('/health/live', (_req: Request, res: Response) => {
|
||||
res.json({ status: 'ok', uptime: process.uptime() });
|
||||
});
|
||||
|
||||
app.get('/health/ready', async (_req: Request, res: Response) => {
|
||||
try {
|
||||
await deps.db.selectFrom('sessions').select('id').limit(1).execute();
|
||||
res.json({ status: 'ready', db: 'connected' });
|
||||
} catch (err) {
|
||||
res.status(503).json({ status: 'not_ready', db: 'disconnected', error: String(err) });
|
||||
}
|
||||
});
|
||||
|
||||
// 7. Module routes
|
||||
app.use('/api', createRouter(deps));
|
||||
|
||||
// 8. 404 handler
|
||||
app.use(notFoundMiddleware);
|
||||
|
||||
// 9. Global error handler — always last
|
||||
app.use(globalErrorHandler);
|
||||
|
||||
return app;
|
||||
}
|
||||
Reference in New Issue
Block a user