@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
JavaScript
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