From 4c583cb67dda87e55bb2bb36e031fdccfdd47cd3 Mon Sep 17 00:00:00 2001 From: Bruno Tot Date: Wed, 17 Apr 2024 13:21:25 +0200 Subject: [PATCH] Add swagger decorator solution --- .vscode/launch.json | 10 +- packages/backend/package.json | 3 +- packages/backend/src/App.ts | 31 +++-- packages/backend/src/config/index.ts | 2 +- packages/backend/src/config/logger/logger.ts | 52 +++++++- packages/backend/src/config/swagger/index.ts | 1 + .../backend/src/config/swagger/swagger.ts | 110 +++++++++++++--- packages/backend/src/config/swagger/types.ts | 100 +++++++++++++++ .../backend/src/config/vars/zodEnvironment.ts | 1 + .../backend/src/controllers/AuthController.ts | 56 +++++++-- .../backend/src/controllers/UserController.ts | 25 +++- packages/backend/src/decorators/Controller.ts | 14 ++- packages/backend/src/decorators/GetMapping.ts | 7 +- packages/backend/src/decorators/Injectable.ts | 7 +- .../backend/src/decorators/PostMapping.ts | 7 +- packages/backend/src/decorators/Route.ts | 24 +++- packages/backend/src/main.ts | 2 - .../backend/src/meta/RoutesMetaService.ts | 6 +- pnpm-lock.yaml | 117 ++++++++++++++++++ 19 files changed, 508 insertions(+), 67 deletions(-) create mode 100644 packages/backend/src/config/swagger/types.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index eb3f10fc..bf75829a 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -13,14 +13,12 @@ "outputCapture": "std", "console": "integratedTerminal", "stopOnEntry": false, + "autoAttachChildProcesses": true, //"internalConsoleOptions": "openOnSessionStart", + "runtimeVersion": "21.7.0", - "runtimeArgs": [ - "--no-warnings", - "--loader", - "ts-node/esm", - "--experimental-specifier-resolution=node" - ], + "runtimeExecutable": "npm", + "runtimeArgs": ["run", "backend:start"], "presentation": { "group": "1" } }, { diff --git a/packages/backend/package.json b/packages/backend/package.json index 297c5807..925486fd 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -7,7 +7,7 @@ "scripts": { "build": "rm -rf dist && npm run compile:ts && cp package.json dist/backend", "compile:ts": "tsc && npm run tsc-alias", - "start": "node --no-warnings --loader ts-node/esm --experimental-specifier-resolution=node ./dist/backend/src/main.js", + "start": "export PACKAGE_JSON_VERSION=$(grep -o '\"version\": *\"[^\"]*\"' package.json | awk -F'\"' '{print $4}') && node --no-warnings --loader ts-node/esm --experimental-specifier-resolution=node ./dist/backend/src/main.js", "tsc-alias": "tsc --project tsconfig.json && tsc-alias -p tsconfig.json" }, "keywords": [], @@ -20,6 +20,7 @@ "bcrypt": "^5.1.1", "bottlejs": "^2.0.1", "express": "^4.18.2", + "http-status": "^1.7.4", "jsonwebtoken": "^9.0.2", "mongoose": "^8.2.0", "swagger-jsdoc": "^6.2.8", diff --git a/packages/backend/src/App.ts b/packages/backend/src/App.ts index 5ce37ecf..18ab0991 100644 --- a/packages/backend/src/App.ts +++ b/packages/backend/src/App.ts @@ -7,9 +7,8 @@ import hpp from "hpp"; import { connect, set } from "mongoose"; import morgan from "morgan"; import swaggerUi from "swagger-ui-express"; -import { $BackendAppConfig } from "./config/BackendAppConfig"; -import { logger, stream } from "./config/logger/logger"; -import { swaggerSpec } from "./config/swagger"; +import { $BackendAppConfig, startupLog, stream } from "./config"; +import { $SwaggerManager } from "./config/swagger"; import { getInjectionClasses } from "./decorators/Injectable"; import { RoutesMetaService } from "./meta/RoutesMetaService"; import { withCredentials } from "./middleware/withCredentials"; @@ -19,11 +18,15 @@ export class App { public app: express.Application; public env: string; public port: string; + public swaggerPath: string; + public url: string; constructor() { this.app = express(); this.env = $BackendAppConfig.env.NODE_ENV; this.port = $BackendAppConfig.env.PORT; + this.swaggerPath = "api-docs"; + this.url = `http://localhost:${this.port}`; this.databaseConnect(); this.initializeMiddlewares(); @@ -34,10 +37,14 @@ export class App { public listen() { this.app.listen(this.port, () => { - logger.info(`=================================`); - logger.info(`======= ENV: ${this.env} =======`); - logger.info(`🚀 App listening on the port ${this.port}`); - logger.info(`=================================`); + startupLog({ + title: "Express app started!", + data: { + "🏠 Env": this.env, + "🚀 App": this.url, + "📝 Swagger": `${this.url}/${this.swaggerPath}`, + }, + }); }); } @@ -89,12 +96,16 @@ export class App { } private initializeSwagger() { - this.app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerSpec)); - this.app.get("/api-docs.json", (req, res) => { + const swaggerSpec = $SwaggerManager.buildSpec(); + this.app.use( + `/${this.swaggerPath}`, + swaggerUi.serve, + swaggerUi.setup(swaggerSpec) + ); + this.app.get(`/${this.swaggerPath}.json`, (_req, res) => { res.setHeader("Content-Type", "application/json"); res.send(swaggerSpec); }); - logger.info(`Docs available at http://localhost:${this.port}/api-docs`); } /*private initializeErrorHandling() { diff --git a/packages/backend/src/config/index.ts b/packages/backend/src/config/index.ts index 0c6fa7d4..54a5bbc4 100644 --- a/packages/backend/src/config/index.ts +++ b/packages/backend/src/config/index.ts @@ -1,9 +1,9 @@ // BackendAppConfig must be imported first in order to be used in other files. export * from "./BackendAppConfig"; +export * from "./swagger"; // Variables used in BackendAppConfig come second. export * from "./vars"; export * from "./ioc"; export * from "./logger"; -export * from "./swagger"; diff --git a/packages/backend/src/config/logger/logger.ts b/packages/backend/src/config/logger/logger.ts index aa5c6fdd..0443c592 100644 --- a/packages/backend/src/config/logger/logger.ts +++ b/packages/backend/src/config/logger/logger.ts @@ -67,4 +67,54 @@ const stream = { }, }; -export { logger, stream }; +type StartupLogProps = { + title: string; + data: Record; + padding?: number; + kvSeparator?: string; +}; + +function startupLog({ + title, + data, + kvSeparator = " : ", + padding = 2, +}: StartupLogProps) { + const center = (text: string, length: number) => { + const remainingSpace = length - text.length; + const leftBorderCount = Math.floor(remainingSpace / 2); + const rightBorderCount = remainingSpace - leftBorderCount; + const left = " ".repeat(leftBorderCount); + const right = " ".repeat(rightBorderCount); + return `${left}${text}${right}`; + }; + + const spacer = " ".repeat(padding); + const hrY = kvSeparator; + + const keyValueLengths = Object.entries(data).map( + ([key, value]) => key.length + hrY.length + value.length + ); + const containerWidth = + Math.max(title.length, ...keyValueLengths) + padding * 2; + const maxKeyLength = Math.max(...Object.keys(data).map((key) => key.length)); + + const hrX = `${"-".repeat(containerWidth)}`; + + const content = Object.entries(data).map(([key, value]) => { + const keyPadding = " ".repeat(maxKeyLength - key.length); + const text = `${key}${keyPadding}${hrY}${value}`; + const remainder = " ".repeat( + containerWidth - text.length - spacer.length * 2 + ); + return `|${spacer}${text}${remainder}${spacer}|`; + }); + + logger.info(`┌${hrX}┐`); + logger.info(`|${center(title, containerWidth)}|`); + logger.info(`├${hrX}┤`); + content.forEach((text) => logger.info(text)); + logger.info(`└${hrX}┘`); +} + +export { StartupLogProps, logger, startupLog, stream }; diff --git a/packages/backend/src/config/swagger/index.ts b/packages/backend/src/config/swagger/index.ts index 980b93ef..5ef1558e 100644 --- a/packages/backend/src/config/swagger/index.ts +++ b/packages/backend/src/config/swagger/index.ts @@ -1 +1,2 @@ export * from "./swagger"; +export * from "./types"; diff --git a/packages/backend/src/config/swagger/swagger.ts b/packages/backend/src/config/swagger/swagger.ts index ed6d5730..850b1fdf 100644 --- a/packages/backend/src/config/swagger/swagger.ts +++ b/packages/backend/src/config/swagger/swagger.ts @@ -1,27 +1,99 @@ +import { Class } from "@org/shared"; import swaggerJsdoc from "swagger-jsdoc"; +//import PackageJson from "./../../../package.json"; +import { $BackendAppConfig } from "../BackendAppConfig"; +import { RoutesMetaService } from "./../../meta"; +import { + SwaggerDefinition, + SwaggerPath, + SwaggerRequestMapping, + SwaggerTag, +} from "./types"; -const packageRoot = (path: string) => `packages/backend/${path}`; - -const options: swaggerJsdoc.Options = { - definition: { - openapi: "3.0.0", - info: { - title: "REST API", - version: "0.0.1", - description: "This is a dynamically generated Swagger API documentation", +const DEFAULT_SWAGGER_DEFINITION: SwaggerDefinition = { + openapi: "3.0.0", + info: { + title: "REST API", + license: { + name: "MIT", + url: "https://spdx.org/licenses/MIT.html", + }, + termsOfService: "http://swagger.io/terms/", + contact: { + email: "", + name: "", + url: "", }, - components: { - securitySchemas: { - bearerAuth: { - type: "http", - scheme: "bearer", - bearerFormat: "JWT", - }, + version: $BackendAppConfig.env.PACKAGE_JSON_VERSION, + description: "This is a dynamically generated Swagger API documentation", + }, + components: { + securitySchemas: { + bearerAuth: { + type: "http", + scheme: "bearer", + bearerFormat: "JWT", }, }, - security: [{ bearerAuth: [] }], }, - apis: [packageRoot("src/controllers/*.ts")], + security: [{ bearerAuth: [] }], + tags: [], + paths: {}, }; -export const swaggerSpec = swaggerJsdoc(options); +export class SwaggerManager { + #definition: SwaggerDefinition; + #controllerClasses: Class[]; + + get definition() { + return this.#definition; + } + + get controllerClasses() { + return this.#controllerClasses; + } + + get tags() { + return this.#definition.tags; + } + + constructor() { + this.#definition = DEFAULT_SWAGGER_DEFINITION; + this.#controllerClasses = []; + } + + registerTag(tagData: SwaggerTag & { constructor: Class }) { + if (this.tags.some((t) => t.name === tagData.name)) return; + const { constructor, ...tag } = tagData; + this.controllerClasses.push(constructor); + this.tags.push(tag); + } + + registerPath( + path: string, + requestMapping: SwaggerRequestMapping, + data: SwaggerPath + ) { + if (!this.definition.paths[path]) this.definition.paths[path] = {}; + this.definition.paths[path][requestMapping] = data; + } + + #registerPaths() { + $SwaggerManager.controllerClasses.forEach((controllerClass) => { + const meta = RoutesMetaService.from(controllerClass).value; + meta.routes.forEach((route) => { + const fullPath = `${meta.basePath}${route.path}`; + const swagger = route.swagger ?? {}; + swagger.tags = [String(controllerClass.name)]; + $SwaggerManager.registerPath(fullPath, route.method, swagger); + }); + }); + } + + buildSpec(): object { + this.#registerPaths(); + return swaggerJsdoc({ definition: this.definition, apis: [] }); + } +} + +export const $SwaggerManager = new SwaggerManager(); diff --git a/packages/backend/src/config/swagger/types.ts b/packages/backend/src/config/swagger/types.ts new file mode 100644 index 00000000..fd9e132b --- /dev/null +++ b/packages/backend/src/config/swagger/types.ts @@ -0,0 +1,100 @@ +import HttpStatus from "http-status"; +import swaggerJsdoc from "swagger-jsdoc"; + +type HttpStatusConverter = { + [K in keyof T]: T[K] extends number ? T[K] : never; +}; + +type HttpStatusMain = Purify>; + +type HttpStatusNumeric = Values; + +/** + * A type that extracts the values from the properties of an object type `T`. + * @typeParam T - An object type. + */ +export type Values = T[keyof T]; + +/** + * A type that excludes properties with values of type `TExclude` from `TParent`. + * @typeParam TParent - The parent type. + * @typeParam TExclude - The type to exclude from `TParent`. + */ +export type Exclude = Pick< + TParent, + Values<{ + [Prop in keyof TParent]: [TParent[Prop]] extends [TExclude] ? never : Prop; + }> +>; + +/** + * A type that removes properties with values of type `never` from `T`. + * @typeParam T - The type to purify. + */ +export type Purify = Exclude; + +export type SwaggerExternalDocs = { + description: string; + url: string; +}; + +export type SwaggerTag = { + name: string; + description?: string; + externalDocs?: SwaggerExternalDocs; +}; + +export type SwaggerPath = { + tags?: string[]; + summary?: string; + description?: string; + operationId?: string; + requestBody?: SwaggerRequestBody; + responses?: SwaggerResponse; +}; + +export type SwaggerPaths = Record< + string, + Partial> +>; + +export type SwaggerRequestMapping = + | "put" + | "get" + | "post" + | "delete" + | "patch" + | "options" + | "head"; + +export type SwaggerResponse = Partial< + Record< + HttpStatusNumeric, + { + description?: string; + content?: SwaggerRequestContent; + } + > +>; + +export type SwaggerEntitySchema = { + $ref?: string; + type?: string; + format?: string; +}; + +export type SwaggerRequestContent = Record< + string, + { schema: SwaggerEntitySchema } +>; + +export type SwaggerRequestBody = { + description?: string; + content?: SwaggerRequestContent; + required?: boolean; +}; + +export type SwaggerDefinition = swaggerJsdoc.SwaggerDefinition & { + tags: SwaggerTag[]; + paths: SwaggerPaths; +}; diff --git a/packages/backend/src/config/vars/zodEnvironment.ts b/packages/backend/src/config/vars/zodEnvironment.ts index 0fad70d7..82443577 100644 --- a/packages/backend/src/config/vars/zodEnvironment.ts +++ b/packages/backend/src/config/vars/zodEnvironment.ts @@ -47,6 +47,7 @@ function parseZodEnvironmentSchema>( export const VAR_ZOD_ENVIRONMENT = parseZodEnvironmentSchema( z.object({ + PACKAGE_JSON_VERSION: z.string().default("?.?.?"), NODE_ENV: z.string().default("development"), PORT: z.string().default("8080"), LOG_FORMAT: z.string().default("dev"), diff --git a/packages/backend/src/controllers/AuthController.ts b/packages/backend/src/controllers/AuthController.ts index 11d988f8..14df4cf9 100644 --- a/packages/backend/src/controllers/AuthController.ts +++ b/packages/backend/src/controllers/AuthController.ts @@ -1,23 +1,31 @@ import { TODO } from "@org/shared"; import bcrypt from "bcrypt"; import { Request, Response } from "express"; +import HttpStatus from "http-status"; import jwt, { VerifyErrors } from "jsonwebtoken"; import { $BackendAppConfig } from "../config/BackendAppConfig"; import { Controller, PostMapping } from "../decorators"; import User from "../domain/MongoUser"; -@Controller("/auth") +@Controller("/auth", { + description: "Authentication", +}) export class AuthController { - /** - * @openapi - * /auth/login: - * post: - * description: Login to the application - * responses: - * 200: - * description: Returns an access token - */ - @PostMapping("/login") + @PostMapping("/login", { + description: "Login user", + summary: "Login user", + responses: { + [HttpStatus.OK]: { + description: "Access token", + }, + [HttpStatus.UNAUTHORIZED]: { + description: "Unauthorized", + }, + [HttpStatus.BAD_REQUEST]: { + description: "Bad request", + }, + }, + }) async login(req: Request, res: Response) { const cookies = req.cookies; @@ -97,7 +105,15 @@ export class AuthController { } } - @PostMapping("/logout") + @PostMapping("/logout", { + description: "Logout user", + summary: "Logout user", + responses: { + [HttpStatus.NO_CONTENT]: { + description: "No content", + }, + }, + }) async logout(req: Request, res: Response) { // On client, also delete the accessToken @@ -128,7 +144,21 @@ export class AuthController { res.sendStatus(204); } - @PostMapping("/refresh") + @PostMapping("/refresh", { + description: "Refresh access token", + summary: "Refresh access token", + responses: { + [HttpStatus.OK]: { + description: "New access", + }, + [HttpStatus.FORBIDDEN]: { + description: "Forbidden", + }, + [HttpStatus.UNAUTHORIZED]: { + description: "Unauthorized", + }, + }, + }) async refresh(req: Request, res: Response) { const cookies = req.cookies; if (!cookies?.jwt) return res.sendStatus(401); diff --git a/packages/backend/src/controllers/UserController.ts b/packages/backend/src/controllers/UserController.ts index 4ecce54e..17db9fe7 100644 --- a/packages/backend/src/controllers/UserController.ts +++ b/packages/backend/src/controllers/UserController.ts @@ -1,5 +1,6 @@ import { Role } from "@org/shared"; import { Request, Response } from "express"; +import HttpStatus from "http-status"; import { Autowired } from "../decorators/Autowired"; import { Controller } from "../decorators/Controller"; import { GetMapping } from "../decorators/GetMapping"; @@ -9,18 +10,36 @@ import { UserService } from "../infrastructure/service/UserService"; import { withJwt } from "../middleware/withJwt"; import { withUserRoles } from "../middleware/withUserRoles"; -@Controller("/users") +@Controller("/users", { + description: "User management", +}) export class UserController { @Autowired() userService: UserService; @Use(withJwt(), withUserRoles(Role.ADMIN)) - @GetMapping() + @GetMapping("", { + description: "Get all users", + summary: "Get all users", + responses: { + [HttpStatus.OK]: { + description: "List of users", + }, + }, + }) async findAll(_req: Request, res: Response) { const users = await this.userService.findAll(); res.json(users); } - @PostMapping() + @PostMapping("", { + description: "Create a user", + summary: "Create a user", + responses: { + [HttpStatus.CREATED]: { + description: "User created", + }, + }, + }) async create(req: Request, res: Response) { const user = await this.userService.create(req.body); res.status(201).json(user); diff --git a/packages/backend/src/decorators/Controller.ts b/packages/backend/src/decorators/Controller.ts index 9d0899e6..0843e6b4 100644 --- a/packages/backend/src/decorators/Controller.ts +++ b/packages/backend/src/decorators/Controller.ts @@ -1,9 +1,19 @@ import { Class } from "@org/shared"; +import { $SwaggerManager, SwaggerTag } from "../config"; import { RoutesMetaService } from "../meta/RoutesMetaService"; import { Injectable } from "./Injectable"; -export function Controller(basePath: string) { - return Injectable((context) => { +export function Controller( + basePath: string, + swaggerTag: Omit = {} +) { + return Injectable((context, constructor) => { + const swaggerTagName = String(context.name!); + $SwaggerManager.registerTag({ + constructor, + name: swaggerTagName, + ...swaggerTag, + }); RoutesMetaService.from(context).setBasePath(basePath); }); } diff --git a/packages/backend/src/decorators/GetMapping.ts b/packages/backend/src/decorators/GetMapping.ts index 1bbba321..82dc6df5 100644 --- a/packages/backend/src/decorators/GetMapping.ts +++ b/packages/backend/src/decorators/GetMapping.ts @@ -1,9 +1,14 @@ +import { SwaggerPath } from "../config"; import { RouteHandler } from "../meta/RoutesMetaService"; import { Route } from "./Route"; -export function GetMapping(path: string = "") { +export function GetMapping( + path: string = "", + swagger?: SwaggerPath +) { return Route({ method: "get", path, + swagger, }); } diff --git a/packages/backend/src/decorators/Injectable.ts b/packages/backend/src/decorators/Injectable.ts index c60f7e71..1c595592 100644 --- a/packages/backend/src/decorators/Injectable.ts +++ b/packages/backend/src/decorators/Injectable.ts @@ -8,7 +8,10 @@ export function getInjectionClasses() { return injectionClasses; } -export type ClassDecoratorSupplier = (context: DecoratorContext) => void; +export type ClassDecoratorSupplier = ( + context: DecoratorContext, + constructor: Class +) => void; export function Injectable( supplier?: ClassDecoratorSupplier @@ -19,7 +22,7 @@ export function Injectable( const targetName = normalizeTargetName(constructorName); InjectionMetaService.from(context).setName(targetName); injectionClasses.push(constructor); - supplier?.(context); + supplier?.(context, constructor); }); } diff --git a/packages/backend/src/decorators/PostMapping.ts b/packages/backend/src/decorators/PostMapping.ts index 17121dd5..54a11036 100644 --- a/packages/backend/src/decorators/PostMapping.ts +++ b/packages/backend/src/decorators/PostMapping.ts @@ -1,9 +1,14 @@ +import { SwaggerPath } from "../config"; import { RouteHandler } from "../meta/RoutesMetaService"; import { Route } from "./Route"; -export function PostMapping(path: string = "") { +export function PostMapping( + path: string = "", + swagger?: SwaggerPath +) { return Route({ method: "post", path, + swagger, }); } diff --git a/packages/backend/src/decorators/Route.ts b/packages/backend/src/decorators/Route.ts index f6b3c2d5..ea0ea27d 100644 --- a/packages/backend/src/decorators/Route.ts +++ b/packages/backend/src/decorators/Route.ts @@ -9,16 +9,23 @@ import { } from "../meta/RoutesMetaService"; // eslint-disable-next-line @typescript-eslint/no-unused-vars import { TODO } from "@org/shared"; +import HttpStatus from "http-status"; +import { SwaggerPath } from "../config"; -export function Route( - props: Omit -) { +export type RouteProps = Omit & { + swagger?: SwaggerPath; +}; + +export function Route({ + swagger = {}, + ...props +}: RouteProps) { return createMethodDecorator(({ target, meta }) => { const context = meta.context; + //const name = String(context.name!); async function handler(req: Request, res: Response) { try { - InjectionMetaService.from(context).value.name; const container = InjectionMetaService.from(context).value.name; const _this = inject(container); return await target.call(_this, req, res); @@ -37,6 +44,15 @@ export function Route( name: String(context.name), middlewares: [], handler, + swagger: { + ...swagger, + responses: { + [HttpStatus.INTERNAL_SERVER_ERROR]: { + description: "Internal server error", + }, + ...(swagger.responses ?? {}), + }, + }, }); return handler as Fn; diff --git a/packages/backend/src/main.ts b/packages/backend/src/main.ts index 7163fdf5..dbe6cf78 100644 --- a/packages/backend/src/main.ts +++ b/packages/backend/src/main.ts @@ -9,5 +9,3 @@ process.on("uncaughtException", (err) => { const app = new App(); app.listen(); - -// Dummy comment diff --git a/packages/backend/src/meta/RoutesMetaService.ts b/packages/backend/src/meta/RoutesMetaService.ts index 2100eae9..6caeaa3e 100644 --- a/packages/backend/src/meta/RoutesMetaService.ts +++ b/packages/backend/src/meta/RoutesMetaService.ts @@ -3,6 +3,7 @@ import { ClassMetadataInjectType, } from "@tsvdec/decorators"; import { NextFunction, Request, Response } from "express"; +import { SwaggerPath } from "../config"; export type RouteMethod = | "get" @@ -37,7 +38,10 @@ export type RequestMappingProps = { middlewares: Array; }; -export type RequestRoute = RequestMappingProps & { handler: RouteHandler }; +export type RequestRoute = RequestMappingProps & { + handler: RouteHandler; + swagger?: SwaggerPath; +}; export type RoutesMetaItem = { basePath: string; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bb233fd7..070f7460 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -50,6 +50,118 @@ importers: express: specifier: ^4.18.2 version: 4.18.2 + http-status: + specifier: ^1.7.4 + version: 1.7.4 + jsonwebtoken: + specifier: ^9.0.2 + version: 9.0.2 + mongoose: + specifier: ^8.2.0 + version: 8.2.0 + swagger-jsdoc: + specifier: ^6.2.8 + version: 6.2.8(openapi-types@12.1.3) + swagger-ui-express: + specifier: ^5.0.0 + version: 5.0.0(express@4.18.2) + zod: + specifier: ^3.22.4 + version: 3.22.4 + devDependencies: + '@types/bcrypt': + specifier: ^5.0.2 + version: 5.0.2 + '@types/compression': + specifier: ^1.7.5 + version: 1.7.5 + '@types/cookie-parser': + specifier: ^1.4.7 + version: 1.4.7 + '@types/cors': + specifier: ^2.8.17 + version: 2.8.17 + '@types/express': + specifier: ^4.17.21 + version: 4.17.21 + '@types/hpp': + specifier: ^0.2.6 + version: 0.2.6 + '@types/jsonwebtoken': + specifier: ^9.0.6 + version: 9.0.6 + '@types/morgan': + specifier: ^1.9.9 + version: 1.9.9 + '@types/node': + specifier: ^20.11.22 + version: 20.11.22 + '@types/swagger-jsdoc': + specifier: ^6.0.4 + version: 6.0.4 + '@types/swagger-ui-express': + specifier: ^4.1.6 + version: 4.1.6 + compression: + specifier: ^1.7.4 + version: 1.7.4 + cookie-parser: + specifier: ^1.4.6 + version: 1.4.6 + cors: + specifier: ^2.8.5 + version: 2.8.5 + cross-dirname: + specifier: ^0.1.0 + version: 0.1.0 + dotenv: + specifier: ^16.4.5 + version: 16.4.5 + helmet: + specifier: ^7.1.0 + version: 7.1.0 + hpp: + specifier: ^0.2.3 + version: 0.2.3 + morgan: + specifier: ^1.10.0 + version: 1.10.0 + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@20.11.22)(typescript@5.3.3) + typescript: + specifier: ^5.3.3 + version: 5.3.3 + winston: + specifier: ^3.11.0 + version: 3.11.0 + winston-daily-rotate-file: + specifier: ^5.0.0 + version: 5.0.0(winston@3.11.0) + + packages/backend/dist/backend: + dependencies: + '@org/shared': + specifier: ^0.0.1 + version: link:../../../shared + '@tsvdec/core': + specifier: ^2.0.11 + version: 2.0.11(@tsvdec/decorators@1.0.7) + '@tsvdec/decorators': + specifier: ^1.0.7 + version: 1.0.7 + bcrypt: + specifier: ^5.1.1 + version: 5.1.1 + bottlejs: + specifier: ^2.0.1 + version: 2.0.1 + express: + specifier: ^4.18.2 + version: 4.18.2 + http-status: + specifier: ^1.7.4 + version: 1.7.4 jsonwebtoken: specifier: ^9.0.2 version: 9.0.2 @@ -3251,6 +3363,11 @@ packages: toidentifier: 1.0.1 dev: false + /http-status@1.7.4: + resolution: {integrity: sha512-c2qSwNtTlHVYAhMj9JpGdyo0No/+DiKXCJ9pHtZ2Yf3QmPnBIytKSRT7BuyIiQ7icXLynavGmxUqkOjSrAuMuA==} + engines: {node: '>= 0.4.0'} + dev: false + /https-proxy-agent@5.0.1: resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} engines: {node: '>= 6'}