Browse Source

v1.1.0: swagger

CrazyDoctor 3 days ago
parent
commit
97f25526a5
51 changed files with 659 additions and 7 deletions
  1. 2 0
      dist/base/http/Route.d.ts
  2. 6 0
      dist/base/http/Route.js
  3. 1 1
      dist/base/http/Route.js.map
  4. 26 0
      dist/base/swagger/SwaggerDoc.d.ts
  5. 124 0
      dist/base/swagger/SwaggerDoc.js
  6. 0 0
      dist/base/swagger/SwaggerDoc.js.map
  7. 9 0
      dist/base/swagger/SwaggerParameterInType.d.ts
  8. 14 0
      dist/base/swagger/SwaggerParameterInType.js
  9. 1 0
      dist/base/swagger/SwaggerParameterInType.js.map
  10. 10 0
      dist/base/swagger/SwaggerParameterType.d.ts
  11. 3 0
      dist/base/swagger/SwaggerParameterType.js
  12. 1 0
      dist/base/swagger/SwaggerParameterType.js.map
  13. 6 0
      dist/base/swagger/SwaggerRequestBodyContentType.d.ts
  14. 3 0
      dist/base/swagger/SwaggerRequestBodyContentType.js
  15. 1 0
      dist/base/swagger/SwaggerRequestBodyContentType.js.map
  16. 6 0
      dist/base/swagger/SwaggerRequestBodyType.d.ts
  17. 3 0
      dist/base/swagger/SwaggerRequestBodyType.js
  18. 1 0
      dist/base/swagger/SwaggerRequestBodyType.js.map
  19. 6 0
      dist/base/swagger/SwaggerResponseContentType.d.ts
  20. 3 0
      dist/base/swagger/SwaggerResponseContentType.js
  21. 1 0
      dist/base/swagger/SwaggerResponseContentType.js.map
  22. 7 0
      dist/base/swagger/SwaggerResponseType.d.ts
  23. 3 0
      dist/base/swagger/SwaggerResponseType.js
  24. 1 0
      dist/base/swagger/SwaggerResponseType.js.map
  25. 8 0
      dist/base/swagger/SwaggerSchemaType.d.ts
  26. 3 0
      dist/base/swagger/SwaggerSchemaType.js
  27. 1 0
      dist/base/swagger/SwaggerSchemaType.js.map
  28. 8 0
      dist/base/swagger/index.d.ts
  29. 8 0
      dist/base/swagger/index.js
  30. 1 0
      dist/base/swagger/index.js.map
  31. 1 0
      dist/index.d.ts
  32. 1 0
      dist/index.js
  33. 1 1
      dist/index.js.map
  34. 8 0
      dist/server/Server.d.ts
  35. 53 0
      dist/server/Server.js
  36. 0 0
      dist/server/Server.js.map
  37. 8 0
      dist/server/ServerProperties.d.ts
  38. 8 0
      lib/base/http/Route.ts
  39. 155 0
      lib/base/swagger/SwaggerDoc.ts
  40. 10 0
      lib/base/swagger/SwaggerParameterInType.ts
  41. 12 0
      lib/base/swagger/SwaggerParameterType.ts
  42. 8 0
      lib/base/swagger/SwaggerRequestBodyContentType.ts
  43. 8 0
      lib/base/swagger/SwaggerRequestBodyType.ts
  44. 8 0
      lib/base/swagger/SwaggerResponseContentType.ts
  45. 9 0
      lib/base/swagger/SwaggerResponseType.ts
  46. 7 0
      lib/base/swagger/SwaggerSchemaType.ts
  47. 8 0
      lib/base/swagger/index.ts
  48. 1 0
      lib/index.ts
  49. 68 0
      lib/server/Server.ts
  50. 12 4
      lib/server/ServerProperties.ts
  51. 6 1
      package.json

+ 2 - 0
dist/base/http/Route.d.ts

@@ -4,6 +4,8 @@ import { HttpHandler } from './HttpHandler';
 declare abstract class Route extends HttpHandler {
     protected abstract method: HttpMethod;
     protected abstract action: (req: Request, res: Response) => any;
+    protected get documentation(): string;
     getMethod(): HttpMethod;
+    getDocumentation(): string;
 }
 export { Route };

+ 6 - 0
dist/base/http/Route.js

@@ -3,9 +3,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
 exports.Route = void 0;
 const HttpHandler_1 = require("./HttpHandler");
 class Route extends HttpHandler_1.HttpHandler {
+    get documentation() {
+        return '';
+    }
     getMethod() {
         return this.method;
     }
+    getDocumentation() {
+        return this.documentation;
+    }
 }
 exports.Route = Route;
 //# sourceMappingURL=Route.js.map

+ 1 - 1
dist/base/http/Route.js.map

@@ -1 +1 @@
-{"version":3,"file":"Route.js","sourceRoot":"","sources":["../../../lib/base/http/Route.ts"],"names":[],"mappings":";;;AAEA,+CAA4C;AAE5C,MAAe,KAAM,SAAQ,yBAAW;IAIhC,SAAS;QACf,OAAO,IAAI,CAAC,MAAM,CAAC;IACpB,CAAC;CACD;AAEQ,sBAAK"}
+{"version":3,"file":"Route.js","sourceRoot":"","sources":["../../../lib/base/http/Route.ts"],"names":[],"mappings":";;;AAEA,+CAA4C;AAE5C,MAAe,KAAM,SAAQ,yBAAW;IAIvC,IAAc,aAAa;QAC1B,OAAO,EAAE,CAAC;IACX,CAAC;IAEM,SAAS;QACf,OAAO,IAAI,CAAC,MAAM,CAAC;IACpB,CAAC;IAEM,gBAAgB;QACtB,OAAO,IAAI,CAAC,aAAa,CAAC;IAC3B,CAAC;CACD;AAEQ,sBAAK"}

+ 26 - 0
dist/base/swagger/SwaggerDoc.d.ts

@@ -0,0 +1,26 @@
+import { SwaggerParameter } from './SwaggerParameterType';
+import { SwaggerRequestBody } from './SwaggerRequestBodyType';
+import { SwaggerResponse } from './SwaggerResponseType';
+declare class SwaggerDoc {
+    private method;
+    private route?;
+    private summary?;
+    private description?;
+    private requestBody?;
+    private parameters;
+    private responses;
+    static get(route: string): SwaggerDoc;
+    static post(route: string): SwaggerDoc;
+    private constructor();
+    private setRoute;
+    setSummary(summary: string): SwaggerDoc;
+    setDescription(description: string): SwaggerDoc;
+    setRequestBody(requestBody: SwaggerRequestBody): SwaggerDoc;
+    addParameter(param: SwaggerParameter): SwaggerDoc;
+    addResponse(res: SwaggerResponse): SwaggerDoc;
+    toAnnotation(): string;
+    private getMethod;
+    private deserializeSchema;
+    private repeatStr;
+}
+export { SwaggerDoc };

+ 124 - 0
dist/base/swagger/SwaggerDoc.js

@@ -0,0 +1,124 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.SwaggerDoc = void 0;
+const http_1 = require("../http");
+class SwaggerDoc {
+    static get(route) {
+        return new SwaggerDoc(http_1.HttpMethod.GET).setRoute(route);
+    }
+    static post(route) {
+        return new SwaggerDoc(http_1.HttpMethod.POST).setRoute(route);
+    }
+    constructor(method) {
+        this.parameters = [];
+        this.responses = [];
+        this.method = method;
+    }
+    setRoute(route) {
+        this.route = route;
+        return this;
+    }
+    setSummary(summary) {
+        this.summary = summary;
+        return this;
+    }
+    setDescription(description) {
+        this.description = description;
+        return this;
+    }
+    setRequestBody(requestBody) {
+        this.requestBody = requestBody;
+        return this;
+    }
+    addParameter(param) {
+        this.parameters.push(param);
+        return this;
+    }
+    addResponse(res) {
+        this.responses.push(res);
+        return this;
+    }
+    toAnnotation() {
+        let annotation = '/**\n';
+        annotation += ' *\t@swagger\n';
+        annotation += ` *\t${this.route}:\n`;
+        annotation += ` *\t\t${this.getMethod()}:\n`;
+        if (this.summary)
+            annotation += ` *\t\t\tsummary: ${this.summary}\n`;
+        if (this.description)
+            annotation += ` *\t\t\tdescription: ${this.description}\n`;
+        if (this.parameters.length > 0) {
+            annotation += ' *\t\t\tparameters:\n';
+            for (const param of this.parameters) {
+                annotation += ` *\t\t\t- in: ${param.in}\n`;
+                annotation += ` *\t\t\t  name: ${param.name}\n`;
+                annotation += ` *\t\t\t  required: ${param.required}\n`;
+                annotation += ` *\t\t\t  description: ${param.description}\n`;
+                if (param.schema) {
+                    annotation += ' *\t\t\t  schema:\n';
+                    annotation += this.deserializeSchema(param.schema, 4);
+                }
+            }
+        }
+        if (this.requestBody) {
+            annotation += ' *\t\t\trequestBody:\n';
+            annotation += ` *\t\t\t\tdescription: ${this.requestBody.description}\n`;
+            annotation += ' *\t\t\t\tcontent:\n';
+            annotation += ` *\t\t\t\t\t${this.requestBody.content.mediaType}:\n`;
+            annotation += ' *\t\t\t\t\t\tschema:\n';
+            annotation += this.deserializeSchema(this.requestBody.content.schema, 6);
+        }
+        if (this.responses.length > 0) {
+            annotation += ' *\t\t\tresponses:\n';
+            for (const res of this.responses) {
+                annotation += ` *\t\t\t\t${res.code}:\n`;
+                annotation += ` *\t\t\t\t\tdescription: ${res.description}\n`;
+                if (res.content) {
+                    annotation += ' *\t\t\t\t\tcontent:\n';
+                    annotation += ` *\t\t\t\t\t\t${res.content.mediaType}:\n`;
+                    annotation += ' *\t\t\t\t\t\t\tschema:\n';
+                    annotation += this.deserializeSchema(res.content.schema, 7);
+                }
+            }
+        }
+        annotation += ' */\n';
+        return annotation.replace(/\t/g, '  ');
+    }
+    getMethod() {
+        switch (this.method) {
+            case http_1.HttpMethod.GET:
+                return 'get';
+            case http_1.HttpMethod.POST:
+                return 'post';
+            default:
+                return 'undefined';
+        }
+    }
+    deserializeSchema(schema, level) {
+        let res = '';
+        const indent = ' *' + this.repeatStr('\t', level + 1);
+        res += indent + `type: ${schema.type}\n`;
+        if (schema.items && schema.items.length > 0) {
+            res += indent + 'items:\n';
+            for (const item of schema.items) {
+                res += this.deserializeSchema(item, level + 2);
+            }
+        }
+        if (schema.properties && Object.keys(schema.properties).length > 0) {
+            res += indent + 'properties:\n';
+            for (const key of Object.keys(schema.properties)) {
+                res += indent + '\t' + `${key}:\n`;
+                res += this.deserializeSchema(schema.properties[key], level + 3);
+            }
+        }
+        return res;
+    }
+    repeatStr(str, n) {
+        let res = '';
+        for (let i = 0; i < n; i++)
+            res += str;
+        return res;
+    }
+}
+exports.SwaggerDoc = SwaggerDoc;
+//# sourceMappingURL=SwaggerDoc.js.map

File diff suppressed because it is too large
+ 0 - 0
dist/base/swagger/SwaggerDoc.js.map


+ 9 - 0
dist/base/swagger/SwaggerParameterInType.d.ts

@@ -0,0 +1,9 @@
+declare enum SwaggerParameterIn {
+    PATH = "path",
+    QUERY = "query",
+    HEADER = "header",
+    COOKIE = "cookie",
+    BODY = "body",
+    REQUEST_BODY = "requestBody"
+}
+export { SwaggerParameterIn };

+ 14 - 0
dist/base/swagger/SwaggerParameterInType.js

@@ -0,0 +1,14 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.SwaggerParameterIn = void 0;
+var SwaggerParameterIn;
+(function (SwaggerParameterIn) {
+    SwaggerParameterIn["PATH"] = "path";
+    SwaggerParameterIn["QUERY"] = "query";
+    SwaggerParameterIn["HEADER"] = "header";
+    SwaggerParameterIn["COOKIE"] = "cookie";
+    SwaggerParameterIn["BODY"] = "body";
+    SwaggerParameterIn["REQUEST_BODY"] = "requestBody"; // since OpenAPI 3.0.0
+})(SwaggerParameterIn || (exports.SwaggerParameterIn = SwaggerParameterIn = {}));
+;
+//# sourceMappingURL=SwaggerParameterInType.js.map

+ 1 - 0
dist/base/swagger/SwaggerParameterInType.js.map

@@ -0,0 +1 @@
+{"version":3,"file":"SwaggerParameterInType.js","sourceRoot":"","sources":["../../../lib/base/swagger/SwaggerParameterInType.ts"],"names":[],"mappings":";;;AAAA,IAAK,kBAOJ;AAPD,WAAK,kBAAkB;IACnB,mCAAa,CAAA;IACb,qCAAe,CAAA;IACf,uCAAiB,CAAA;IACjB,uCAAiB,CAAA;IACjB,mCAAa,CAAA;IACb,kDAA4B,CAAA,CAAC,sBAAsB;AACvD,CAAC,EAPI,kBAAkB,kCAAlB,kBAAkB,QAOtB;AAAA,CAAC"}

+ 10 - 0
dist/base/swagger/SwaggerParameterType.d.ts

@@ -0,0 +1,10 @@
+import { SwaggerParameterIn } from './SwaggerParameterInType';
+import { SwaggerSchema } from './SwaggerSchemaType';
+type SwaggerParameter = {
+    in: SwaggerParameterIn;
+    name: string;
+    required: boolean;
+    description: string;
+    schema?: SwaggerSchema;
+};
+export { SwaggerParameter };

+ 3 - 0
dist/base/swagger/SwaggerParameterType.js

@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=SwaggerParameterType.js.map

+ 1 - 0
dist/base/swagger/SwaggerParameterType.js.map

@@ -0,0 +1 @@
+{"version":3,"file":"SwaggerParameterType.js","sourceRoot":"","sources":["../../../lib/base/swagger/SwaggerParameterType.ts"],"names":[],"mappings":""}

+ 6 - 0
dist/base/swagger/SwaggerRequestBodyContentType.d.ts

@@ -0,0 +1,6 @@
+import { SwaggerSchema } from './SwaggerSchemaType';
+type SwaggerRequestBodyContent = {
+    mediaType: string;
+    schema: SwaggerSchema;
+};
+export { SwaggerRequestBodyContent };

+ 3 - 0
dist/base/swagger/SwaggerRequestBodyContentType.js

@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=SwaggerRequestBodyContentType.js.map

+ 1 - 0
dist/base/swagger/SwaggerRequestBodyContentType.js.map

@@ -0,0 +1 @@
+{"version":3,"file":"SwaggerRequestBodyContentType.js","sourceRoot":"","sources":["../../../lib/base/swagger/SwaggerRequestBodyContentType.ts"],"names":[],"mappings":""}

+ 6 - 0
dist/base/swagger/SwaggerRequestBodyType.d.ts

@@ -0,0 +1,6 @@
+import { SwaggerRequestBodyContent } from './SwaggerRequestBodyContentType';
+type SwaggerRequestBody = {
+    description: string;
+    content: SwaggerRequestBodyContent;
+};
+export { SwaggerRequestBody };

+ 3 - 0
dist/base/swagger/SwaggerRequestBodyType.js

@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=SwaggerRequestBodyType.js.map

+ 1 - 0
dist/base/swagger/SwaggerRequestBodyType.js.map

@@ -0,0 +1 @@
+{"version":3,"file":"SwaggerRequestBodyType.js","sourceRoot":"","sources":["../../../lib/base/swagger/SwaggerRequestBodyType.ts"],"names":[],"mappings":""}

+ 6 - 0
dist/base/swagger/SwaggerResponseContentType.d.ts

@@ -0,0 +1,6 @@
+import { SwaggerSchema } from './SwaggerSchemaType';
+type SwaggerResponseContent = {
+    mediaType: string;
+    schema: SwaggerSchema;
+};
+export { SwaggerResponseContent };

+ 3 - 0
dist/base/swagger/SwaggerResponseContentType.js

@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=SwaggerResponseContentType.js.map

+ 1 - 0
dist/base/swagger/SwaggerResponseContentType.js.map

@@ -0,0 +1 @@
+{"version":3,"file":"SwaggerResponseContentType.js","sourceRoot":"","sources":["../../../lib/base/swagger/SwaggerResponseContentType.ts"],"names":[],"mappings":""}

+ 7 - 0
dist/base/swagger/SwaggerResponseType.d.ts

@@ -0,0 +1,7 @@
+import { SwaggerResponseContent } from './SwaggerResponseContentType';
+type SwaggerResponse = {
+    code: number;
+    description: string;
+    content?: SwaggerResponseContent;
+};
+export { SwaggerResponse };

+ 3 - 0
dist/base/swagger/SwaggerResponseType.js

@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=SwaggerResponseType.js.map

+ 1 - 0
dist/base/swagger/SwaggerResponseType.js.map

@@ -0,0 +1 @@
+{"version":3,"file":"SwaggerResponseType.js","sourceRoot":"","sources":["../../../lib/base/swagger/SwaggerResponseType.ts"],"names":[],"mappings":""}

+ 8 - 0
dist/base/swagger/SwaggerSchemaType.d.ts

@@ -0,0 +1,8 @@
+type SwaggerSchema = {
+    type: string;
+    items?: SwaggerSchema[];
+    properties?: {
+        [name: string]: SwaggerSchema;
+    };
+};
+export { SwaggerSchema };

+ 3 - 0
dist/base/swagger/SwaggerSchemaType.js

@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=SwaggerSchemaType.js.map

+ 1 - 0
dist/base/swagger/SwaggerSchemaType.js.map

@@ -0,0 +1 @@
+{"version":3,"file":"SwaggerSchemaType.js","sourceRoot":"","sources":["../../../lib/base/swagger/SwaggerSchemaType.ts"],"names":[],"mappings":""}

+ 8 - 0
dist/base/swagger/index.d.ts

@@ -0,0 +1,8 @@
+export { SwaggerDoc } from './SwaggerDoc';
+export { SwaggerParameter } from './SwaggerParameterType';
+export { SwaggerParameterIn } from './SwaggerParameterInType';
+export { SwaggerSchema } from './SwaggerSchemaType';
+export { SwaggerResponse } from './SwaggerResponseType';
+export { SwaggerResponseContent } from './SwaggerResponseContentType';
+export { SwaggerRequestBody } from './SwaggerRequestBodyType';
+export { SwaggerRequestBodyContent } from './SwaggerRequestBodyContentType';

+ 8 - 0
dist/base/swagger/index.js

@@ -0,0 +1,8 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.SwaggerParameterIn = exports.SwaggerDoc = void 0;
+var SwaggerDoc_1 = require("./SwaggerDoc");
+Object.defineProperty(exports, "SwaggerDoc", { enumerable: true, get: function () { return SwaggerDoc_1.SwaggerDoc; } });
+var SwaggerParameterInType_1 = require("./SwaggerParameterInType");
+Object.defineProperty(exports, "SwaggerParameterIn", { enumerable: true, get: function () { return SwaggerParameterInType_1.SwaggerParameterIn; } });
+//# sourceMappingURL=index.js.map

+ 1 - 0
dist/base/swagger/index.js.map

@@ -0,0 +1 @@
+{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../lib/base/swagger/index.ts"],"names":[],"mappings":";;;AAAA,2CAA0C;AAAjC,wGAAA,UAAU,OAAA;AAEnB,mEAA8D;AAArD,4HAAA,kBAAkB,OAAA"}

+ 1 - 0
dist/index.d.ts

@@ -5,3 +5,4 @@ export * from './base/websocket';
 export * from './base/util';
 export * from './base/logger';
 export * from './base/i18n';
+export * from './base/swagger';

+ 1 - 0
dist/index.js

@@ -21,4 +21,5 @@ __exportStar(require("./base/websocket"), exports);
 __exportStar(require("./base/util"), exports);
 __exportStar(require("./base/logger"), exports);
 __exportStar(require("./base/i18n"), exports);
+__exportStar(require("./base/swagger"), exports);
 //# sourceMappingURL=index.js.map

+ 1 - 1
dist/index.js.map

@@ -1 +1 @@
-{"version":3,"file":"index.js","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAAyB;AACzB,oDAAkC;AAClC,8CAA4B;AAC5B,mDAAiC;AACjC,8CAA4B;AAC5B,gDAA8B;AAC9B,8CAA4B"}
+{"version":3,"file":"index.js","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAAyB;AACzB,oDAAkC;AAClC,8CAA4B;AAC5B,mDAAiC;AACjC,8CAA4B;AAC5B,gDAA8B;AAC9B,8CAA4B;AAC5B,iDAA+B"}

+ 8 - 0
dist/server/Server.d.ts

@@ -7,6 +7,7 @@ declare class Server {
     private instance;
     private httpServer;
     private readonly port;
+    private readonly host;
     private readonly logger;
     private i18n;
     static readonly i18nDefaultPath = "../resources/i18n.json";
@@ -22,12 +23,18 @@ declare class Server {
     private readonly viewEngine?;
     private readonly viewsPath?;
     private readonly options?;
+    private readonly swaggerDocsPath?;
+    private readonly swaggerTitle?;
+    private readonly swaggerDescription?;
+    private readonly swaggerApiVersion?;
+    private readonly swaggerRoute?;
     constructor(properties: ServerProperties);
     init(): Promise<Server>;
     private postInit;
     private processHttpHandlers;
     addMiddleware(middleware: Middleware): Server;
     addRoute(route: Route): Server;
+    private registerRoutesDocumentation;
     registerWsServers(): Server;
     applyWsHandlers(): Server;
     getWsConnections(url: string): Set<WS> | null;
@@ -37,6 +44,7 @@ declare class Server {
     log(message: Message): void;
     private get;
     private post;
+    private registerSwaggerMiddleware;
     registerRoutes(dir: string): Promise<Server>;
     registerMiddlewares(dir: string): Promise<Server>;
     getLogger(): Logger;

+ 53 - 0
dist/server/Server.js

@@ -50,12 +50,16 @@ const exceptions_3 = require("../base/exceptions");
 const exceptions_4 = require("../base/exceptions");
 const exceptions_5 = require("../base/exceptions");
 const websocket_1 = require("../base/websocket");
+const swagger_jsdoc_1 = __importDefault(require("swagger-jsdoc"));
+const swagger_ui_express_1 = __importDefault(require("swagger-ui-express"));
 /** @sealed */
 class Server {
     constructor(properties) {
+        var _a, _b, _c, _d, _e;
         this.instance = (0, express_1.default)();
         this.httpServer = http_2.default.createServer(this.instance);
         this.port = properties.port;
+        this.host = properties.host || 'http://localhost:3000';
         this.i18n = i18n_1.i18nLoader.getInstance();
         if (properties.locale)
             this.i18n.setLocale(properties.locale);
@@ -69,6 +73,13 @@ class Server {
         this.viewEngine = properties.viewEngine;
         this.viewsPath = properties.viewsPath;
         this.options = properties.options;
+        if (properties.swagger) {
+            this.swaggerDocsPath = (_a = properties.swagger) === null || _a === void 0 ? void 0 : _a.docsPath;
+            this.swaggerTitle = ((_b = properties.swagger) === null || _b === void 0 ? void 0 : _b.title) || 'API Documentation';
+            this.swaggerDescription = ((_c = properties.swagger) === null || _c === void 0 ? void 0 : _c.description) || 'API Documentation';
+            this.swaggerApiVersion = ((_d = properties.swagger) === null || _d === void 0 ? void 0 : _d.version) || '1.0.0';
+            this.swaggerRoute = ((_e = properties.swagger) === null || _e === void 0 ? void 0 : _e.route) || '/api-docs';
+        }
         this.initialized = false;
     }
     init() {
@@ -93,6 +104,8 @@ class Server {
                 yield this.registerMiddlewares(this.middlewaresPath);
             if (this.routesPath)
                 yield this.registerRoutes(this.routesPath);
+            if (this.swaggerDocsPath)
+                fs_1.default.writeFileSync(this.swaggerDocsPath, '');
             if (Object.keys(this.wsHandlers).length > 0) {
                 this.registerWsServers();
                 this.applyWsHandlers();
@@ -130,6 +143,23 @@ class Server {
                 throw new exceptions_2.IncorrectMethodException();
         }
     }
+    registerRoutesDocumentation() {
+        for (const routeName in this.httpHandlers) {
+            const route = this.httpHandlers[routeName];
+            if (!(route instanceof http_1.Route))
+                continue;
+            if (route.getRoute() == null)
+                throw new exceptions_1.RouteNotSetException();
+            if (![http_1.HttpMethod.GET, http_1.HttpMethod.POST].includes(route.getMethod()))
+                throw new exceptions_2.IncorrectMethodException();
+            const docs = route.getDocumentation();
+            if (this.swaggerDocsPath && docs.length > 0) {
+                fs_1.default.appendFileSync(this.swaggerDocsPath, `${docs}\n`);
+                this.logInfo(`Swagger documentation for route '${route.getRoute()}' generated!`);
+            }
+        }
+        return this;
+    }
     registerWsServers() {
         for (const url of Object.keys(this.wsHandlers)) {
             const wsServer = this.wsServers[url] = new ws_1.Server({ noServer: true });
@@ -190,6 +220,27 @@ class Server {
         this.instance.post(route.getRoute(), route.getAction());
         return this;
     }
+    registerSwaggerMiddleware() {
+        if (!this.swaggerDocsPath)
+            return this;
+        const swaggerOptions = {
+            swaggerDefinition: {
+                openapi: '3.0.0',
+                info: {
+                    title: this.swaggerTitle,
+                    version: this.swaggerApiVersion,
+                    description: this.swaggerDescription,
+                },
+                servers: [
+                    { url: this.host }
+                ],
+            },
+            apis: [this.swaggerDocsPath],
+        };
+        const swaggerDocs = (0, swagger_jsdoc_1.default)(swaggerOptions);
+        this.instance.use(this.swaggerRoute, swagger_ui_express_1.default.serve, swagger_ui_express_1.default.setup(swaggerDocs));
+        return this;
+    }
     registerRoutes(dir) {
         return __awaiter(this, void 0, void 0, function* () {
             const files = fs_1.default.readdirSync(dir);
@@ -235,6 +286,8 @@ class Server {
     start(callback) {
         if (!this.initialized)
             throw new exceptions_5.ServerNotInitializedException();
+        this.registerRoutesDocumentation();
+        this.registerSwaggerMiddleware();
         this.processHttpHandlers();
         const cb = () => {
             this.logInfo((0, i18n_1.$$)('org.crazydoctor.expressts.start', { 'port': this.port }));

File diff suppressed because it is too large
+ 0 - 0
dist/server/Server.js.map


+ 8 - 0
dist/server/ServerProperties.d.ts

@@ -1,12 +1,20 @@
 import { WebSocketHandler } from '../base/websocket';
 type ServerProperties = {
     port: number;
+    host?: string;
     locale?: string;
     i18nPath?: string;
     middlewaresPath?: string;
     routesPath?: string;
     viewEngine?: string;
     viewsPath?: string;
+    swagger?: {
+        docsPath?: string;
+        title?: string;
+        version?: string;
+        route?: string;
+        description?: string;
+    };
     wsHandlers?: {
         [url: string]: WebSocketHandler;
     };

+ 8 - 0
lib/base/http/Route.ts

@@ -6,9 +6,17 @@ abstract class Route extends HttpHandler {
 	protected abstract method: HttpMethod;
 	protected abstract action: (req: Request, res: Response) => any;
 
+	protected get documentation(): string {
+		return '';
+	}
+
 	public getMethod(): HttpMethod {
 		return this.method;
 	}
+
+	public getDocumentation(): string {
+		return this.documentation;
+	}
 }
 
 export { Route };

+ 155 - 0
lib/base/swagger/SwaggerDoc.ts

@@ -0,0 +1,155 @@
+import { HttpMethod } from '../http';
+import { SwaggerParameter } from './SwaggerParameterType';
+import { SwaggerRequestBody } from './SwaggerRequestBodyType';
+import { SwaggerResponse } from './SwaggerResponseType';
+import { SwaggerSchema } from './SwaggerSchemaType';
+
+class SwaggerDoc {
+	private method: HttpMethod;
+	private route?: string;
+	private summary?: string;
+	private description?: string;
+	private requestBody?: SwaggerRequestBody;
+	private parameters: SwaggerParameter[] = [];
+	private responses: SwaggerResponse[] = [];
+
+	public static get(route: string): SwaggerDoc {
+		return new SwaggerDoc(HttpMethod.GET).setRoute(route);
+	}
+
+	public static post(route: string): SwaggerDoc {
+		return new SwaggerDoc(HttpMethod.POST).setRoute(route);
+	}
+    
+	private constructor(method: HttpMethod) {
+		this.method = method;
+	}
+
+	private setRoute(route: string): SwaggerDoc {
+		this.route = route;
+		return this;
+	}
+
+	public setSummary(summary: string): SwaggerDoc {
+		this.summary = summary;
+		return this;
+	}
+
+	public setDescription(description: string): SwaggerDoc {
+		this.description = description;
+		return this;
+	}
+
+	public setRequestBody(requestBody: SwaggerRequestBody): SwaggerDoc {
+		this.requestBody = requestBody;
+		return this;
+	}
+
+	public addParameter(param: SwaggerParameter): SwaggerDoc {
+		this.parameters.push(param);
+		return this;
+	}
+
+	public addResponse(res: SwaggerResponse): SwaggerDoc {
+		this.responses.push(res);
+		return this;
+	}
+
+	public toAnnotation(): string {
+		let annotation  =   '/**\n';
+		annotation      +=  ' *\t@swagger\n';
+		annotation      +=  ` *\t${this.route}:\n`;
+		annotation      +=  ` *\t\t${this.getMethod()}:\n`;
+        
+		if(this.summary)
+			annotation  +=  ` *\t\t\tsummary: ${this.summary}\n`;
+        
+		if(this.description)
+			annotation  +=  ` *\t\t\tdescription: ${this.description}\n`;
+        
+		if(this.parameters.length > 0) {
+			annotation	+=	' *\t\t\tparameters:\n';
+			for(const param of this.parameters) {
+				annotation += ` *\t\t\t- in: ${param.in}\n`;
+				annotation += ` *\t\t\t  name: ${param.name}\n`;
+				annotation += ` *\t\t\t  required: ${param.required}\n`;
+				annotation += ` *\t\t\t  description: ${param.description}\n`;
+				if(param.schema) {
+					annotation += ' *\t\t\t  schema:\n';
+					annotation += this.deserializeSchema(param.schema, 4);
+				}
+			}
+		}
+
+		if(this.requestBody) {
+			annotation	+=	' *\t\t\trequestBody:\n';
+			annotation	+=	` *\t\t\t\tdescription: ${this.requestBody.description}\n`;
+			annotation	+=	' *\t\t\t\tcontent:\n';
+			annotation	+=	` *\t\t\t\t\t${this.requestBody.content.mediaType}:\n`;
+			annotation	+=	' *\t\t\t\t\t\tschema:\n';
+			annotation	+=	this.deserializeSchema(this.requestBody.content.schema, 6);
+		}
+
+		if(this.responses.length > 0) {
+			annotation += ' *\t\t\tresponses:\n';
+			for(const res of this.responses) {
+				annotation += ` *\t\t\t\t${res.code}:\n`;
+				annotation += ` *\t\t\t\t\tdescription: ${res.description}\n`;
+				if(res.content) {
+					annotation += ' *\t\t\t\t\tcontent:\n';
+					annotation += ` *\t\t\t\t\t\t${res.content.mediaType}:\n`;
+					annotation += ' *\t\t\t\t\t\t\tschema:\n';
+					annotation += this.deserializeSchema(res.content.schema, 7);
+				}
+			}
+		}
+
+		annotation		+=	' */\n';
+		return annotation.replace(/\t/g, '  ');
+	}
+
+	private getMethod(): string {
+		switch(this.method) {
+		case HttpMethod.GET:
+			return 'get';
+		case HttpMethod.POST:
+			return 'post';
+		default:
+			return 'undefined';
+		}
+	}
+
+	private deserializeSchema(schema: SwaggerSchema, level: number): string {
+		let res = '';
+		const indent = ' *' + this.repeatStr('\t', level + 1);
+		res += indent + `type: ${schema.type}\n`;
+		if(schema.items && schema.items.length > 0) {
+			res += indent + 'items:\n';
+			for(const item of schema.items) {
+				res += this.deserializeSchema(item, level + 2);
+			}
+		}
+
+		if(schema.properties && Object.keys(schema.properties).length > 0) {
+			res += indent + 'properties:\n';
+			for(const key of Object.keys(schema.properties)) {
+				res += indent + '\t' + `${key}:\n`;
+				res += this.deserializeSchema(schema.properties[key], level + 3);
+			}
+		}
+
+		return res;
+	}
+
+	private repeatStr(str: string, n: number): string {
+		let res = '';
+
+		for(let i = 0; i < n; i++)
+			res += str;
+
+		return res;
+	}
+
+}
+
+export { SwaggerDoc };

+ 10 - 0
lib/base/swagger/SwaggerParameterInType.ts

@@ -0,0 +1,10 @@
+enum SwaggerParameterIn {
+    PATH = 'path',
+    QUERY = 'query',
+    HEADER = 'header',
+    COOKIE = 'cookie',
+    BODY = 'body',
+    REQUEST_BODY = 'requestBody' // since OpenAPI 3.0.0
+};
+
+export { SwaggerParameterIn };

+ 12 - 0
lib/base/swagger/SwaggerParameterType.ts

@@ -0,0 +1,12 @@
+import { SwaggerParameterIn } from './SwaggerParameterInType';
+import { SwaggerSchema } from './SwaggerSchemaType';
+
+type SwaggerParameter = {
+    in: SwaggerParameterIn,
+    name: string,
+    required: boolean,
+    description: string,
+    schema?: SwaggerSchema
+};
+
+export { SwaggerParameter };

+ 8 - 0
lib/base/swagger/SwaggerRequestBodyContentType.ts

@@ -0,0 +1,8 @@
+import { SwaggerSchema } from './SwaggerSchemaType';
+
+type SwaggerRequestBodyContent = {
+    mediaType: string,
+    schema: SwaggerSchema
+};
+
+export { SwaggerRequestBodyContent };

+ 8 - 0
lib/base/swagger/SwaggerRequestBodyType.ts

@@ -0,0 +1,8 @@
+import { SwaggerRequestBodyContent } from './SwaggerRequestBodyContentType';
+
+type SwaggerRequestBody = {
+    description: string,
+    content: SwaggerRequestBodyContent
+};
+
+export { SwaggerRequestBody };

+ 8 - 0
lib/base/swagger/SwaggerResponseContentType.ts

@@ -0,0 +1,8 @@
+import { SwaggerSchema } from './SwaggerSchemaType';
+
+type SwaggerResponseContent = {
+    mediaType: string,
+    schema: SwaggerSchema
+};
+
+export { SwaggerResponseContent };

+ 9 - 0
lib/base/swagger/SwaggerResponseType.ts

@@ -0,0 +1,9 @@
+import { SwaggerResponseContent } from './SwaggerResponseContentType';
+
+type SwaggerResponse = {
+    code: number,
+    description: string,
+    content?: SwaggerResponseContent
+};
+
+export { SwaggerResponse };

+ 7 - 0
lib/base/swagger/SwaggerSchemaType.ts

@@ -0,0 +1,7 @@
+type SwaggerSchema = {
+    type: string,
+    items?: SwaggerSchema[],
+    properties?: { [name: string] : SwaggerSchema }
+};
+
+export { SwaggerSchema };

+ 8 - 0
lib/base/swagger/index.ts

@@ -0,0 +1,8 @@
+export { SwaggerDoc } from './SwaggerDoc';
+export { SwaggerParameter } from './SwaggerParameterType';
+export { SwaggerParameterIn } from './SwaggerParameterInType';
+export { SwaggerSchema } from './SwaggerSchemaType';
+export { SwaggerResponse } from './SwaggerResponseType';
+export { SwaggerResponseContent } from './SwaggerResponseContentType';
+export { SwaggerRequestBody } from './SwaggerRequestBodyType';
+export { SwaggerRequestBodyContent } from './SwaggerRequestBodyContentType';

+ 1 - 0
lib/index.ts

@@ -5,3 +5,4 @@ export * from './base/websocket';
 export * from './base/util';
 export * from './base/logger';
 export * from './base/i18n';
+export * from './base/swagger';

+ 68 - 0
lib/server/Server.ts

@@ -13,12 +13,15 @@ import { InvalidMiddlewareException } from '../base/exceptions';
 import { InvalidRouteException } from '../base/exceptions';
 import { ServerNotInitializedException } from '../base/exceptions';
 import { WebSocketHandler } from '../base/websocket';
+import swaggerJsDoc from 'swagger-jsdoc';
+import swaggerUi from 'swagger-ui-express';
 
 /** @sealed */
 class Server {
 	private instance: Express;
 	private httpServer: http.Server;
 	private readonly port: number;
+	private readonly host: string;
 	private readonly logger: Logger;
 	private i18n: i18nLoader;
 	public static readonly i18nDefaultPath = '../resources/i18n.json';
@@ -36,10 +39,17 @@ class Server {
 	private readonly viewsPath?: string;
 	private readonly options?: {[key: string]: any};
 
+	private readonly swaggerDocsPath? : string;
+	private readonly swaggerTitle?: string;
+	private readonly swaggerDescription?: string;
+	private readonly swaggerApiVersion?: string;
+	private readonly swaggerRoute?: string;
+
 	public constructor(properties: ServerProperties) {
 		this.instance = express();
 		this.httpServer = http.createServer(this.instance);
 		this.port = properties.port;
+		this.host = properties.host || 'http://localhost:3000';
 		this.i18n = i18nLoader.getInstance();
 		if(properties.locale)
 			this.i18n.setLocale(properties.locale);
@@ -53,6 +63,13 @@ class Server {
 		this.viewEngine = properties.viewEngine;
 		this.viewsPath = properties.viewsPath;
 		this.options = properties.options;
+		if(properties.swagger) {
+			this.swaggerDocsPath = properties.swagger?.docsPath;
+			this.swaggerTitle = properties.swagger?.title || 'API Documentation';
+			this.swaggerDescription = properties.swagger?.description || 'API Documentation';
+			this.swaggerApiVersion = properties.swagger?.version ||'1.0.0';
+			this.swaggerRoute = properties.swagger?.route || '/api-docs';
+		}
 		this.initialized = false;
 	}
 
@@ -76,6 +93,8 @@ class Server {
 			await this.registerMiddlewares(this.middlewaresPath);
 		if(this.routesPath)
 			await this.registerRoutes(this.routesPath);
+		if(this.swaggerDocsPath)
+			fs.writeFileSync(this.swaggerDocsPath, '');
 		if(Object.keys(this.wsHandlers).length > 0) {
 			this.registerWsServers();
 			this.applyWsHandlers();
@@ -117,6 +136,28 @@ class Server {
 		}
 	}
 
+	private registerRoutesDocumentation(): Server {
+		for(const routeName in this.httpHandlers) {
+			const route = this.httpHandlers[routeName];
+			if(!(route instanceof Route))
+				continue;
+
+			if(route.getRoute() == null)
+				throw new RouteNotSetException();
+	
+			if(![HttpMethod.GET, HttpMethod.POST].includes(route.getMethod()))
+				throw new IncorrectMethodException();
+	
+			const docs = route.getDocumentation();
+	
+			if(this.swaggerDocsPath && docs.length > 0) {
+				fs.appendFileSync(this.swaggerDocsPath, `${docs}\n`);
+				this.logInfo(`Swagger documentation for route '${route.getRoute()}' generated!`);
+			}
+		}
+		return this;
+	}
+
 	public registerWsServers(): Server {
 		for(const url of Object.keys(this.wsHandlers)) {
 			const wsServer = this.wsServers[url] = new WebSocketServer({ noServer: true });
@@ -184,6 +225,31 @@ class Server {
 		return this;
 	}
 
+	private registerSwaggerMiddleware(): Server {
+		if(!this.swaggerDocsPath)
+			return this;
+
+		const swaggerOptions = {
+			swaggerDefinition: {
+				openapi: '3.0.0',
+				info: {
+					title: this.swaggerTitle!,
+					version: this.swaggerApiVersion!,
+					description: this.swaggerDescription!,
+				},
+				servers: [
+					{ url: this.host }
+				],
+			},
+			apis: [this.swaggerDocsPath],
+		};
+
+		const swaggerDocs = swaggerJsDoc(swaggerOptions);
+
+		this.instance.use(this.swaggerRoute!, swaggerUi.serve, swaggerUi.setup(swaggerDocs));
+		return this;
+	}
+
 	public async registerRoutes(dir: string): Promise<Server> {
 		const files = fs.readdirSync(dir);
 
@@ -228,6 +294,8 @@ class Server {
 	public start(callback?: () => any): void {
 		if(!this.initialized)
 			throw new ServerNotInitializedException();
+		this.registerRoutesDocumentation();
+		this.registerSwaggerMiddleware();
 		this.processHttpHandlers();
 		const cb = (): void => {
 			this.logInfo($$('org.crazydoctor.expressts.start', { 'port': this.port }));

+ 12 - 4
lib/server/ServerProperties.ts

@@ -2,14 +2,22 @@ import { WebSocketHandler } from '../base/websocket';
 
 type ServerProperties = {
 	port: number,
+	host?: string,
 	locale?: string,
 	i18nPath?: string,
 	middlewaresPath?: string,
 	routesPath?: string,
-  viewEngine?: string,
-  viewsPath?: string,
-  wsHandlers?: {[url: string] : WebSocketHandler},
-  options?: {[key: string] : any}
+	viewEngine?: string,
+	viewsPath?: string,
+	swagger?: {
+		docsPath?: string,
+		title?: string,
+		version?: string,
+		route?: string,
+		description?: string
+	},
+	wsHandlers?: {[url: string] : WebSocketHandler},
+	options?: {[key: string] : any}
 };
 
 export { ServerProperties };

+ 6 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "org.crazydoctor.expressts",
-  "version": "1.0.8",
+  "version": "1.1.0",
   "devDependencies": {
     "@types/express-session": "^1.17.7",
     "@types/node": "^20.4.9",
@@ -12,6 +12,8 @@
   "dependencies": {
     "@types/express": "^4.17.17",
     "@types/pino": "^7.0.5",
+    "@types/swagger-jsdoc": "^6.0.4",
+    "@types/swagger-ui-express": "^4.1.6",
     "@types/ws": "^8.5.10",
     "express": "^4.18.2",
     "express-session": "^1.17.3",
@@ -19,6 +21,8 @@
     "pino": "^8.14.2",
     "pino-http": "^8.3.3",
     "pino-pretty": "^10.2.0",
+    "swagger-jsdoc": "^6.2.8",
+    "swagger-ui-express": "^5.0.1",
     "ts-node": "^10.9.1",
     "typescript": "^5.1.6",
     "ws": "^8.16.0"
@@ -30,6 +34,7 @@
   ],
   "scripts": {
     "lint": "eslint .",
+    "fixLint": "eslint . --fix",
     "build": "npm run lint && tsc && ncp lib/resources/ dist/resources/"
   }
 }

Some files were not shown because too many files changed in this diff