UNPKG

@mini2/core

Version:

Mini Express Framework - Lightweight and modular Express.js framework with TypeScript support

214 lines 9.07 kB
"use strict"; 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