fase(7): api server refactor with composition root
This commit is contained in:
76
dist/api/middleware/errorHandler.js
vendored
Normal file
76
dist/api/middleware/errorHandler.js
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.RateLimitError = exports.ConflictError = exports.NotFoundError = exports.ForbiddenError = exports.AuthenticationError = exports.ValidationError = exports.AppError = void 0;
|
||||
exports.globalErrorHandler = globalErrorHandler;
|
||||
class AppError extends Error {
|
||||
constructor(message, statusCode, code, isOperational = true) {
|
||||
super(message);
|
||||
this.statusCode = statusCode;
|
||||
this.code = code;
|
||||
this.isOperational = isOperational;
|
||||
this.name = this.constructor.name;
|
||||
Error.captureStackTrace(this, this.constructor);
|
||||
}
|
||||
}
|
||||
exports.AppError = AppError;
|
||||
class ValidationError extends AppError {
|
||||
constructor(message, details) {
|
||||
super(message, 400, 'VALIDATION_ERROR');
|
||||
this.details = details;
|
||||
}
|
||||
}
|
||||
exports.ValidationError = ValidationError;
|
||||
class AuthenticationError extends AppError {
|
||||
constructor(message = 'Unauthorized') {
|
||||
super(message, 401, 'AUTHENTICATION_ERROR');
|
||||
}
|
||||
}
|
||||
exports.AuthenticationError = AuthenticationError;
|
||||
class ForbiddenError extends AppError {
|
||||
constructor(message = 'Forbidden') {
|
||||
super(message, 403, 'FORBIDDEN');
|
||||
}
|
||||
}
|
||||
exports.ForbiddenError = ForbiddenError;
|
||||
class NotFoundError extends AppError {
|
||||
constructor(resource) {
|
||||
super(`${resource} not found`, 404, 'NOT_FOUND');
|
||||
}
|
||||
}
|
||||
exports.NotFoundError = NotFoundError;
|
||||
class ConflictError extends AppError {
|
||||
constructor(message) {
|
||||
super(message, 409, 'CONFLICT');
|
||||
}
|
||||
}
|
||||
exports.ConflictError = ConflictError;
|
||||
class RateLimitError extends AppError {
|
||||
constructor() {
|
||||
super('Too many requests', 429, 'RATE_LIMIT');
|
||||
}
|
||||
}
|
||||
exports.RateLimitError = RateLimitError;
|
||||
function globalErrorHandler(err, req, res, _next) {
|
||||
const logger = req.log;
|
||||
if (err instanceof AppError && err.isOperational) {
|
||||
if (logger) {
|
||||
logger.warn({ err, statusCode: err.statusCode }, err.message);
|
||||
}
|
||||
const body = { error: err.message, code: err.code };
|
||||
if (err instanceof ValidationError && err.details !== undefined) {
|
||||
body['details'] = err.details;
|
||||
}
|
||||
res.status(err.statusCode).json(body);
|
||||
return;
|
||||
}
|
||||
if (logger) {
|
||||
logger.error({ err }, 'Unhandled error');
|
||||
}
|
||||
else {
|
||||
console.error('Unhandled error', err);
|
||||
}
|
||||
res.status(500).json({
|
||||
error: process.env['NODE_ENV'] === 'production' ? 'Internal server error' : err.message,
|
||||
code: 'INTERNAL_ERROR',
|
||||
});
|
||||
}
|
||||
9
dist/api/middleware/notFound.js
vendored
Normal file
9
dist/api/middleware/notFound.js
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.notFoundMiddleware = notFoundMiddleware;
|
||||
function notFoundMiddleware(req, res) {
|
||||
res.status(404).json({
|
||||
error: `Route ${req.method} ${req.path} not found`,
|
||||
code: 'NOT_FOUND',
|
||||
});
|
||||
}
|
||||
11
dist/api/middleware/requestId.js
vendored
Normal file
11
dist/api/middleware/requestId.js
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.createRequestIdMiddleware = createRequestIdMiddleware;
|
||||
const uuid_1 = require("uuid");
|
||||
function createRequestIdMiddleware(logger) {
|
||||
return (req, _res, next) => {
|
||||
req.id = req.headers['x-request-id'] ?? (0, uuid_1.v4)();
|
||||
req.log = logger.child({ requestId: req.id, method: req.method, url: req.url });
|
||||
next();
|
||||
};
|
||||
}
|
||||
17
dist/api/router.js
vendored
Normal file
17
dist/api/router.js
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.createRouter = createRouter;
|
||||
/**
|
||||
* ABE API Router — registers all module routes.
|
||||
*/
|
||||
const express_1 = require("express");
|
||||
const CrawlingController_1 = require("../modules/crawling/infrastructure/http/CrawlingController");
|
||||
const FindingsController_1 = require("../modules/findings/infrastructure/http/FindingsController");
|
||||
const FuzzingController_1 = require("../modules/fuzzing/infrastructure/http/FuzzingController");
|
||||
function createRouter(deps) {
|
||||
const router = (0, express_1.Router)();
|
||||
router.use('/sessions', (0, CrawlingController_1.createCrawlingRouter)(deps.crawlingDeps));
|
||||
router.use('/findings', (0, FindingsController_1.createFindingsRouter)(deps.findingsDeps));
|
||||
router.use('/fuzz', (0, FuzzingController_1.createFuzzingRouter)(deps.fuzzingDeps));
|
||||
return router;
|
||||
}
|
||||
64
dist/api/server.js
vendored
Normal file
64
dist/api/server.js
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.createServer = createServer;
|
||||
/**
|
||||
* ABE API Server — Express app factory.
|
||||
* Middleware order matters: requestId → helmet → cors → rateLimit → body → routes → notFound → errorHandler
|
||||
*/
|
||||
const express_1 = __importDefault(require("express"));
|
||||
const cors_1 = __importDefault(require("cors"));
|
||||
const helmet_1 = __importDefault(require("helmet"));
|
||||
const express_rate_limit_1 = __importDefault(require("express-rate-limit"));
|
||||
const requestId_1 = require("./middleware/requestId");
|
||||
const notFound_1 = require("./middleware/notFound");
|
||||
const errorHandler_1 = require("./middleware/errorHandler");
|
||||
const router_1 = require("./router");
|
||||
function createServer(deps) {
|
||||
const app = (0, express_1.default)();
|
||||
// 1. Request ID — must be first so all logs have requestId
|
||||
app.use((0, requestId_1.createRequestIdMiddleware)(deps.logger));
|
||||
// 2. Security headers
|
||||
app.use((0, helmet_1.default)({
|
||||
contentSecurityPolicy: {
|
||||
directives: {
|
||||
defaultSrc: ["'self'"],
|
||||
connectSrc: ["'self'", 'ws:', 'wss:'],
|
||||
scriptSrc: ["'self'", "'unsafe-inline'"],
|
||||
},
|
||||
},
|
||||
}));
|
||||
// 3. CORS
|
||||
app.use((0, cors_1.default)({ origin: deps.config.cors.origin, credentials: true }));
|
||||
// 4. Rate limiting
|
||||
app.use((0, express_rate_limit_1.default)({
|
||||
windowMs: deps.config.api.rateLimitWindowMs,
|
||||
max: deps.config.api.rateLimitMax,
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
}));
|
||||
// 5. Body parsing
|
||||
app.use(express_1.default.json({ limit: '10mb' }));
|
||||
// 6. Health endpoints — no auth required
|
||||
app.get('/health/live', (_req, res) => {
|
||||
res.json({ status: 'ok', uptime: process.uptime() });
|
||||
});
|
||||
app.get('/health/ready', async (_req, res) => {
|
||||
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', (0, router_1.createRouter)(deps));
|
||||
// 8. 404 handler
|
||||
app.use(notFound_1.notFoundMiddleware);
|
||||
// 9. Global error handler — always last
|
||||
app.use(errorHandler_1.globalErrorHandler);
|
||||
return app;
|
||||
}
|
||||
Reference in New Issue
Block a user