UNPKG

@fdm-monster/server

Version:

FDM Monster is a bulk OctoPrint, Klipper, PrusaLink and BambuLab manager to set up, configure and monitor 3D printers. Our aim is to provide neat overview over your farm.

153 lines (152 loc) 5.01 kB
import { getDirname } from "../fs.utils.js"; import { API_METADATA_KEY } from "./decorators.js"; import { findControllers } from "awilix-express"; //#region src/utils/swagger/generator.ts var SwaggerGenerator = class { logger; constructor(logger) { this.logger = logger; } openApiDoc = { openapi: "3.1.0", info: { title: "FDM Monster API", version: process.env.npm_package_version || "2.0.0", description: "FDM Monster is a bulk OctoPrint, Klipper, PrusaLink and BambuLab manager to set up, configure and monitor 3D printers. Our aim is to provide neat overview over your farm.", license: { name: "AGPL-3.0-or-later", url: "https://www.gnu.org/licenses/agpl-3.0.en.html" }, contact: { name: "FDM Monster GitHub", url: "https://github.com/fdm-monster/fdm-monster" } }, servers: [{ url: "/api", description: "API Server" }], paths: {}, components: { schemas: {}, securitySchemes: { bearerAuth: { type: "http", scheme: "bearer", bearerFormat: "JWT", description: "Enter your JWT token" } } }, security: [{ bearerAuth: [] }] }; async generate() { try { const discoveredControllers = await findControllers(`../../controllers/*.controller.js`, { cwd: getDirname(import.meta.url), ignore: ["**/*.map", "**/*.d.ts"], absolute: true, esModules: true }); for (const registration of discoveredControllers) await this.processController(registration); this.logger.log(`Generated OpenAPI spec with ${Object.keys(this.openApiDoc.paths || {}).length} paths`); } catch (error) { this.logger.error("Failed to generate swagger specification", error); } return this.openApiDoc; } async processController(prototype) { for (const [methodName, methodConfig] of prototype.state.methods) if (methodConfig.paths.length > 0) await this.processMethod(prototype, prototype.state.root, methodName, methodConfig); } async processMethod(controller, root, methodName, methodConfig) { if (!methodName) return; const method = methodName.toString(); const name = method.toLowerCase(); for (let methodPath of methodConfig.paths) { methodPath = root.paths[0] + methodPath; methodPath = methodPath.replaceAll(/:([a-zA-Z0-9_]+)/g, "{$1}"); const metadata = Reflect.getMetadata(API_METADATA_KEY, controller.target); for (const verb of methodConfig.verbs) { const key = `${name}:operation`; const description = metadata?.hasOwnProperty(key) ? metadata[key] : null; const httpMethod = verb.toLowerCase(); const operationObject = { tags: [controller.target.name], summary: description?.summary ?? method, description: description?.description ?? "", responses: description?.responses ?? { "200": { description: "Successful response" } } }; operationObject.parameters = this.extractPathParameters(methodPath); const paramTypes = Reflect.getMetadata("design:paramtypes", controller.target, name); const returnType = Reflect.getMetadata("design:returntype", controller.target, name); if (paramTypes) { const additionalParams = this.processParameterTypes(paramTypes); operationObject.parameters = [...operationObject.parameters, ...additionalParams]; } if (returnType && operationObject.responses?.["200"]) operationObject.responses["200"].content = { "application/json": { schema: this.processReturnType(returnType) } }; const operation = { [httpMethod]: operationObject }; this.openApiDoc.paths ??= {}; this.openApiDoc.paths[methodPath] = { ...this.openApiDoc.paths[methodPath], ...operation }; } } } extractPathParameters(path) { const paramRegex = /\{([a-zA-Z0-9_]+)\}/g; const params = []; let match; while ((match = paramRegex.exec(path)) !== null) { const paramName = match[1]; params.push({ name: paramName, in: "path", required: true, schema: { type: "string" }, description: `The ${paramName} parameter` }); } return params; } processParameterTypes(types) { return types.map((type) => this.createParameterDefinition(type)); } processReturnType(type) { return this.createSchemaDefinition(type); } createSchemaDefinition(type) { return { type: "object", properties: this.getTypeProperties(type) }; } createParameterDefinition(type) { return { in: "body", schema: this.createSchemaDefinition(type) }; } getTypeProperties(type) { const properties = {}; const metadata = Reflect.getMetadata("Router Config", type) || {}; Object.keys(metadata).forEach((key) => { properties[key] = { type: this.getPropertyType(metadata[key].type), description: metadata[key].description, example: metadata[key].example }; }); return properties; } getPropertyType(type) { return { String: "string", Number: "number", Boolean: "boolean", Object: "object", Array: "array" }[type?.name] || "string"; } }; //#endregion export { SwaggerGenerator }; //# sourceMappingURL=generator.js.map