@mini2/core
Version:
Mini Express Framework - Lightweight and modular Express.js framework with TypeScript support
214 lines • 9.07 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SwaggerIntegration = void 0;
require("reflect-metadata");
const swagger_ui_express_1 = __importDefault(require("swagger-ui-express"));
const rest_1 = require("./rest");
const class_validator_jsonschema_1 = require("class-validator-jsonschema");
class SwaggerIntegration {
constructor(options = {}) {
this.options = {
title: 'Mini Framework API',
description: 'API documentation for Mini Framework',
version: '1.0.0',
servers: [
{ url: 'http://localhost:3000', description: 'Development server' },
],
docsPath: '/api-docs',
jsonPath: '/api-docs.json',
...options,
};
}
generateSwaggerSpec(controllers) {
const paths = {};
const components = {
securitySchemes: {
bearerAuth: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT',
},
},
schemas: (0, class_validator_jsonschema_1.validationMetadatasToSchemas)(),
};
controllers.forEach((controller) => {
const controllerPath = Reflect.getMetadata(rest_1.keyOfPath, controller.constructor);
if (!controllerPath) {
console.log(`❌ No path metadata found for ${controller.constructor.name}`);
return;
}
const allProperties = Object.getOwnPropertyNames(Object.getPrototypeOf(controller));
allProperties.forEach((property) => {
const routeOptions = Reflect.getMetadata(rest_1.keyOfRouteOptions, controller, property);
if (!routeOptions || !routeOptions.path || !routeOptions.method) {
if (property !== 'constructor') {
console.log(`⚠️ Skipping ${property} - no valid route options`);
}
return;
}
const fullPath = controllerPath.replace(/\/$/, '') + routeOptions.path.replace(/:([a-zA-Z0-9_]+)/g, '{$1}');
const method = routeOptions.method.toLowerCase();
if (!paths[fullPath]) {
paths[fullPath] = {};
}
// Generate OpenAPI operation
const operation = {
summary: this.generateSummary(method, fullPath),
description: this.generateDescription(method, fullPath),
tags: [this.extractControllerTag(controllerPath)],
responses: {
'200': {
description: 'Success',
content: {
'application/json': {
schema: {
type: 'object',
},
},
},
},
},
};
// Add parameters from path
const pathParams = this.extractPathParameters(routeOptions.path);
if (pathParams.length > 0) {
operation.parameters = pathParams.map((param) => ({
name: param,
in: 'path',
required: true,
schema: {
type: 'string',
},
}));
}
// Add request body for POST/PUT/PATCH
if (['post', 'put', 'patch'].includes(method) && routeOptions.validations) {
const bodyValidation = routeOptions.validations?.find((v) => v.body);
if (bodyValidation) {
operation.requestBody = {
required: true,
content: {
'application/json': {
schema: this.generateSchemaFromValidation(bodyValidation.body),
},
},
};
}
}
// Add security if authenticated
if (routeOptions.authenticated) {
operation.security = [{ bearerAuth: [] }];
}
// Add error responses
if (routeOptions.authenticated) {
operation.responses['401'] = {
description: 'Unauthorized',
};
}
if (routeOptions.permissions && routeOptions.permissions.length > 0) {
operation.responses['403'] = {
description: 'Forbidden',
};
}
operation.responses['400'] = {
description: 'Bad Request',
};
paths[fullPath][method] = operation;
});
});
this.swaggerSpec = {
openapi: '3.0.0',
info: {
title: this.options.title,
description: this.options.description,
version: this.options.version,
contact: {
name: 'API Support',
email: 'support@example.com',
},
},
servers: this.options.servers,
paths,
components,
};
}
generateSummary(method, path) {
const action = method.toUpperCase();
const resource = this.extractResourceName(path);
const actionMap = {
GET: path.includes('/:') ? `Get ${resource} by ID` : `Get all ${resource}`,
POST: `Create ${resource}`,
PUT: `Update ${resource}`,
PATCH: `Partially update ${resource}`,
DELETE: `Delete ${resource}`,
};
return actionMap[action] || `${action} ${resource}`;
}
generateDescription(method, path) {
const action = method.toLowerCase();
const resource = this.extractResourceName(path);
const descriptions = {
get: path.includes('/:')
? `Retrieve a specific ${resource} by its ID`
: `Retrieve all ${resource} records`,
post: `Create a new ${resource} record`,
put: `Update an existing ${resource} record`,
patch: `Partially update an existing ${resource} record`,
delete: `Delete a ${resource} record`,
};
return descriptions[action] || `${action} operation on ${resource}`;
}
extractControllerTag(controllerPath) {
const segments = controllerPath.split('/').filter(Boolean);
const lastSegment = segments[segments.length - 1];
return lastSegment.charAt(0).toUpperCase() + lastSegment.slice(1);
}
extractResourceName(path) {
const segments = path.split('/').filter(Boolean);
let resource = segments[segments.length - 1];
// Remove path parameters (e.g., :id)
if (resource.startsWith(':')) {
resource = segments[segments.length - 2] || 'Resource';
}
return resource.charAt(0).toUpperCase() + resource.slice(1);
}
extractPathParameters(path) {
const matches = path.match(/:([a-zA-Z_][a-zA-Z0-9_]*)/g);
return matches ? matches.map((match) => match.substring(1)) : [];
}
generateSchemaFromValidation(validationClass) {
const className = validationClass.name;
return { $ref: `#/components/schemas/${className}` };
}
setupSwagger(app) {
// Swagger UI middleware
app.use(this.options.docsPath, swagger_ui_express_1.default.serve, swagger_ui_express_1.default.setup(this.swaggerSpec, {
explorer: true,
customCss: '.swagger-ui .topbar { display: none }',
customSiteTitle: this.options.title,
swaggerOptions: {
docExpansion: 'list',
filter: true,
showRequestHeaders: true,
tryItOutEnabled: true,
persistAuthorization: true,
},
}));
// JSON endpoint for OpenAPI spec
app.get(this.options.jsonPath, (_req, res) => {
res.setHeader('Content-Type', 'application/json');
res.send(this.swaggerSpec);
});
console.log(`📚 Swagger UI available at: ${this.options.docsPath}`);
console.log(`📄 OpenAPI JSON spec available at: ${this.options.jsonPath}`);
}
getSwaggerSpec() {
return this.swaggerSpec;
}
}
exports.SwaggerIntegration = SwaggerIntegration;
exports.default = SwaggerIntegration;
//# sourceMappingURL=swagger.js.map