From dc87e18c1fa7465da25606aa788a6575657382f8 Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 4 May 2026 15:52:46 +0300 Subject: [PATCH 1/3] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20JWT=20=D0=B0=D1=83=D1=82=D0=B5=D0=BD=D1=82?= =?UTF-8?q?=D0=B8=D1=84=D0=B8=D0=BA=D0=B0=D1=86=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- custom.d.ts | 7 ++ package-lock.json | 15 ++++ package.json | 1 + src/main.rest.ts | 18 ++-- src/rest/rest.application.ts | 67 ++++++++------ src/shared/libs/config/rest.config.schema.ts | 51 ++++++----- .../rest/middleware/parse-token.middleware.ts | 56 ++++++++++++ .../middleware/private-route.middleware.ts | 18 ++++ src/shared/modules/auth/auth-container.ts | 19 ++++ .../modules/auth/auth-service.interface.ts | 6 ++ src/shared/modules/auth/auth-service.ts | 58 ++++++++++++ src/shared/modules/auth/auth.constant.ts | 2 + .../modules/auth/auth.exception-filter.ts | 31 +++++++ .../auth/errors/base-user.exception.ts | 7 ++ src/shared/modules/auth/errors/index.ts | 3 + .../auth/errors/user-not-found.exception.ts | 8 ++ .../user-password-incorrect.exception.ts | 8 ++ src/shared/modules/auth/index.ts | 3 + src/shared/modules/auth/types/TokenPayload.ts | 5 ++ src/shared/modules/offer/dto/offer-dto.ts | 44 ++++----- src/shared/modules/offer/offer-controller.ts | 89 ++++++++++++------- .../modules/offer/offer-service.interface.ts | 8 +- src/shared/modules/offer/offer-service.ts | 30 +++---- src/shared/modules/user/rdo/user.rdo.ts | 19 ++-- src/shared/modules/user/user-controller.ts | 79 +++++++++------- .../modules/user/user-service.interface.ts | 10 +-- src/shared/modules/user/user-service.ts | 22 ++--- src/shared/modules/user/user.entity.ts | 25 +++--- src/shared/types/container.ts | 30 ++++--- tsconfig.json | 3 +- 30 files changed, 534 insertions(+), 208 deletions(-) create mode 100644 custom.d.ts create mode 100644 src/shared/libs/rest/middleware/parse-token.middleware.ts create mode 100644 src/shared/libs/rest/middleware/private-route.middleware.ts create mode 100644 src/shared/modules/auth/auth-container.ts create mode 100644 src/shared/modules/auth/auth-service.interface.ts create mode 100644 src/shared/modules/auth/auth-service.ts create mode 100644 src/shared/modules/auth/auth.constant.ts create mode 100644 src/shared/modules/auth/auth.exception-filter.ts create mode 100644 src/shared/modules/auth/errors/base-user.exception.ts create mode 100644 src/shared/modules/auth/errors/index.ts create mode 100644 src/shared/modules/auth/errors/user-not-found.exception.ts create mode 100644 src/shared/modules/auth/errors/user-password-incorrect.exception.ts create mode 100644 src/shared/modules/auth/index.ts create mode 100644 src/shared/modules/auth/types/TokenPayload.ts diff --git a/custom.d.ts b/custom.d.ts new file mode 100644 index 0000000..c4b240b --- /dev/null +++ b/custom.d.ts @@ -0,0 +1,7 @@ +import { TokenPayload } from "./src/shared/modules/auth/types/TokenPayload.js"; + +declare module "express-serve-static-core" { + export interface Request { + tokenPayload: TokenPayload; + } +} diff --git a/package-lock.json b/package-lock.json index d53377f..fd79d5f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "got": "^14.6.6", "http-status-codes": "^2.3.0", "inversify": "^6.0.1", + "jose": "^4.15.4", "mime-types": "3.0.2", "mongoose": "^7.5.3", "multer": "2.1.1", @@ -4159,6 +4160,15 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/jose": { + "version": "4.15.4", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.4.tgz", + "integrity": "sha512-W+oqK4H+r5sITxfxpSU+MMdr/YSWGvgZMQDIsNoBDGGy4i7GBPTtvFKibQzW06n3U3TqHjhvBJsirShsEJ6eeQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/joycon": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", @@ -9766,6 +9776,11 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "jose": { + "version": "4.15.4", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.4.tgz", + "integrity": "sha512-W+oqK4H+r5sITxfxpSU+MMdr/YSWGvgZMQDIsNoBDGGy4i7GBPTtvFKibQzW06n3U3TqHjhvBJsirShsEJ6eeQ==" + }, "joycon": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", diff --git a/package.json b/package.json index 9267412..b41e93b 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "got": "^14.6.6", "http-status-codes": "^2.3.0", "inversify": "^6.0.1", + "jose": "^4.15.4", "mime-types": "3.0.2", "mongoose": "^7.5.3", "multer": "2.1.1", diff --git a/src/main.rest.ts b/src/main.rest.ts index f973b8f..4a6fc26 100644 --- a/src/main.rest.ts +++ b/src/main.rest.ts @@ -1,11 +1,12 @@ -import 'reflect-metadata'; -import { RestApplication } from './rest/index.js'; -import { Container } from 'inversify'; -import { Component } from './shared/types/container.js'; -import { createRestContainer } from './rest/rest.container.js'; -import { createUserContainer } from './shared/modules/user/user.container.js'; -import { createOfferContainer } from './shared/modules/offer/offer.container.js'; -import { createCommentContainer } from './shared/modules/comment/comment.container.js'; +import "reflect-metadata"; +import { RestApplication } from "./rest/index.js"; +import { Container } from "inversify"; +import { Component } from "./shared/types/container.js"; +import { createRestContainer } from "./rest/rest.container.js"; +import { createUserContainer } from "./shared/modules/user/user.container.js"; +import { createOfferContainer } from "./shared/modules/offer/offer.container.js"; +import { createCommentContainer } from "./shared/modules/comment/comment.container.js"; +import { createAuthContainer } from "./shared/modules/auth/auth-container.js"; async function bootstrap() { const appContainer = Container.merge( @@ -13,6 +14,7 @@ async function bootstrap() { createUserContainer(), createOfferContainer(), createCommentContainer(), + createAuthContainer(), ); const application = appContainer.get( diff --git a/src/rest/rest.application.ts b/src/rest/rest.application.ts index 017d029..6f18943 100644 --- a/src/rest/rest.application.ts +++ b/src/rest/rest.application.ts @@ -1,12 +1,13 @@ -import { inject, injectable } from 'inversify'; -import { Config, RestSchema } from '../shared/libs/config/index.js'; -import { Logger } from '../shared/libs/logger/logger.interface.js'; -import { Component } from '../shared/types/container.js'; -import { DatabaseClient } from '../shared/libs/database-client/index.js'; -import { getMongoUri } from '../shared/helpers/database-client.js'; -import express, { Express } from 'express'; -import { Controller } from '../shared/libs/rest/controller/controller.interface.js'; -import { ExceptionFilter } from '../shared/libs/rest/exception-filter/exception-filter.interface.js'; +import { inject, injectable } from "inversify"; +import { Config, RestSchema } from "../shared/libs/config/index.js"; +import { Logger } from "../shared/libs/logger/logger.interface.js"; +import { Component } from "../shared/types/container.js"; +import { DatabaseClient } from "../shared/libs/database-client/index.js"; +import { getMongoUri } from "../shared/helpers/database-client.js"; +import express, { Express } from "express"; +import { Controller } from "../shared/libs/rest/controller/controller.interface.js"; +import { ExceptionFilter } from "../shared/libs/rest/exception-filter/exception-filter.interface.js"; +import { ParseTokenMiddleware } from "../shared/libs/rest/middleware/parse-token.middleware.js"; @injectable() export class RestApplication { @@ -25,52 +26,64 @@ export class RestApplication { private readonly exceptionFilter: ExceptionFilter, @inject(Component.CommentController) private readonly commentController: Controller, + @inject(Component.AuthExceptionFilter) + private readonly authExceptionFilter: ExceptionFilter, ) { this.server = express(); } private async _initDB() { const mongoURI = getMongoUri( - this.config.get('DB_USER'), - this.config.get('DB_PASSWORD'), - this.config.get('DB_HOST'), - this.config.get('DB_PORT'), - this.config.get('DB_NAME'), + this.config.get("DB_USER"), + this.config.get("DB_PASSWORD"), + this.config.get("DB_HOST"), + this.config.get("DB_PORT"), + this.config.get("DB_NAME"), ); return this.databaseClient.connect(mongoURI); } private async _initMiddleware() { + const authenticateMiddleware = new ParseTokenMiddleware( + this.config.get("JWT_SECRET"), + ); + + this.server.use( + authenticateMiddleware.execute.bind(authenticateMiddleware), + ); this.server.use(express.json()); this.server.use( - '/upload', - express.static(this.config.get('UPLOAD_FILE_DIRECTORY')), + "/upload", + express.static(this.config.get("UPLOAD_FILE_DIRECTORY")), ); } private async _initControllers() { - this.server.use('/offers', this.offerController.router); - this.server.use('/users', this.userController.router); - this.server.use('/comments', this.commentController.router); + this.server.use("/offers", this.offerController.router); + this.server.use("/users", this.userController.router); + this.server.use("/comments", this.commentController.router); } private async _initServer() { - const port = this.config.get('PORT'); + const port = this.config.get("PORT"); this.server.listen(port); } private async __initExceptionFilters() { + this.server.use( + this.authExceptionFilter.catch.bind(this.authExceptionFilter), + ); this.server.use(this.exceptionFilter.catch.bind(this.exceptionFilter)); } public async init() { - this.logger.info('Application initialization'); - this.logger.info(`GET value from env $PORT: ${this.config.get('PORT')}`); + this.logger.info("Application initialization"); + this.logger.info(`GET value from env $PORT: ${this.config.get("PORT")}`); - this.logger.info('Init app-level middleware'); + this.logger.info("Init app-level middleware"); await this._initMiddleware(); - this.logger.info('App-level middleware initialization completed'); + this.logger.info("App-level middleware initialization completed"); await this._initServer(); await this._initControllers(); @@ -78,11 +91,11 @@ export class RestApplication { await this.__initExceptionFilters(); this.logger.info( - `🚀 Server started on http://localhost:${this.config.get('PORT')}`, + `🚀 Server started on http://localhost:${this.config.get("PORT")}`, ); - this.logger.info('Database initialization'); + this.logger.info("Database initialization"); await this._initDB(); - this.logger.info('Init database completed'); + this.logger.info("Init database completed"); } } diff --git a/src/shared/libs/config/rest.config.schema.ts b/src/shared/libs/config/rest.config.schema.ts index 64cc4db..ab1cf3e 100644 --- a/src/shared/libs/config/rest.config.schema.ts +++ b/src/shared/libs/config/rest.config.schema.ts @@ -1,5 +1,5 @@ -import convict from 'convict'; -import validator from 'convict-format-with-validator'; +import convict from "convict"; +import validator from "convict-format-with-validator"; export type RestSchema = { PORT: number; @@ -10,57 +10,64 @@ export type RestSchema = { DB_PORT: string; DB_NAME: string; UPLOAD_FILE_DIRECTORY: string; + JWT_SECRET: string; }; convict.addFormats(validator); export const restSchema = convict({ PORT: { - doc: 'Port to bind.', - format: 'port', + doc: "Port to bind.", + format: "port", default: 3000, - env: 'PORT', + env: "PORT", }, SALT: { - doc: 'Salt for password hash', + doc: "Salt for password hash", format: String, default: null, - env: 'SALT', + env: "SALT", }, DB_HOST: { - doc: 'IP address of the database server (MongoDB)', - format: 'ipaddress', - env: 'DB_HOST', - default: '127.0.0.1', + doc: "IP address of the database server (MongoDB)", + format: "ipaddress", + env: "DB_HOST", + default: "127.0.0.1", }, DB_USER: { - doc: 'Username to connect to the database (MongoDB)', + doc: "Username to connect to the database (MongoDB)", format: String, - env: 'DB_USER', + env: "DB_USER", default: null, }, DB_PASSWORD: { - doc: 'Database password (MongoDB)', + doc: "Database password (MongoDB)", format: String, - env: 'DB_PASSWORD', + env: "DB_PASSWORD", default: null, }, DB_PORT: { - doc: 'Database port (MongoDB)', + doc: "Database port (MongoDB)", format: String, - env: 'DB_PORT', - default: '27017', + env: "DB_PORT", + default: "27017", }, DB_NAME: { - doc: 'Database name (MongoDB)', + doc: "Database name (MongoDB)", format: String, - env: 'DB_NAME', + env: "DB_NAME", default: null, }, UPLOAD_FILE_DIRECTORY: { - doc: 'Upload file directory', + doc: "Upload file directory", format: String, - env: 'UPLOAD_FILE_DIRECTORY', + env: "UPLOAD_FILE_DIRECTORY", + default: null, + }, + JWT_SECRET: { + doc: "JWT secret", + format: String, + env: "JWT_SECRET", default: null, }, }); diff --git a/src/shared/libs/rest/middleware/parse-token.middleware.ts b/src/shared/libs/rest/middleware/parse-token.middleware.ts new file mode 100644 index 0000000..0eb6529 --- /dev/null +++ b/src/shared/libs/rest/middleware/parse-token.middleware.ts @@ -0,0 +1,56 @@ +import { NextFunction, Request, Response } from "express"; +import { Middleware } from "../index.js"; +import crypto from "node:crypto"; +import { jwtVerify } from "jose"; +import { HttpError } from "../errors/index.js"; +import { StatusCodes } from "http-status-codes"; +import { TokenPayload } from "../../../modules/auth/types/TokenPayload.js"; + +function isTokenPayload(payload: unknown): payload is TokenPayload { + return ( + typeof payload === "object" && + payload !== null && + "email" in payload && + typeof payload.email === "string" && + "name" in payload && + typeof payload.name === "string" && + "id" in payload && + typeof payload.id === "string" + ); +} + +export class ParseTokenMiddleware implements Middleware { + constructor(private readonly jwtSecret: string) {} + + async execute( + req: Request, + _res: Response, + next: NextFunction, + ): Promise { + const authorizationHeader = req.headers?.authorization?.split(" "); + + if (!authorizationHeader) { + return next(); + } + + const [, token] = authorizationHeader; + + const { payload } = await jwtVerify( + token, + crypto.createSecretKey(this.jwtSecret, "utf-8"), + ); + + if (isTokenPayload(payload)) { + req.tokenPayload = { ...payload }; + return next(); + } + + return next( + new HttpError( + StatusCodes.UNAUTHORIZED, + "Invalid token", + "AuthenticateMiddleware", + ), + ); + } +} diff --git a/src/shared/libs/rest/middleware/private-route.middleware.ts b/src/shared/libs/rest/middleware/private-route.middleware.ts new file mode 100644 index 0000000..9137be8 --- /dev/null +++ b/src/shared/libs/rest/middleware/private-route.middleware.ts @@ -0,0 +1,18 @@ +import { NextFunction, Request, Response } from "express"; +import { Middleware } from "../index.js"; +import { HttpError } from "../errors/index.js"; +import { StatusCodes } from "http-status-codes"; + +export class PrivateRouteMiddleware implements Middleware { + execute(req: Request, _res: Response, next: NextFunction): void { + if (!req.tokenPayload) { + throw new HttpError( + StatusCodes.UNAUTHORIZED, + "Unauthorized", + "PrivateRouteMiddleware", + ); + } + + return next(); + } +} diff --git a/src/shared/modules/auth/auth-container.ts b/src/shared/modules/auth/auth-container.ts new file mode 100644 index 0000000..d8997b7 --- /dev/null +++ b/src/shared/modules/auth/auth-container.ts @@ -0,0 +1,19 @@ +import { Container } from "inversify"; +import { Component } from "../../types/container.js"; +import { DefaultAuthService } from "./auth-service.js"; +import { AuthExceptionFilter } from "./auth.exception-filter.js"; + +export function createAuthContainer() { + const authContainer = new Container(); + + authContainer + .bind(Component.AuthService) + .to(DefaultAuthService) + .inSingletonScope(); + authContainer + .bind(Component.AuthExceptionFilter) + .to(AuthExceptionFilter) + .inSingletonScope(); + + return authContainer; +} diff --git a/src/shared/modules/auth/auth-service.interface.ts b/src/shared/modules/auth/auth-service.interface.ts new file mode 100644 index 0000000..93b19bc --- /dev/null +++ b/src/shared/modules/auth/auth-service.interface.ts @@ -0,0 +1,6 @@ +import { LoginUserDTO, UserEntity } from "../user/index.js"; + +export interface AuthServiceInterface { + authenticate(user: UserEntity): Promise; + verify(dto: LoginUserDTO): Promise; +} diff --git a/src/shared/modules/auth/auth-service.ts b/src/shared/modules/auth/auth-service.ts new file mode 100644 index 0000000..e156aa7 --- /dev/null +++ b/src/shared/modules/auth/auth-service.ts @@ -0,0 +1,58 @@ +import { inject, injectable } from "inversify"; +import { LoginUserDTO, UserEntity } from "../user/index.js"; +import { AuthServiceInterface } from "./auth-service.interface.js"; +import { Component } from "../../types/container.js"; +import { Logger } from "../../libs/logger/index.js"; +import { UserService } from "../user/user-service.js"; +import { Config, RestSchema } from "../../libs/config/index.js"; +import * as crypto from "node:crypto"; +import { TokenPayload } from "./types/TokenPayload.js"; +import { SignJWT } from "jose"; +import { JWT_ALGORITHM, JWT_EXPIRED } from "./auth.constant.js"; +import { + UserNotFoundException, + UserPasswordIncorrectException, +} from "./errors/index.js"; + +@injectable() +export class DefaultAuthService implements AuthServiceInterface { + constructor( + @inject(Component.Logger) private readonly logger: Logger, + @inject(Component.UserService) private readonly userService: UserService, + @inject(Component.Config) private readonly config: Config, + ) {} + public async authenticate(user: UserEntity): Promise { + const secret = this.config.get("JWT_SECRET"); + const secretKey = crypto.createSecretKey(secret, "utf-8"); + + const tokenPayload: TokenPayload = { + email: user.email, + name: user.name, + id: user._id.toString(), + }; + + this.logger.info(`Create token for ${user.email}`); + + return new SignJWT(tokenPayload) + .setProtectedHeader({ alg: JWT_ALGORITHM }) + .setIssuedAt() + .setExpirationTime(JWT_EXPIRED) + .sign(secretKey); + } + + public async verify(dto: LoginUserDTO): Promise { + const user = await this.userService.findByEmail(dto.email); + + if (!user) { + this.logger.warn(`User with ${dto.email} not found`); + throw new UserNotFoundException(); + } + + if (!user.verifyPassword(dto.password, this.config.get("SALT"))) { + this.logger.warn(`Incorrect password for user ${dto.email}`); + throw new UserPasswordIncorrectException(); + } + + return user; + } +} diff --git a/src/shared/modules/auth/auth.constant.ts b/src/shared/modules/auth/auth.constant.ts new file mode 100644 index 0000000..4d9230d --- /dev/null +++ b/src/shared/modules/auth/auth.constant.ts @@ -0,0 +1,2 @@ +export const JWT_ALGORITHM = "HS256"; +export const JWT_EXPIRED = "2d"; diff --git a/src/shared/modules/auth/auth.exception-filter.ts b/src/shared/modules/auth/auth.exception-filter.ts new file mode 100644 index 0000000..c70cac1 --- /dev/null +++ b/src/shared/modules/auth/auth.exception-filter.ts @@ -0,0 +1,31 @@ +import { NextFunction, Request, Response } from "express"; +import { ExceptionFilter } from "../../libs/rest/exception-filter/exception-filter.interface.js"; +import { BaseUserException } from "./index.js"; +import { Logger } from "../../libs/logger/index.js"; +import { Component } from "../../types/container.js"; +import { inject, injectable } from "inversify"; + +@injectable() +export class AuthExceptionFilter implements ExceptionFilter { + constructor(@inject(Component.Logger) private readonly logger: Logger) { + this.logger.info("AuthExceptionFilter was created"); + } + + public catch( + error: unknown, + _req: Request, + res: Response, + next: NextFunction, + ): void { + if (!(error instanceof BaseUserException)) { + return next(error); + } + + this.logger.error(`[AuthModule] ${error.message}`, error); + + res.status(error.httpStatusCode).json({ + type: "AUTHORIZATION", + error: error.message, + }); + } +} diff --git a/src/shared/modules/auth/errors/base-user.exception.ts b/src/shared/modules/auth/errors/base-user.exception.ts new file mode 100644 index 0000000..c0beeeb --- /dev/null +++ b/src/shared/modules/auth/errors/base-user.exception.ts @@ -0,0 +1,7 @@ +import { HttpError } from "../../../libs/rest/errors/index.js"; + +export class BaseUserException extends HttpError { + constructor(httpStatusCode: number, message: string) { + super(httpStatusCode, message); + } +} diff --git a/src/shared/modules/auth/errors/index.ts b/src/shared/modules/auth/errors/index.ts new file mode 100644 index 0000000..ad8d722 --- /dev/null +++ b/src/shared/modules/auth/errors/index.ts @@ -0,0 +1,3 @@ +export * from "./base-user.exception.js"; +export * from "./user-password-incorrect.exception.js"; +export * from "./user-not-found.exception.js"; diff --git a/src/shared/modules/auth/errors/user-not-found.exception.ts b/src/shared/modules/auth/errors/user-not-found.exception.ts new file mode 100644 index 0000000..0bb6dd2 --- /dev/null +++ b/src/shared/modules/auth/errors/user-not-found.exception.ts @@ -0,0 +1,8 @@ +import { StatusCodes } from "http-status-codes"; +import { BaseUserException } from "./base-user.exception.js"; + +export class UserNotFoundException extends BaseUserException { + constructor() { + super(StatusCodes.NOT_FOUND, "User not found"); + } +} diff --git a/src/shared/modules/auth/errors/user-password-incorrect.exception.ts b/src/shared/modules/auth/errors/user-password-incorrect.exception.ts new file mode 100644 index 0000000..f17b937 --- /dev/null +++ b/src/shared/modules/auth/errors/user-password-incorrect.exception.ts @@ -0,0 +1,8 @@ +import { StatusCodes } from "http-status-codes"; +import { BaseUserException } from "./base-user.exception.js"; + +export class UserPasswordIncorrectException extends BaseUserException { + constructor() { + super(StatusCodes.UNAUTHORIZED, "User password incorrect"); + } +} diff --git a/src/shared/modules/auth/index.ts b/src/shared/modules/auth/index.ts new file mode 100644 index 0000000..f41a227 --- /dev/null +++ b/src/shared/modules/auth/index.ts @@ -0,0 +1,3 @@ +export * from "./auth-container.js"; +export * from "./auth-service.interface.js"; +export * from "./errors/index.js"; diff --git a/src/shared/modules/auth/types/TokenPayload.ts b/src/shared/modules/auth/types/TokenPayload.ts new file mode 100644 index 0000000..648388e --- /dev/null +++ b/src/shared/modules/auth/types/TokenPayload.ts @@ -0,0 +1,5 @@ +export type TokenPayload = { + email: string; + name: string; + id: string; +}; diff --git a/src/shared/modules/offer/dto/offer-dto.ts b/src/shared/modules/offer/dto/offer-dto.ts index e9aa503..05159ad 100644 --- a/src/shared/modules/offer/dto/offer-dto.ts +++ b/src/shared/modules/offer/dto/offer-dto.ts @@ -1,4 +1,4 @@ -import { Amenity, City, HousingType } from '../../../types/offer.js'; +import { Amenity, City, HousingType } from "../../../types/offer.js"; import { IsArray, IsBoolean, @@ -11,13 +11,13 @@ import { MaxLength, Min, MinLength, -} from 'class-validator'; -import { OfferDTOValidationMessage } from './offer-dto.messages.js'; +} from "class-validator"; +import { OfferDTOValidationMessage } from "./offer-dto.messages.js"; export class CreateOfferDto { @MinLength(10, { message: OfferDTOValidationMessage.title.minLength }) @MaxLength(100, { message: OfferDTOValidationMessage.title.maxLength }) - title!: string; + title!: string; @MinLength(20, { message: OfferDTOValidationMessage.description.minLength, @@ -25,59 +25,59 @@ export class CreateOfferDto { @MaxLength(1024, { message: OfferDTOValidationMessage.description.maxLength, }) - description!: string; + description!: string; @IsDateString({}, { message: OfferDTOValidationMessage.date.isDate }) - date!: Date; + date!: Date; @IsEnum( - ['Paris', 'Cologne', 'Brussels', 'Amsterdam', 'Hamburg', 'Dusseldorf'], + ["Paris", "Cologne", "Brussels", "Amsterdam", "Hamburg", "Dusseldorf"], { message: OfferDTOValidationMessage.city.isEnum }, ) - city!: City; + city!: City; @IsString({ message: OfferDTOValidationMessage.imagePreview.isString }) - imagePreview!: string; + imagePreview!: string; @IsArray({ message: OfferDTOValidationMessage.images.isArray }) - images!: string[]; + images!: string[]; @IsBoolean({ message: OfferDTOValidationMessage.isPremium.isBoolean }) - isPremium!: boolean; + isPremium!: boolean; @IsBoolean({ message: OfferDTOValidationMessage.isFavorite.isBoolean }) - isFavorite!: boolean; + isFavorite!: boolean; @Min(1, { message: OfferDTOValidationMessage.rating.min }) @Max(5, { message: OfferDTOValidationMessage.rating.max }) - rating!: number; + rating!: number; - @IsEnum(['apartment', 'house', 'room', 'hotel'], { + @IsEnum(["apartment", "house", "room", "hotel"], { message: OfferDTOValidationMessage.type.isEnum, }) - type!: HousingType; + type!: HousingType; @Min(1, { message: OfferDTOValidationMessage.bedrooms.min }) @Max(8, { message: OfferDTOValidationMessage.bedrooms.max }) - bedrooms!: number; + bedrooms!: number; @Min(1, { message: OfferDTOValidationMessage.maxAdults.min }) @Max(10, { message: OfferDTOValidationMessage.maxAdults.max }) - maxAdults!: number; + maxAdults!: number; @Min(100, { message: OfferDTOValidationMessage.price.min }) @Max(100000, { message: OfferDTOValidationMessage.price.max }) - price!: number; + price!: number; @IsArray({ message: OfferDTOValidationMessage.goods.isArray }) - goods!: Amenity[]; + goods!: Amenity[]; @IsObject({ message: OfferDTOValidationMessage.host.isObject }) - host!: string; + host!: string; @IsInt({ message: OfferDTOValidationMessage.commentsCount.isInt }) - commentsCount!: number; + commentsCount!: number; @IsObject({ message: OfferDTOValidationMessage.location.isObject }) - location!: { latitude: number; longitude: number }; + location!: { latitude: number; longitude: number }; } diff --git a/src/shared/modules/offer/offer-controller.ts b/src/shared/modules/offer/offer-controller.ts index be41068..d3690b8 100644 --- a/src/shared/modules/offer/offer-controller.ts +++ b/src/shared/modules/offer/offer-controller.ts @@ -1,21 +1,23 @@ -import { inject } from 'inversify'; -import { BaseController } from '../../libs/rest/controller/base-controller.abstract.js'; -import { Component } from '../../types/container.js'; -import { OfferServiceInterface } from './offer-service.interface.js'; -import { HttpMethod } from '../../libs/rest/index.js'; -import { Request, Response } from 'express'; -import { Logger } from '../../libs/logger/index.js'; -import { fillDTO } from '../../helpers/common.js'; -import { OfferRDO } from './rdo/offer.rdo.js'; -import { StatusCodes } from 'http-status-codes'; -import { ValidateObjectIdMiddleware } from '../../libs/rest/middleware/validate-objectid.middleware.js'; -import { City } from '../../types/offer.js'; -import { CommentServiceInterface } from '../comment/comment-service.interface.js'; -import { ValidateDTOMiddleware } from '../../libs/rest/middleware/validate-object.middleware.js'; -import { CreateOfferDto } from './dto/offer-dto.js'; -import { UpdateOfferDto } from './dto/update-dto.js'; -import { DocumentExistsMiddleware } from '../../libs/rest/middleware/document-exists.middleware.js'; -import { DocumentExists } from '../../libs/rest/types/document-exists.interface.js'; +import { inject } from "inversify"; +import { BaseController } from "../../libs/rest/controller/base-controller.abstract.js"; +import { Component } from "../../types/container.js"; +import { OfferServiceInterface } from "./offer-service.interface.js"; +import { HttpMethod } from "../../libs/rest/index.js"; +import { Request, Response } from "express"; +import { Logger } from "../../libs/logger/index.js"; +import { fillDTO } from "../../helpers/common.js"; +import { OfferRDO } from "./rdo/offer.rdo.js"; +import { StatusCodes } from "http-status-codes"; +import { ValidateObjectIdMiddleware } from "../../libs/rest/middleware/validate-objectid.middleware.js"; +import { City } from "../../types/offer.js"; +import { CommentServiceInterface } from "../comment/comment-service.interface.js"; +import { ValidateDTOMiddleware } from "../../libs/rest/middleware/validate-object.middleware.js"; +import { CreateOfferDto } from "./dto/offer-dto.js"; +import { UpdateOfferDto } from "./dto/update-dto.js"; +import { DocumentExistsMiddleware } from "../../libs/rest/middleware/document-exists.middleware.js"; +import { DocumentExists } from "../../libs/rest/types/document-exists.interface.js"; +import { PrivateRouteMiddleware } from "../../libs/rest/middleware/private-route.middleware.js"; +import { UserServiceInterface } from "../user/user-service.interface.js"; export class OfferController extends BaseController { constructor( @@ -24,50 +26,68 @@ export class OfferController extends BaseController { private readonly offerService: OfferServiceInterface & DocumentExists, @inject(Component.CommentService) private readonly commentService: CommentServiceInterface, + @inject(Component.UserService) + private readonly userService: UserServiceInterface, ) { super(logger); - this.addRoute({ path: '/', method: HttpMethod.Get, handler: this.index }); + this.addRoute({ path: "/", method: HttpMethod.Get, handler: this.index }); this.addRoute({ - path: '/', + path: "/", method: HttpMethod.Post, handler: this.create, - middlewares: [new ValidateDTOMiddleware(CreateOfferDto)], + middlewares: [ + new PrivateRouteMiddleware(), + new ValidateDTOMiddleware(CreateOfferDto), + ], }); this.addRoute({ - path: '/:offerId', + path: "/:offerId", method: HttpMethod.Put, handler: this.update, middlewares: [ - new ValidateObjectIdMiddleware('offerId'), + new PrivateRouteMiddleware(), + new ValidateObjectIdMiddleware("offerId"), new ValidateDTOMiddleware(UpdateOfferDto), - new DocumentExistsMiddleware('offerId', 'offer', this.offerService), + new DocumentExistsMiddleware("offerId", "offer", this.offerService), ], }); this.addRoute({ - path: '/:offerId', + path: "/:offerId", method: HttpMethod.Delete, handler: this.delete, middlewares: [ - new ValidateObjectIdMiddleware('offerId'), - new DocumentExistsMiddleware('offerId', 'offer', this.offerService), + new PrivateRouteMiddleware(), + new ValidateObjectIdMiddleware("offerId"), + new DocumentExistsMiddleware("offerId", "offer", this.offerService), ], }); this.addRoute({ - path: '/:offerId/comments', + path: "/:offerId/comments", method: HttpMethod.Get, handler: this.getComments, middlewares: [ - new ValidateObjectIdMiddleware('offerId'), - new DocumentExistsMiddleware('offerId', 'offer', this.offerService), + new ValidateObjectIdMiddleware("offerId"), + new DocumentExistsMiddleware("offerId", "offer", this.offerService), ], }); } public async index(req: Request, res: Response) { - console.log(req.body); const offers = await this.offerService.find(); - const responseData = fillDTO(OfferRDO, offers); + let favoritesIds: string[] = []; + + if (req.tokenPayload) { + const user = await this.userService.findById(req.tokenPayload.id); + favoritesIds = + user?.favorites.map((offer) => offer.toString()) ?? ([] as string[]); + } + + const offersWithStatus = offers.map((offer) => ({ + ...offer.toObject(), + isFavorite: favoritesIds.includes(offer.id), + })); + const responseData = fillDTO(OfferRDO, offersWithStatus); this.ok(res, responseData); } @@ -83,7 +103,10 @@ export class OfferController extends BaseController { return this.logger.error(error.message, error); } - const result = await this.offerService.createOffer(req.body); + const result = await this.offerService.createOffer({ + ...req.body, + userId: req.tokenPayload.id, + }); this.created(res, fillDTO(OfferRDO, result)); } diff --git a/src/shared/modules/offer/offer-service.interface.ts b/src/shared/modules/offer/offer-service.interface.ts index 6f85822..174eac2 100644 --- a/src/shared/modules/offer/offer-service.interface.ts +++ b/src/shared/modules/offer/offer-service.interface.ts @@ -1,7 +1,7 @@ -import { DocumentType } from '@typegoose/typegoose'; -import { CreateOfferDto } from './dto/offer-dto.js'; -import { OfferEntity } from './offer.entity.js'; -import { City, UpdateOfferDto } from './dto/update-dto.js'; +import { DocumentType } from "@typegoose/typegoose"; +import { CreateOfferDto } from "./dto/offer-dto.js"; +import { OfferEntity } from "./offer.entity.js"; +import { City, UpdateOfferDto } from "./dto/update-dto.js"; export interface OfferServiceInterface { createOffer(dto: CreateOfferDto): Promise>; diff --git a/src/shared/modules/offer/offer-service.ts b/src/shared/modules/offer/offer-service.ts index 8ad7b2b..26cc78d 100644 --- a/src/shared/modules/offer/offer-service.ts +++ b/src/shared/modules/offer/offer-service.ts @@ -1,14 +1,14 @@ -import { inject, injectable } from 'inversify'; -import { CreateOfferDto } from './dto/offer-dto.js'; -import { OfferServiceInterface } from './offer-service.interface.js'; -import { OfferEntity } from './offer.entity.js'; -import { Component } from '../../types/container.js'; -import { Logger } from '../../libs/logger/index.js'; -import { City, UpdateOfferDto } from './dto/update-dto.js'; -import { DocumentType, types } from '@typegoose/typegoose'; -import { CommentEntity } from '../comment/comment.entity.js'; -import { Types } from 'mongoose'; -import { DocumentExists } from '../../libs/rest/types/document-exists.interface.js'; +import { inject, injectable } from "inversify"; +import { CreateOfferDto } from "./dto/offer-dto.js"; +import { OfferServiceInterface } from "./offer-service.interface.js"; +import { OfferEntity } from "./offer.entity.js"; +import { Component } from "../../types/container.js"; +import { Logger } from "../../libs/logger/index.js"; +import { City, UpdateOfferDto } from "./dto/update-dto.js"; +import { DocumentType, types } from "@typegoose/typegoose"; +import { CommentEntity } from "../comment/comment.entity.js"; +import { Types } from "mongoose"; +import { DocumentExists } from "../../libs/rest/types/document-exists.interface.js"; @injectable() export class OfferService implements OfferServiceInterface, DocumentExists { @@ -27,7 +27,7 @@ export class OfferService implements OfferServiceInterface, DocumentExists { } async findOfferById(id: string): Promise | null> { - return this.offerModel.findById(id).populate('host').exec(); + return this.offerModel.findById(id).populate("host").exec(); } async exists(documentId: string): Promise { @@ -51,7 +51,7 @@ export class OfferService implements OfferServiceInterface, DocumentExists { async recalcRating(offerId: string): Promise { const stats = await this.commentModel.aggregate([ { $match: { offerId: new Types.ObjectId(offerId) } }, - { $group: { _id: '$offerId', avgRating: { $avg: '$rating' } } }, + { $group: { _id: "$offerId", avgRating: { $avg: "$rating" } } }, ]); const avg = stats[0]?.avgRating ?? 0; @@ -64,7 +64,7 @@ export class OfferService implements OfferServiceInterface, DocumentExists { } async find(): Promise[]> { - return this.offerModel.find().populate('host').exec(); + return this.offerModel.find().populate("host").exec(); } async findPremiumOffersByCity( @@ -72,7 +72,7 @@ export class OfferService implements OfferServiceInterface, DocumentExists { ): Promise[]> { return this.offerModel .find({ city, isPremium: true }) - .populate('host') + .populate("host") .exec(); } diff --git a/src/shared/modules/user/rdo/user.rdo.ts b/src/shared/modules/user/rdo/user.rdo.ts index 6da44a2..3f2011f 100644 --- a/src/shared/modules/user/rdo/user.rdo.ts +++ b/src/shared/modules/user/rdo/user.rdo.ts @@ -1,18 +1,25 @@ -import { Expose } from 'class-transformer'; +import { Expose } from "class-transformer"; export class UserRDO { @Expose() - name: string; + name: string; @Expose() - email: string; + email: string; @Expose() - avatar: string; + avatar: string; @Expose() - isPro: boolean; + isPro: boolean; @Expose() - favorites: string[]; + favorites: string[]; +} + +export class LoggedUserRDO { + @Expose() + public email: string; + @Expose() + public token: string; } diff --git a/src/shared/modules/user/user-controller.ts b/src/shared/modules/user/user-controller.ts index d60315a..af3f779 100644 --- a/src/shared/modules/user/user-controller.ts +++ b/src/shared/modules/user/user-controller.ts @@ -1,17 +1,18 @@ -import { inject } from 'inversify'; -import { BaseController } from '../../libs/rest/controller/base-controller.abstract.js'; -import { Component } from '../../types/container.js'; -import { HttpMethod } from '../../libs/rest/index.js'; -import { Request, Response } from 'express'; -import { Logger } from '../../libs/logger/index.js'; -import { UserServiceInterface } from './user-service.interface.js'; -import { StatusCodes } from 'http-status-codes'; -import { Config, RestSchema } from '../../libs/config/index.js'; -import { UserRDO } from './rdo/user.rdo.js'; -import { fillDTO } from '../../helpers/common.js'; -import { HttpError } from '../../libs/rest/errors/http-error.js'; -import { ValidateDTOMiddleware } from '../../libs/rest/middleware/validate-object.middleware.js'; -import { CreateUserDto, LoginUserDTO } from './dto/user-dto.js'; +import { inject } from "inversify"; +import { BaseController } from "../../libs/rest/controller/base-controller.abstract.js"; +import { Component } from "../../types/container.js"; +import { HttpMethod } from "../../libs/rest/index.js"; +import { Request, Response } from "express"; +import { Logger } from "../../libs/logger/index.js"; +import { UserServiceInterface } from "./user-service.interface.js"; +import { StatusCodes } from "http-status-codes"; +import { Config, RestSchema } from "../../libs/config/index.js"; +import { LoggedUserRDO, UserRDO } from "./rdo/user.rdo.js"; +import { fillDTO } from "../../helpers/common.js"; +import { HttpError } from "../../libs/rest/errors/http-error.js"; +import { ValidateDTOMiddleware } from "../../libs/rest/middleware/validate-object.middleware.js"; +import { CreateUserDto, LoginUserDTO } from "./dto/user-dto.js"; +import { AuthServiceInterface, UserNotFoundException } from "../auth/index.js"; export class UserController extends BaseController { constructor( @@ -19,27 +20,35 @@ export class UserController extends BaseController { @inject(Component.UserService) private readonly userService: UserServiceInterface, @inject(Component.Config) private readonly config: Config, + @inject(Component.AuthService) + private readonly authService: AuthServiceInterface, ) { super(logger); this.addRoute({ - path: '/register', + path: "/register", method: HttpMethod.Post, handler: this.create, middlewares: [new ValidateDTOMiddleware(CreateUserDto)], }); this.addRoute({ - path: '/login', + path: "/login", method: HttpMethod.Post, handler: this.auth, middlewares: [new ValidateDTOMiddleware(LoginUserDTO)], }); this.addRoute({ - path: '/:userId/avatar', + path: "/:userId/avatar", method: HttpMethod.Post, handler: this.uploadAvatar, }); + + this.addRoute({ + path: "/login", + method: HttpMethod.Get, + handler: this.checkAuthenticate, + }); } public async create(req: Request, res: Response) { @@ -54,26 +63,23 @@ export class UserController extends BaseController { const result = await this.userService.register( req.body, - this.config.get('SALT'), + this.config.get("SALT"), ); this.created(res, fillDTO(UserRDO, result)); } - public async auth(req: Request, _res: Response) { - const existsUser = await this.userService.findByEmail(req.body.email); + public async auth(req: Request, res: Response) { + const user = await this.authService.verify(req.body as LoginUserDTO); - if (!existsUser) { - throw new HttpError( - StatusCodes.NOT_FOUND, - `User with email ${req.body.email} not found`, - ); + if (!user) { + throw new UserNotFoundException(); } - throw new HttpError( - StatusCodes.NOT_IMPLEMENTED, - 'Not implemented', - 'UserController', - ); + const token = await this.authService.authenticate(user); + + const response = fillDTO(LoggedUserRDO, { email: user.email, token }); + + this.ok(res, response); } public uploadAvatar(req: Request, res: Response) { @@ -81,4 +87,17 @@ export class UserController extends BaseController { file: req.file?.path, }); } + + public async checkAuthenticate(req: Request, res: Response) { + const user = await this.userService.findByEmail(req.tokenPayload.email); + + if (!user) { + throw new HttpError( + StatusCodes.UNAUTHORIZED, + "Unauthorized", + "UserController", + ); + } + this.ok(res, fillDTO(LoggedUserRDO, user)); + } } diff --git a/src/shared/modules/user/user-service.interface.ts b/src/shared/modules/user/user-service.interface.ts index 065559c..273ba98 100644 --- a/src/shared/modules/user/user-service.interface.ts +++ b/src/shared/modules/user/user-service.interface.ts @@ -1,12 +1,12 @@ -import { DocumentType } from '@typegoose/typegoose'; -import { CreateUserDto } from './dto/user-dto.js'; -import { UserEntity } from './user.entity.js'; -import { OfferEntity } from '../offer/offer.entity.js'; +import { DocumentType } from "@typegoose/typegoose"; +import { CreateUserDto } from "./dto/user-dto.js"; +import { UserEntity } from "./user.entity.js"; +import { OfferEntity } from "../offer/offer.entity.js"; export interface UserServiceInterface { register(dto: CreateUserDto, salt: string): Promise>; findByEmail(email: string): Promise | null>; - findById(id: number): Promise | null>; + findById(id: string): Promise | null>; findFavoriteOffers(userId: number): Promise[]>; toggleFavoriteOffer(offerId: string, userId: number): Promise; } diff --git a/src/shared/modules/user/user-service.ts b/src/shared/modules/user/user-service.ts index f2f7851..f208746 100644 --- a/src/shared/modules/user/user-service.ts +++ b/src/shared/modules/user/user-service.ts @@ -1,11 +1,11 @@ -import { DocumentType } from '@typegoose/typegoose'; -import { CreateUserDto } from './dto/user-dto.js'; -import { UserServiceInterface } from './user-service.interface.js'; -import { UserEntity, UserModel } from './user.entity.js'; -import { inject, injectable } from 'inversify'; -import { Component } from '../../types/container.js'; -import { Logger } from '../../libs/logger/index.js'; -import { OfferEntity } from '../offer/offer.entity.js'; +import { DocumentType } from "@typegoose/typegoose"; +import { CreateUserDto } from "./dto/user-dto.js"; +import { UserServiceInterface } from "./user-service.interface.js"; +import { UserEntity, UserModel } from "./user.entity.js"; +import { inject, injectable } from "inversify"; +import { Component } from "../../types/container.js"; +import { Logger } from "../../libs/logger/index.js"; +import { OfferEntity } from "../offer/offer.entity.js"; @injectable() export class UserService implements UserServiceInterface { @@ -31,20 +31,20 @@ export class UserService implements UserServiceInterface { return this.userModel.findOne({ email }).exec(); } - async findById(id: number): Promise | null> { + async findById(id: string): Promise | null> { return this.userModel.findById(id).exec(); } async findFavoriteOffers( userId: number, ): Promise[]> { - const user = await UserModel.findById(userId).populate('favorites').exec(); + const user = await UserModel.findById(userId).populate("favorites").exec(); return (user?.favorites as DocumentType[]) ?? []; } async toggleFavoriteOffer(offerId: string, userId: number): Promise { - const user = await UserModel.findById(userId).populate('favorites').exec(); + const user = await UserModel.findById(userId).populate("favorites").exec(); if (!user) { return; } diff --git a/src/shared/modules/user/user.entity.ts b/src/shared/modules/user/user.entity.ts index f0fb7ef..1785392 100644 --- a/src/shared/modules/user/user.entity.ts +++ b/src/shared/modules/user/user.entity.ts @@ -1,6 +1,6 @@ -import { createSha256 } from '../../helpers/hash.js'; -import { User } from '../../types/user.interface.js'; -import type { OfferEntity } from '../offer/offer.entity.js'; +import { createSha256 } from "../../helpers/hash.js"; +import { User } from "../../types/user.interface.js"; +import type { OfferEntity } from "../offer/offer.entity.js"; import { defaultClasses, @@ -8,36 +8,36 @@ import { modelOptions, prop, Ref, -} from '@typegoose/typegoose'; +} from "@typegoose/typegoose"; // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging export interface UserEntity extends defaultClasses.Base {} @modelOptions({ schemaOptions: { - collection: 'users', + collection: "users", timestamps: true, }, }) // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging export class UserEntity extends defaultClasses.TimeStamps implements User { - @prop({ required: true, minlength: 1, maxlength: 15, default: '' }) + @prop({ required: true, minlength: 1, maxlength: 15, default: "" }) public name!: string; - @prop({ required: true, default: '', unique: true }) + @prop({ required: true, default: "", unique: true }) public email: string; - @prop({ required: false, default: '' }) + @prop({ required: false, default: "" }) public avatar: string; - @prop({ required: true, minlength: 6, maxlength: 1024, default: '' }) + @prop({ required: true, minlength: 6, maxlength: 1024, default: "" }) public password: string; @prop({ required: true }) public isPro: boolean; - @prop({ ref: 'OfferEntity', default: [] }) + @prop({ ref: "OfferEntity", default: [] }) public favorites: Ref[]; constructor(userData: User) { @@ -53,6 +53,11 @@ export class UserEntity extends defaultClasses.TimeStamps implements User { this.password = createSha256(password, salt); } + public verifyPassword(password: string, salt: string): boolean { + const hash = createSha256(password, salt); + return this.password === hash; + } + public getPassword(): string { return this.password; } diff --git a/src/shared/types/container.ts b/src/shared/types/container.ts index 8a3a25f..e2c0e9c 100644 --- a/src/shared/types/container.ts +++ b/src/shared/types/container.ts @@ -1,16 +1,18 @@ export const Component = { - RestApplication: Symbol.for('RestApplication'), - Logger: Symbol.for('Logger'), - Config: Symbol.for('Config'), - DatabaseClient: Symbol.for('DatabaseClient'), - UserService: Symbol.for('UserService'), - UserModel: Symbol.for('UserModel'), - OfferService: Symbol.for('OfferService'), - OfferModel: Symbol.for('OfferModel'), - CommentService: Symbol.for('CommentService'), - CommentModel: Symbol.for('CommentModel'), - OfferController: Symbol.for('OfferController'), - UserController: Symbol.for('UserController'), - ExceptionFilter: Symbol.for('ExceptionFilter'), - CommentController: Symbol.for('CommentController'), + RestApplication: Symbol.for("RestApplication"), + Logger: Symbol.for("Logger"), + Config: Symbol.for("Config"), + DatabaseClient: Symbol.for("DatabaseClient"), + UserService: Symbol.for("UserService"), + UserModel: Symbol.for("UserModel"), + OfferService: Symbol.for("OfferService"), + OfferModel: Symbol.for("OfferModel"), + CommentService: Symbol.for("CommentService"), + CommentModel: Symbol.for("CommentModel"), + OfferController: Symbol.for("OfferController"), + UserController: Symbol.for("UserController"), + ExceptionFilter: Symbol.for("ExceptionFilter"), + CommentController: Symbol.for("CommentController"), + AuthService: Symbol.for("AuthService"), + AuthExceptionFilter: Symbol.for("AuthExceptionFilter"), } as const; diff --git a/tsconfig.json b/tsconfig.json index 54102f0..8686727 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -37,5 +37,6 @@ ], "ts-node": { "esm": true - } + }, + "files": ["custom.d.ts"] } From 70745d42e062ee6ca9e7c8687149773811d109d1 Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 4 May 2026 15:54:58 +0300 Subject: [PATCH 2/3] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20JWT=20=D0=B0=D1=83=D1=82=D0=B5=D0=BD=D1=82?= =?UTF-8?q?=D0=B8=D1=84=D0=B8=D0=BA=D0=B0=D1=86=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.rest.ts | 18 +++--- src/rest/rest.application.ts | 58 ++++++++--------- src/shared/libs/config/rest.config.schema.ts | 48 +++++++------- .../rest/middleware/parse-token.middleware.ts | 36 +++++------ .../middleware/private-route.middleware.ts | 12 ++-- src/shared/modules/auth/auth-container.ts | 8 +-- .../modules/auth/auth-service.interface.ts | 2 +- src/shared/modules/auth/auth-service.ts | 31 +++++----- src/shared/modules/auth/auth.constant.ts | 4 +- .../modules/auth/auth.exception-filter.ts | 16 ++--- .../auth/errors/base-user.exception.ts | 2 +- src/shared/modules/auth/errors/index.ts | 6 +- .../auth/errors/user-not-found.exception.ts | 6 +- .../user-password-incorrect.exception.ts | 6 +- src/shared/modules/auth/index.ts | 6 +- .../modules/comment/comment-controller.ts | 6 +- src/shared/modules/offer/dto/offer-dto.ts | 44 ++++++------- src/shared/modules/offer/offer-controller.ts | 62 +++++++++---------- .../modules/offer/offer-service.interface.ts | 8 +-- src/shared/modules/offer/offer-service.ts | 30 ++++----- src/shared/modules/user/rdo/user.rdo.ts | 13 ++-- src/shared/modules/user/user-controller.ts | 44 ++++++------- .../modules/user/user-service.interface.ts | 8 +-- src/shared/modules/user/user-service.ts | 20 +++--- src/shared/modules/user/user.entity.ts | 20 +++--- src/shared/types/container.ts | 32 +++++----- 26 files changed, 276 insertions(+), 270 deletions(-) diff --git a/src/main.rest.ts b/src/main.rest.ts index 4a6fc26..9dafe1f 100644 --- a/src/main.rest.ts +++ b/src/main.rest.ts @@ -1,12 +1,12 @@ -import "reflect-metadata"; -import { RestApplication } from "./rest/index.js"; -import { Container } from "inversify"; -import { Component } from "./shared/types/container.js"; -import { createRestContainer } from "./rest/rest.container.js"; -import { createUserContainer } from "./shared/modules/user/user.container.js"; -import { createOfferContainer } from "./shared/modules/offer/offer.container.js"; -import { createCommentContainer } from "./shared/modules/comment/comment.container.js"; -import { createAuthContainer } from "./shared/modules/auth/auth-container.js"; +import 'reflect-metadata'; +import { RestApplication } from './rest/index.js'; +import { Container } from 'inversify'; +import { Component } from './shared/types/container.js'; +import { createRestContainer } from './rest/rest.container.js'; +import { createUserContainer } from './shared/modules/user/user.container.js'; +import { createOfferContainer } from './shared/modules/offer/offer.container.js'; +import { createCommentContainer } from './shared/modules/comment/comment.container.js'; +import { createAuthContainer } from './shared/modules/auth/auth-container.js'; async function bootstrap() { const appContainer = Container.merge( diff --git a/src/rest/rest.application.ts b/src/rest/rest.application.ts index 6f18943..d23f675 100644 --- a/src/rest/rest.application.ts +++ b/src/rest/rest.application.ts @@ -1,13 +1,13 @@ -import { inject, injectable } from "inversify"; -import { Config, RestSchema } from "../shared/libs/config/index.js"; -import { Logger } from "../shared/libs/logger/logger.interface.js"; -import { Component } from "../shared/types/container.js"; -import { DatabaseClient } from "../shared/libs/database-client/index.js"; -import { getMongoUri } from "../shared/helpers/database-client.js"; -import express, { Express } from "express"; -import { Controller } from "../shared/libs/rest/controller/controller.interface.js"; -import { ExceptionFilter } from "../shared/libs/rest/exception-filter/exception-filter.interface.js"; -import { ParseTokenMiddleware } from "../shared/libs/rest/middleware/parse-token.middleware.js"; +import { inject, injectable } from 'inversify'; +import { Config, RestSchema } from '../shared/libs/config/index.js'; +import { Logger } from '../shared/libs/logger/logger.interface.js'; +import { Component } from '../shared/types/container.js'; +import { DatabaseClient } from '../shared/libs/database-client/index.js'; +import { getMongoUri } from '../shared/helpers/database-client.js'; +import express, { Express } from 'express'; +import { Controller } from '../shared/libs/rest/controller/controller.interface.js'; +import { ExceptionFilter } from '../shared/libs/rest/exception-filter/exception-filter.interface.js'; +import { ParseTokenMiddleware } from '../shared/libs/rest/middleware/parse-token.middleware.js'; @injectable() export class RestApplication { @@ -34,11 +34,11 @@ export class RestApplication { private async _initDB() { const mongoURI = getMongoUri( - this.config.get("DB_USER"), - this.config.get("DB_PASSWORD"), - this.config.get("DB_HOST"), - this.config.get("DB_PORT"), - this.config.get("DB_NAME"), + this.config.get('DB_USER'), + this.config.get('DB_PASSWORD'), + this.config.get('DB_HOST'), + this.config.get('DB_PORT'), + this.config.get('DB_NAME'), ); return this.databaseClient.connect(mongoURI); @@ -46,7 +46,7 @@ export class RestApplication { private async _initMiddleware() { const authenticateMiddleware = new ParseTokenMiddleware( - this.config.get("JWT_SECRET"), + this.config.get('JWT_SECRET'), ); this.server.use( @@ -54,19 +54,19 @@ export class RestApplication { ); this.server.use(express.json()); this.server.use( - "/upload", - express.static(this.config.get("UPLOAD_FILE_DIRECTORY")), + '/upload', + express.static(this.config.get('UPLOAD_FILE_DIRECTORY')), ); } private async _initControllers() { - this.server.use("/offers", this.offerController.router); - this.server.use("/users", this.userController.router); - this.server.use("/comments", this.commentController.router); + this.server.use('/offers', this.offerController.router); + this.server.use('/users', this.userController.router); + this.server.use('/comments', this.commentController.router); } private async _initServer() { - const port = this.config.get("PORT"); + const port = this.config.get('PORT'); this.server.listen(port); } @@ -78,12 +78,12 @@ export class RestApplication { } public async init() { - this.logger.info("Application initialization"); - this.logger.info(`GET value from env $PORT: ${this.config.get("PORT")}`); + this.logger.info('Application initialization'); + this.logger.info(`GET value from env $PORT: ${this.config.get('PORT')}`); - this.logger.info("Init app-level middleware"); + this.logger.info('Init app-level middleware'); await this._initMiddleware(); - this.logger.info("App-level middleware initialization completed"); + this.logger.info('App-level middleware initialization completed'); await this._initServer(); await this._initControllers(); @@ -91,11 +91,11 @@ export class RestApplication { await this.__initExceptionFilters(); this.logger.info( - `🚀 Server started on http://localhost:${this.config.get("PORT")}`, + `🚀 Server started on http://localhost:${this.config.get('PORT')}`, ); - this.logger.info("Database initialization"); + this.logger.info('Database initialization'); await this._initDB(); - this.logger.info("Init database completed"); + this.logger.info('Init database completed'); } } diff --git a/src/shared/libs/config/rest.config.schema.ts b/src/shared/libs/config/rest.config.schema.ts index ab1cf3e..a318da0 100644 --- a/src/shared/libs/config/rest.config.schema.ts +++ b/src/shared/libs/config/rest.config.schema.ts @@ -1,5 +1,5 @@ -import convict from "convict"; -import validator from "convict-format-with-validator"; +import convict from 'convict'; +import validator from 'convict-format-with-validator'; export type RestSchema = { PORT: number; @@ -17,57 +17,57 @@ convict.addFormats(validator); export const restSchema = convict({ PORT: { - doc: "Port to bind.", - format: "port", + doc: 'Port to bind.', + format: 'port', default: 3000, - env: "PORT", + env: 'PORT', }, SALT: { - doc: "Salt for password hash", + doc: 'Salt for password hash', format: String, default: null, - env: "SALT", + env: 'SALT', }, DB_HOST: { - doc: "IP address of the database server (MongoDB)", - format: "ipaddress", - env: "DB_HOST", - default: "127.0.0.1", + doc: 'IP address of the database server (MongoDB)', + format: 'ipaddress', + env: 'DB_HOST', + default: '127.0.0.1', }, DB_USER: { - doc: "Username to connect to the database (MongoDB)", + doc: 'Username to connect to the database (MongoDB)', format: String, - env: "DB_USER", + env: 'DB_USER', default: null, }, DB_PASSWORD: { - doc: "Database password (MongoDB)", + doc: 'Database password (MongoDB)', format: String, - env: "DB_PASSWORD", + env: 'DB_PASSWORD', default: null, }, DB_PORT: { - doc: "Database port (MongoDB)", + doc: 'Database port (MongoDB)', format: String, - env: "DB_PORT", - default: "27017", + env: 'DB_PORT', + default: '27017', }, DB_NAME: { - doc: "Database name (MongoDB)", + doc: 'Database name (MongoDB)', format: String, - env: "DB_NAME", + env: 'DB_NAME', default: null, }, UPLOAD_FILE_DIRECTORY: { - doc: "Upload file directory", + doc: 'Upload file directory', format: String, - env: "UPLOAD_FILE_DIRECTORY", + env: 'UPLOAD_FILE_DIRECTORY', default: null, }, JWT_SECRET: { - doc: "JWT secret", + doc: 'JWT secret', format: String, - env: "JWT_SECRET", + env: 'JWT_SECRET', default: null, }, }); diff --git a/src/shared/libs/rest/middleware/parse-token.middleware.ts b/src/shared/libs/rest/middleware/parse-token.middleware.ts index 0eb6529..294efde 100644 --- a/src/shared/libs/rest/middleware/parse-token.middleware.ts +++ b/src/shared/libs/rest/middleware/parse-token.middleware.ts @@ -1,21 +1,21 @@ -import { NextFunction, Request, Response } from "express"; -import { Middleware } from "../index.js"; -import crypto from "node:crypto"; -import { jwtVerify } from "jose"; -import { HttpError } from "../errors/index.js"; -import { StatusCodes } from "http-status-codes"; -import { TokenPayload } from "../../../modules/auth/types/TokenPayload.js"; +import { NextFunction, Request, Response } from 'express'; +import { Middleware } from '../index.js'; +import crypto from 'node:crypto'; +import { jwtVerify } from 'jose'; +import { HttpError } from '../errors/index.js'; +import { StatusCodes } from 'http-status-codes'; +import { TokenPayload } from '../../../modules/auth/types/TokenPayload.js'; function isTokenPayload(payload: unknown): payload is TokenPayload { return ( - typeof payload === "object" && + typeof payload === 'object' && payload !== null && - "email" in payload && - typeof payload.email === "string" && - "name" in payload && - typeof payload.name === "string" && - "id" in payload && - typeof payload.id === "string" + 'email' in payload && + typeof payload.email === 'string' && + 'name' in payload && + typeof payload.name === 'string' && + 'id' in payload && + typeof payload.id === 'string' ); } @@ -27,7 +27,7 @@ export class ParseTokenMiddleware implements Middleware { _res: Response, next: NextFunction, ): Promise { - const authorizationHeader = req.headers?.authorization?.split(" "); + const authorizationHeader = req.headers?.authorization?.split(' '); if (!authorizationHeader) { return next(); @@ -37,7 +37,7 @@ export class ParseTokenMiddleware implements Middleware { const { payload } = await jwtVerify( token, - crypto.createSecretKey(this.jwtSecret, "utf-8"), + crypto.createSecretKey(this.jwtSecret, 'utf-8'), ); if (isTokenPayload(payload)) { @@ -48,8 +48,8 @@ export class ParseTokenMiddleware implements Middleware { return next( new HttpError( StatusCodes.UNAUTHORIZED, - "Invalid token", - "AuthenticateMiddleware", + 'Invalid token', + 'AuthenticateMiddleware', ), ); } diff --git a/src/shared/libs/rest/middleware/private-route.middleware.ts b/src/shared/libs/rest/middleware/private-route.middleware.ts index 9137be8..3d9f247 100644 --- a/src/shared/libs/rest/middleware/private-route.middleware.ts +++ b/src/shared/libs/rest/middleware/private-route.middleware.ts @@ -1,15 +1,15 @@ -import { NextFunction, Request, Response } from "express"; -import { Middleware } from "../index.js"; -import { HttpError } from "../errors/index.js"; -import { StatusCodes } from "http-status-codes"; +import { NextFunction, Request, Response } from 'express'; +import { Middleware } from '../index.js'; +import { HttpError } from '../errors/index.js'; +import { StatusCodes } from 'http-status-codes'; export class PrivateRouteMiddleware implements Middleware { execute(req: Request, _res: Response, next: NextFunction): void { if (!req.tokenPayload) { throw new HttpError( StatusCodes.UNAUTHORIZED, - "Unauthorized", - "PrivateRouteMiddleware", + 'Unauthorized', + 'PrivateRouteMiddleware', ); } diff --git a/src/shared/modules/auth/auth-container.ts b/src/shared/modules/auth/auth-container.ts index d8997b7..3f71fc2 100644 --- a/src/shared/modules/auth/auth-container.ts +++ b/src/shared/modules/auth/auth-container.ts @@ -1,7 +1,7 @@ -import { Container } from "inversify"; -import { Component } from "../../types/container.js"; -import { DefaultAuthService } from "./auth-service.js"; -import { AuthExceptionFilter } from "./auth.exception-filter.js"; +import { Container } from 'inversify'; +import { Component } from '../../types/container.js'; +import { DefaultAuthService } from './auth-service.js'; +import { AuthExceptionFilter } from './auth.exception-filter.js'; export function createAuthContainer() { const authContainer = new Container(); diff --git a/src/shared/modules/auth/auth-service.interface.ts b/src/shared/modules/auth/auth-service.interface.ts index 93b19bc..66098ab 100644 --- a/src/shared/modules/auth/auth-service.interface.ts +++ b/src/shared/modules/auth/auth-service.interface.ts @@ -1,4 +1,4 @@ -import { LoginUserDTO, UserEntity } from "../user/index.js"; +import { LoginUserDTO, UserEntity } from '../user/index.js'; export interface AuthServiceInterface { authenticate(user: UserEntity): Promise; diff --git a/src/shared/modules/auth/auth-service.ts b/src/shared/modules/auth/auth-service.ts index e156aa7..dae5c63 100644 --- a/src/shared/modules/auth/auth-service.ts +++ b/src/shared/modules/auth/auth-service.ts @@ -1,18 +1,18 @@ -import { inject, injectable } from "inversify"; -import { LoginUserDTO, UserEntity } from "../user/index.js"; -import { AuthServiceInterface } from "./auth-service.interface.js"; -import { Component } from "../../types/container.js"; -import { Logger } from "../../libs/logger/index.js"; -import { UserService } from "../user/user-service.js"; -import { Config, RestSchema } from "../../libs/config/index.js"; -import * as crypto from "node:crypto"; -import { TokenPayload } from "./types/TokenPayload.js"; -import { SignJWT } from "jose"; -import { JWT_ALGORITHM, JWT_EXPIRED } from "./auth.constant.js"; +import { inject, injectable } from 'inversify'; +import { LoginUserDTO, UserEntity } from '../user/index.js'; +import { AuthServiceInterface } from './auth-service.interface.js'; +import { Component } from '../../types/container.js'; +import { Logger } from '../../libs/logger/index.js'; +import { UserService } from '../user/user-service.js'; +import { Config, RestSchema } from '../../libs/config/index.js'; +import * as crypto from 'node:crypto'; +import { TokenPayload } from './types/TokenPayload.js'; +import { SignJWT } from 'jose'; +import { JWT_ALGORITHM, JWT_EXPIRED } from './auth.constant.js'; import { UserNotFoundException, UserPasswordIncorrectException, -} from "./errors/index.js"; +} from './errors/index.js'; @injectable() export class DefaultAuthService implements AuthServiceInterface { @@ -21,9 +21,10 @@ export class DefaultAuthService implements AuthServiceInterface { @inject(Component.UserService) private readonly userService: UserService, @inject(Component.Config) private readonly config: Config, ) {} + public async authenticate(user: UserEntity): Promise { - const secret = this.config.get("JWT_SECRET"); - const secretKey = crypto.createSecretKey(secret, "utf-8"); + const secret = this.config.get('JWT_SECRET'); + const secretKey = crypto.createSecretKey(secret, 'utf-8'); const tokenPayload: TokenPayload = { email: user.email, @@ -48,7 +49,7 @@ export class DefaultAuthService implements AuthServiceInterface { throw new UserNotFoundException(); } - if (!user.verifyPassword(dto.password, this.config.get("SALT"))) { + if (!user.verifyPassword(dto.password, this.config.get('SALT'))) { this.logger.warn(`Incorrect password for user ${dto.email}`); throw new UserPasswordIncorrectException(); } diff --git a/src/shared/modules/auth/auth.constant.ts b/src/shared/modules/auth/auth.constant.ts index 4d9230d..269747d 100644 --- a/src/shared/modules/auth/auth.constant.ts +++ b/src/shared/modules/auth/auth.constant.ts @@ -1,2 +1,2 @@ -export const JWT_ALGORITHM = "HS256"; -export const JWT_EXPIRED = "2d"; +export const JWT_ALGORITHM = 'HS256'; +export const JWT_EXPIRED = '2d'; diff --git a/src/shared/modules/auth/auth.exception-filter.ts b/src/shared/modules/auth/auth.exception-filter.ts index c70cac1..ef8fc3c 100644 --- a/src/shared/modules/auth/auth.exception-filter.ts +++ b/src/shared/modules/auth/auth.exception-filter.ts @@ -1,14 +1,14 @@ -import { NextFunction, Request, Response } from "express"; -import { ExceptionFilter } from "../../libs/rest/exception-filter/exception-filter.interface.js"; -import { BaseUserException } from "./index.js"; -import { Logger } from "../../libs/logger/index.js"; -import { Component } from "../../types/container.js"; -import { inject, injectable } from "inversify"; +import { NextFunction, Request, Response } from 'express'; +import { ExceptionFilter } from '../../libs/rest/exception-filter/exception-filter.interface.js'; +import { BaseUserException } from './index.js'; +import { Logger } from '../../libs/logger/index.js'; +import { Component } from '../../types/container.js'; +import { inject, injectable } from 'inversify'; @injectable() export class AuthExceptionFilter implements ExceptionFilter { constructor(@inject(Component.Logger) private readonly logger: Logger) { - this.logger.info("AuthExceptionFilter was created"); + this.logger.info('AuthExceptionFilter was created'); } public catch( @@ -24,7 +24,7 @@ export class AuthExceptionFilter implements ExceptionFilter { this.logger.error(`[AuthModule] ${error.message}`, error); res.status(error.httpStatusCode).json({ - type: "AUTHORIZATION", + type: 'AUTHORIZATION', error: error.message, }); } diff --git a/src/shared/modules/auth/errors/base-user.exception.ts b/src/shared/modules/auth/errors/base-user.exception.ts index c0beeeb..c9eb2a2 100644 --- a/src/shared/modules/auth/errors/base-user.exception.ts +++ b/src/shared/modules/auth/errors/base-user.exception.ts @@ -1,4 +1,4 @@ -import { HttpError } from "../../../libs/rest/errors/index.js"; +import { HttpError } from '../../../libs/rest/errors/index.js'; export class BaseUserException extends HttpError { constructor(httpStatusCode: number, message: string) { diff --git a/src/shared/modules/auth/errors/index.ts b/src/shared/modules/auth/errors/index.ts index ad8d722..4aa1752 100644 --- a/src/shared/modules/auth/errors/index.ts +++ b/src/shared/modules/auth/errors/index.ts @@ -1,3 +1,3 @@ -export * from "./base-user.exception.js"; -export * from "./user-password-incorrect.exception.js"; -export * from "./user-not-found.exception.js"; +export * from './base-user.exception.js'; +export * from './user-password-incorrect.exception.js'; +export * from './user-not-found.exception.js'; diff --git a/src/shared/modules/auth/errors/user-not-found.exception.ts b/src/shared/modules/auth/errors/user-not-found.exception.ts index 0bb6dd2..d5833d0 100644 --- a/src/shared/modules/auth/errors/user-not-found.exception.ts +++ b/src/shared/modules/auth/errors/user-not-found.exception.ts @@ -1,8 +1,8 @@ -import { StatusCodes } from "http-status-codes"; -import { BaseUserException } from "./base-user.exception.js"; +import { StatusCodes } from 'http-status-codes'; +import { BaseUserException } from './base-user.exception.js'; export class UserNotFoundException extends BaseUserException { constructor() { - super(StatusCodes.NOT_FOUND, "User not found"); + super(StatusCodes.NOT_FOUND, 'User not found'); } } diff --git a/src/shared/modules/auth/errors/user-password-incorrect.exception.ts b/src/shared/modules/auth/errors/user-password-incorrect.exception.ts index f17b937..fa2e994 100644 --- a/src/shared/modules/auth/errors/user-password-incorrect.exception.ts +++ b/src/shared/modules/auth/errors/user-password-incorrect.exception.ts @@ -1,8 +1,8 @@ -import { StatusCodes } from "http-status-codes"; -import { BaseUserException } from "./base-user.exception.js"; +import { StatusCodes } from 'http-status-codes'; +import { BaseUserException } from './base-user.exception.js'; export class UserPasswordIncorrectException extends BaseUserException { constructor() { - super(StatusCodes.UNAUTHORIZED, "User password incorrect"); + super(StatusCodes.UNAUTHORIZED, 'User password incorrect'); } } diff --git a/src/shared/modules/auth/index.ts b/src/shared/modules/auth/index.ts index f41a227..935ea1f 100644 --- a/src/shared/modules/auth/index.ts +++ b/src/shared/modules/auth/index.ts @@ -1,3 +1,3 @@ -export * from "./auth-container.js"; -export * from "./auth-service.interface.js"; -export * from "./errors/index.js"; +export * from './auth-container.js'; +export * from './auth-service.interface.js'; +export * from './errors/index.js'; diff --git a/src/shared/modules/comment/comment-controller.ts b/src/shared/modules/comment/comment-controller.ts index bdf4c74..8e67cec 100644 --- a/src/shared/modules/comment/comment-controller.ts +++ b/src/shared/modules/comment/comment-controller.ts @@ -9,6 +9,7 @@ import { Request, Response } from 'express'; import { CommentRDO } from './rdo/comment-rdo.js'; import { CommentDto } from './dto/comment-dto.js'; import { ValidateDTOMiddleware } from '../../libs/rest/middleware/validate-object.middleware.js'; +import { PrivateRouteMiddleware } from '../../libs/rest/middleware/private-route.middleware.js'; export class CommentController extends BaseController { constructor( @@ -22,7 +23,10 @@ export class CommentController extends BaseController { path: '/', method: HttpMethod.Post, handler: this.create, - middlewares: [new ValidateDTOMiddleware(CommentDto)], + middlewares: [ + new ValidateDTOMiddleware(CommentDto), + new PrivateRouteMiddleware(), + ], }); } diff --git a/src/shared/modules/offer/dto/offer-dto.ts b/src/shared/modules/offer/dto/offer-dto.ts index 05159ad..e9aa503 100644 --- a/src/shared/modules/offer/dto/offer-dto.ts +++ b/src/shared/modules/offer/dto/offer-dto.ts @@ -1,4 +1,4 @@ -import { Amenity, City, HousingType } from "../../../types/offer.js"; +import { Amenity, City, HousingType } from '../../../types/offer.js'; import { IsArray, IsBoolean, @@ -11,13 +11,13 @@ import { MaxLength, Min, MinLength, -} from "class-validator"; -import { OfferDTOValidationMessage } from "./offer-dto.messages.js"; +} from 'class-validator'; +import { OfferDTOValidationMessage } from './offer-dto.messages.js'; export class CreateOfferDto { @MinLength(10, { message: OfferDTOValidationMessage.title.minLength }) @MaxLength(100, { message: OfferDTOValidationMessage.title.maxLength }) - title!: string; + title!: string; @MinLength(20, { message: OfferDTOValidationMessage.description.minLength, @@ -25,59 +25,59 @@ export class CreateOfferDto { @MaxLength(1024, { message: OfferDTOValidationMessage.description.maxLength, }) - description!: string; + description!: string; @IsDateString({}, { message: OfferDTOValidationMessage.date.isDate }) - date!: Date; + date!: Date; @IsEnum( - ["Paris", "Cologne", "Brussels", "Amsterdam", "Hamburg", "Dusseldorf"], + ['Paris', 'Cologne', 'Brussels', 'Amsterdam', 'Hamburg', 'Dusseldorf'], { message: OfferDTOValidationMessage.city.isEnum }, ) - city!: City; + city!: City; @IsString({ message: OfferDTOValidationMessage.imagePreview.isString }) - imagePreview!: string; + imagePreview!: string; @IsArray({ message: OfferDTOValidationMessage.images.isArray }) - images!: string[]; + images!: string[]; @IsBoolean({ message: OfferDTOValidationMessage.isPremium.isBoolean }) - isPremium!: boolean; + isPremium!: boolean; @IsBoolean({ message: OfferDTOValidationMessage.isFavorite.isBoolean }) - isFavorite!: boolean; + isFavorite!: boolean; @Min(1, { message: OfferDTOValidationMessage.rating.min }) @Max(5, { message: OfferDTOValidationMessage.rating.max }) - rating!: number; + rating!: number; - @IsEnum(["apartment", "house", "room", "hotel"], { + @IsEnum(['apartment', 'house', 'room', 'hotel'], { message: OfferDTOValidationMessage.type.isEnum, }) - type!: HousingType; + type!: HousingType; @Min(1, { message: OfferDTOValidationMessage.bedrooms.min }) @Max(8, { message: OfferDTOValidationMessage.bedrooms.max }) - bedrooms!: number; + bedrooms!: number; @Min(1, { message: OfferDTOValidationMessage.maxAdults.min }) @Max(10, { message: OfferDTOValidationMessage.maxAdults.max }) - maxAdults!: number; + maxAdults!: number; @Min(100, { message: OfferDTOValidationMessage.price.min }) @Max(100000, { message: OfferDTOValidationMessage.price.max }) - price!: number; + price!: number; @IsArray({ message: OfferDTOValidationMessage.goods.isArray }) - goods!: Amenity[]; + goods!: Amenity[]; @IsObject({ message: OfferDTOValidationMessage.host.isObject }) - host!: string; + host!: string; @IsInt({ message: OfferDTOValidationMessage.commentsCount.isInt }) - commentsCount!: number; + commentsCount!: number; @IsObject({ message: OfferDTOValidationMessage.location.isObject }) - location!: { latitude: number; longitude: number }; + location!: { latitude: number; longitude: number }; } diff --git a/src/shared/modules/offer/offer-controller.ts b/src/shared/modules/offer/offer-controller.ts index d3690b8..0c67e7d 100644 --- a/src/shared/modules/offer/offer-controller.ts +++ b/src/shared/modules/offer/offer-controller.ts @@ -1,23 +1,23 @@ -import { inject } from "inversify"; -import { BaseController } from "../../libs/rest/controller/base-controller.abstract.js"; -import { Component } from "../../types/container.js"; -import { OfferServiceInterface } from "./offer-service.interface.js"; -import { HttpMethod } from "../../libs/rest/index.js"; -import { Request, Response } from "express"; -import { Logger } from "../../libs/logger/index.js"; -import { fillDTO } from "../../helpers/common.js"; -import { OfferRDO } from "./rdo/offer.rdo.js"; -import { StatusCodes } from "http-status-codes"; -import { ValidateObjectIdMiddleware } from "../../libs/rest/middleware/validate-objectid.middleware.js"; -import { City } from "../../types/offer.js"; -import { CommentServiceInterface } from "../comment/comment-service.interface.js"; -import { ValidateDTOMiddleware } from "../../libs/rest/middleware/validate-object.middleware.js"; -import { CreateOfferDto } from "./dto/offer-dto.js"; -import { UpdateOfferDto } from "./dto/update-dto.js"; -import { DocumentExistsMiddleware } from "../../libs/rest/middleware/document-exists.middleware.js"; -import { DocumentExists } from "../../libs/rest/types/document-exists.interface.js"; -import { PrivateRouteMiddleware } from "../../libs/rest/middleware/private-route.middleware.js"; -import { UserServiceInterface } from "../user/user-service.interface.js"; +import { inject } from 'inversify'; +import { BaseController } from '../../libs/rest/controller/base-controller.abstract.js'; +import { Component } from '../../types/container.js'; +import { OfferServiceInterface } from './offer-service.interface.js'; +import { HttpMethod } from '../../libs/rest/index.js'; +import { Request, Response } from 'express'; +import { Logger } from '../../libs/logger/index.js'; +import { fillDTO } from '../../helpers/common.js'; +import { OfferRDO } from './rdo/offer.rdo.js'; +import { StatusCodes } from 'http-status-codes'; +import { ValidateObjectIdMiddleware } from '../../libs/rest/middleware/validate-objectid.middleware.js'; +import { City } from '../../types/offer.js'; +import { CommentServiceInterface } from '../comment/comment-service.interface.js'; +import { ValidateDTOMiddleware } from '../../libs/rest/middleware/validate-object.middleware.js'; +import { CreateOfferDto } from './dto/offer-dto.js'; +import { UpdateOfferDto } from './dto/update-dto.js'; +import { DocumentExistsMiddleware } from '../../libs/rest/middleware/document-exists.middleware.js'; +import { DocumentExists } from '../../libs/rest/types/document-exists.interface.js'; +import { PrivateRouteMiddleware } from '../../libs/rest/middleware/private-route.middleware.js'; +import { UserServiceInterface } from '../user/user-service.interface.js'; export class OfferController extends BaseController { constructor( @@ -31,9 +31,9 @@ export class OfferController extends BaseController { ) { super(logger); - this.addRoute({ path: "/", method: HttpMethod.Get, handler: this.index }); + this.addRoute({ path: '/', method: HttpMethod.Get, handler: this.index }); this.addRoute({ - path: "/", + path: '/', method: HttpMethod.Post, handler: this.create, middlewares: [ @@ -42,33 +42,33 @@ export class OfferController extends BaseController { ], }); this.addRoute({ - path: "/:offerId", + path: '/:offerId', method: HttpMethod.Put, handler: this.update, middlewares: [ new PrivateRouteMiddleware(), - new ValidateObjectIdMiddleware("offerId"), + new ValidateObjectIdMiddleware('offerId'), new ValidateDTOMiddleware(UpdateOfferDto), - new DocumentExistsMiddleware("offerId", "offer", this.offerService), + new DocumentExistsMiddleware('offerId', 'offer', this.offerService), ], }); this.addRoute({ - path: "/:offerId", + path: '/:offerId', method: HttpMethod.Delete, handler: this.delete, middlewares: [ new PrivateRouteMiddleware(), - new ValidateObjectIdMiddleware("offerId"), - new DocumentExistsMiddleware("offerId", "offer", this.offerService), + new ValidateObjectIdMiddleware('offerId'), + new DocumentExistsMiddleware('offerId', 'offer', this.offerService), ], }); this.addRoute({ - path: "/:offerId/comments", + path: '/:offerId/comments', method: HttpMethod.Get, handler: this.getComments, middlewares: [ - new ValidateObjectIdMiddleware("offerId"), - new DocumentExistsMiddleware("offerId", "offer", this.offerService), + new ValidateObjectIdMiddleware('offerId'), + new DocumentExistsMiddleware('offerId', 'offer', this.offerService), ], }); } diff --git a/src/shared/modules/offer/offer-service.interface.ts b/src/shared/modules/offer/offer-service.interface.ts index 174eac2..6f85822 100644 --- a/src/shared/modules/offer/offer-service.interface.ts +++ b/src/shared/modules/offer/offer-service.interface.ts @@ -1,7 +1,7 @@ -import { DocumentType } from "@typegoose/typegoose"; -import { CreateOfferDto } from "./dto/offer-dto.js"; -import { OfferEntity } from "./offer.entity.js"; -import { City, UpdateOfferDto } from "./dto/update-dto.js"; +import { DocumentType } from '@typegoose/typegoose'; +import { CreateOfferDto } from './dto/offer-dto.js'; +import { OfferEntity } from './offer.entity.js'; +import { City, UpdateOfferDto } from './dto/update-dto.js'; export interface OfferServiceInterface { createOffer(dto: CreateOfferDto): Promise>; diff --git a/src/shared/modules/offer/offer-service.ts b/src/shared/modules/offer/offer-service.ts index 26cc78d..8ad7b2b 100644 --- a/src/shared/modules/offer/offer-service.ts +++ b/src/shared/modules/offer/offer-service.ts @@ -1,14 +1,14 @@ -import { inject, injectable } from "inversify"; -import { CreateOfferDto } from "./dto/offer-dto.js"; -import { OfferServiceInterface } from "./offer-service.interface.js"; -import { OfferEntity } from "./offer.entity.js"; -import { Component } from "../../types/container.js"; -import { Logger } from "../../libs/logger/index.js"; -import { City, UpdateOfferDto } from "./dto/update-dto.js"; -import { DocumentType, types } from "@typegoose/typegoose"; -import { CommentEntity } from "../comment/comment.entity.js"; -import { Types } from "mongoose"; -import { DocumentExists } from "../../libs/rest/types/document-exists.interface.js"; +import { inject, injectable } from 'inversify'; +import { CreateOfferDto } from './dto/offer-dto.js'; +import { OfferServiceInterface } from './offer-service.interface.js'; +import { OfferEntity } from './offer.entity.js'; +import { Component } from '../../types/container.js'; +import { Logger } from '../../libs/logger/index.js'; +import { City, UpdateOfferDto } from './dto/update-dto.js'; +import { DocumentType, types } from '@typegoose/typegoose'; +import { CommentEntity } from '../comment/comment.entity.js'; +import { Types } from 'mongoose'; +import { DocumentExists } from '../../libs/rest/types/document-exists.interface.js'; @injectable() export class OfferService implements OfferServiceInterface, DocumentExists { @@ -27,7 +27,7 @@ export class OfferService implements OfferServiceInterface, DocumentExists { } async findOfferById(id: string): Promise | null> { - return this.offerModel.findById(id).populate("host").exec(); + return this.offerModel.findById(id).populate('host').exec(); } async exists(documentId: string): Promise { @@ -51,7 +51,7 @@ export class OfferService implements OfferServiceInterface, DocumentExists { async recalcRating(offerId: string): Promise { const stats = await this.commentModel.aggregate([ { $match: { offerId: new Types.ObjectId(offerId) } }, - { $group: { _id: "$offerId", avgRating: { $avg: "$rating" } } }, + { $group: { _id: '$offerId', avgRating: { $avg: '$rating' } } }, ]); const avg = stats[0]?.avgRating ?? 0; @@ -64,7 +64,7 @@ export class OfferService implements OfferServiceInterface, DocumentExists { } async find(): Promise[]> { - return this.offerModel.find().populate("host").exec(); + return this.offerModel.find().populate('host').exec(); } async findPremiumOffersByCity( @@ -72,7 +72,7 @@ export class OfferService implements OfferServiceInterface, DocumentExists { ): Promise[]> { return this.offerModel .find({ city, isPremium: true }) - .populate("host") + .populate('host') .exec(); } diff --git a/src/shared/modules/user/rdo/user.rdo.ts b/src/shared/modules/user/rdo/user.rdo.ts index 3f2011f..95f044d 100644 --- a/src/shared/modules/user/rdo/user.rdo.ts +++ b/src/shared/modules/user/rdo/user.rdo.ts @@ -1,25 +1,26 @@ -import { Expose } from "class-transformer"; +import { Expose } from 'class-transformer'; export class UserRDO { @Expose() - name: string; + name: string; @Expose() - email: string; + email: string; @Expose() - avatar: string; + avatar: string; @Expose() - isPro: boolean; + isPro: boolean; @Expose() - favorites: string[]; + favorites: string[]; } export class LoggedUserRDO { @Expose() public email: string; + @Expose() public token: string; } diff --git a/src/shared/modules/user/user-controller.ts b/src/shared/modules/user/user-controller.ts index af3f779..6d4ec94 100644 --- a/src/shared/modules/user/user-controller.ts +++ b/src/shared/modules/user/user-controller.ts @@ -1,18 +1,18 @@ -import { inject } from "inversify"; -import { BaseController } from "../../libs/rest/controller/base-controller.abstract.js"; -import { Component } from "../../types/container.js"; -import { HttpMethod } from "../../libs/rest/index.js"; -import { Request, Response } from "express"; -import { Logger } from "../../libs/logger/index.js"; -import { UserServiceInterface } from "./user-service.interface.js"; -import { StatusCodes } from "http-status-codes"; -import { Config, RestSchema } from "../../libs/config/index.js"; -import { LoggedUserRDO, UserRDO } from "./rdo/user.rdo.js"; -import { fillDTO } from "../../helpers/common.js"; -import { HttpError } from "../../libs/rest/errors/http-error.js"; -import { ValidateDTOMiddleware } from "../../libs/rest/middleware/validate-object.middleware.js"; -import { CreateUserDto, LoginUserDTO } from "./dto/user-dto.js"; -import { AuthServiceInterface, UserNotFoundException } from "../auth/index.js"; +import { inject } from 'inversify'; +import { BaseController } from '../../libs/rest/controller/base-controller.abstract.js'; +import { Component } from '../../types/container.js'; +import { HttpMethod } from '../../libs/rest/index.js'; +import { Request, Response } from 'express'; +import { Logger } from '../../libs/logger/index.js'; +import { UserServiceInterface } from './user-service.interface.js'; +import { StatusCodes } from 'http-status-codes'; +import { Config, RestSchema } from '../../libs/config/index.js'; +import { LoggedUserRDO, UserRDO } from './rdo/user.rdo.js'; +import { fillDTO } from '../../helpers/common.js'; +import { HttpError } from '../../libs/rest/errors/http-error.js'; +import { ValidateDTOMiddleware } from '../../libs/rest/middleware/validate-object.middleware.js'; +import { CreateUserDto, LoginUserDTO } from './dto/user-dto.js'; +import { AuthServiceInterface, UserNotFoundException } from '../auth/index.js'; export class UserController extends BaseController { constructor( @@ -25,27 +25,27 @@ export class UserController extends BaseController { ) { super(logger); this.addRoute({ - path: "/register", + path: '/register', method: HttpMethod.Post, handler: this.create, middlewares: [new ValidateDTOMiddleware(CreateUserDto)], }); this.addRoute({ - path: "/login", + path: '/login', method: HttpMethod.Post, handler: this.auth, middlewares: [new ValidateDTOMiddleware(LoginUserDTO)], }); this.addRoute({ - path: "/:userId/avatar", + path: '/:userId/avatar', method: HttpMethod.Post, handler: this.uploadAvatar, }); this.addRoute({ - path: "/login", + path: '/login', method: HttpMethod.Get, handler: this.checkAuthenticate, }); @@ -63,7 +63,7 @@ export class UserController extends BaseController { const result = await this.userService.register( req.body, - this.config.get("SALT"), + this.config.get('SALT'), ); this.created(res, fillDTO(UserRDO, result)); } @@ -94,8 +94,8 @@ export class UserController extends BaseController { if (!user) { throw new HttpError( StatusCodes.UNAUTHORIZED, - "Unauthorized", - "UserController", + 'Unauthorized', + 'UserController', ); } this.ok(res, fillDTO(LoggedUserRDO, user)); diff --git a/src/shared/modules/user/user-service.interface.ts b/src/shared/modules/user/user-service.interface.ts index 273ba98..9cb64c4 100644 --- a/src/shared/modules/user/user-service.interface.ts +++ b/src/shared/modules/user/user-service.interface.ts @@ -1,7 +1,7 @@ -import { DocumentType } from "@typegoose/typegoose"; -import { CreateUserDto } from "./dto/user-dto.js"; -import { UserEntity } from "./user.entity.js"; -import { OfferEntity } from "../offer/offer.entity.js"; +import { DocumentType } from '@typegoose/typegoose'; +import { CreateUserDto } from './dto/user-dto.js'; +import { UserEntity } from './user.entity.js'; +import { OfferEntity } from '../offer/offer.entity.js'; export interface UserServiceInterface { register(dto: CreateUserDto, salt: string): Promise>; diff --git a/src/shared/modules/user/user-service.ts b/src/shared/modules/user/user-service.ts index f208746..9412d7f 100644 --- a/src/shared/modules/user/user-service.ts +++ b/src/shared/modules/user/user-service.ts @@ -1,11 +1,11 @@ -import { DocumentType } from "@typegoose/typegoose"; -import { CreateUserDto } from "./dto/user-dto.js"; -import { UserServiceInterface } from "./user-service.interface.js"; -import { UserEntity, UserModel } from "./user.entity.js"; -import { inject, injectable } from "inversify"; -import { Component } from "../../types/container.js"; -import { Logger } from "../../libs/logger/index.js"; -import { OfferEntity } from "../offer/offer.entity.js"; +import { DocumentType } from '@typegoose/typegoose'; +import { CreateUserDto } from './dto/user-dto.js'; +import { UserServiceInterface } from './user-service.interface.js'; +import { UserEntity, UserModel } from './user.entity.js'; +import { inject, injectable } from 'inversify'; +import { Component } from '../../types/container.js'; +import { Logger } from '../../libs/logger/index.js'; +import { OfferEntity } from '../offer/offer.entity.js'; @injectable() export class UserService implements UserServiceInterface { @@ -38,13 +38,13 @@ export class UserService implements UserServiceInterface { async findFavoriteOffers( userId: number, ): Promise[]> { - const user = await UserModel.findById(userId).populate("favorites").exec(); + const user = await UserModel.findById(userId).populate('favorites').exec(); return (user?.favorites as DocumentType[]) ?? []; } async toggleFavoriteOffer(offerId: string, userId: number): Promise { - const user = await UserModel.findById(userId).populate("favorites").exec(); + const user = await UserModel.findById(userId).populate('favorites').exec(); if (!user) { return; } diff --git a/src/shared/modules/user/user.entity.ts b/src/shared/modules/user/user.entity.ts index 1785392..925341c 100644 --- a/src/shared/modules/user/user.entity.ts +++ b/src/shared/modules/user/user.entity.ts @@ -1,6 +1,6 @@ -import { createSha256 } from "../../helpers/hash.js"; -import { User } from "../../types/user.interface.js"; -import type { OfferEntity } from "../offer/offer.entity.js"; +import { createSha256 } from '../../helpers/hash.js'; +import { User } from '../../types/user.interface.js'; +import type { OfferEntity } from '../offer/offer.entity.js'; import { defaultClasses, @@ -8,36 +8,36 @@ import { modelOptions, prop, Ref, -} from "@typegoose/typegoose"; +} from '@typegoose/typegoose'; // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging export interface UserEntity extends defaultClasses.Base {} @modelOptions({ schemaOptions: { - collection: "users", + collection: 'users', timestamps: true, }, }) // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging export class UserEntity extends defaultClasses.TimeStamps implements User { - @prop({ required: true, minlength: 1, maxlength: 15, default: "" }) + @prop({ required: true, minlength: 1, maxlength: 15, default: '' }) public name!: string; - @prop({ required: true, default: "", unique: true }) + @prop({ required: true, default: '', unique: true }) public email: string; - @prop({ required: false, default: "" }) + @prop({ required: false, default: '' }) public avatar: string; - @prop({ required: true, minlength: 6, maxlength: 1024, default: "" }) + @prop({ required: true, minlength: 6, maxlength: 1024, default: '' }) public password: string; @prop({ required: true }) public isPro: boolean; - @prop({ ref: "OfferEntity", default: [] }) + @prop({ ref: 'OfferEntity', default: [] }) public favorites: Ref[]; constructor(userData: User) { diff --git a/src/shared/types/container.ts b/src/shared/types/container.ts index e2c0e9c..7125145 100644 --- a/src/shared/types/container.ts +++ b/src/shared/types/container.ts @@ -1,18 +1,18 @@ export const Component = { - RestApplication: Symbol.for("RestApplication"), - Logger: Symbol.for("Logger"), - Config: Symbol.for("Config"), - DatabaseClient: Symbol.for("DatabaseClient"), - UserService: Symbol.for("UserService"), - UserModel: Symbol.for("UserModel"), - OfferService: Symbol.for("OfferService"), - OfferModel: Symbol.for("OfferModel"), - CommentService: Symbol.for("CommentService"), - CommentModel: Symbol.for("CommentModel"), - OfferController: Symbol.for("OfferController"), - UserController: Symbol.for("UserController"), - ExceptionFilter: Symbol.for("ExceptionFilter"), - CommentController: Symbol.for("CommentController"), - AuthService: Symbol.for("AuthService"), - AuthExceptionFilter: Symbol.for("AuthExceptionFilter"), + RestApplication: Symbol.for('RestApplication'), + Logger: Symbol.for('Logger'), + Config: Symbol.for('Config'), + DatabaseClient: Symbol.for('DatabaseClient'), + UserService: Symbol.for('UserService'), + UserModel: Symbol.for('UserModel'), + OfferService: Symbol.for('OfferService'), + OfferModel: Symbol.for('OfferModel'), + CommentService: Symbol.for('CommentService'), + CommentModel: Symbol.for('CommentModel'), + OfferController: Symbol.for('OfferController'), + UserController: Symbol.for('UserController'), + ExceptionFilter: Symbol.for('ExceptionFilter'), + CommentController: Symbol.for('CommentController'), + AuthService: Symbol.for('AuthService'), + AuthExceptionFilter: Symbol.for('AuthExceptionFilter'), } as const; From 55fa3a1805a791d3feb8e1ca075bfaf2fc82ee54 Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 11 May 2026 13:10:31 +0300 Subject: [PATCH 3/3] =?UTF-8?q?=D0=92=D0=BD=D0=B5=D1=81=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../rest/middleware/parse-token.middleware.ts | 39 +++++++++++-------- src/shared/modules/user/user-controller.ts | 2 + 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/shared/libs/rest/middleware/parse-token.middleware.ts b/src/shared/libs/rest/middleware/parse-token.middleware.ts index 294efde..e71f643 100644 --- a/src/shared/libs/rest/middleware/parse-token.middleware.ts +++ b/src/shared/libs/rest/middleware/parse-token.middleware.ts @@ -29,28 +29,33 @@ export class ParseTokenMiddleware implements Middleware { ): Promise { const authorizationHeader = req.headers?.authorization?.split(' '); - if (!authorizationHeader) { + if ( + !authorizationHeader || + authorizationHeader?.[0] !== 'Bearer' || + !authorizationHeader?.[1] + ) { return next(); } const [, token] = authorizationHeader; - - const { payload } = await jwtVerify( - token, - crypto.createSecretKey(this.jwtSecret, 'utf-8'), - ); - - if (isTokenPayload(payload)) { - req.tokenPayload = { ...payload }; + try { + const { payload } = await jwtVerify( + token, + crypto.createSecretKey(this.jwtSecret, 'utf-8'), + ); + + if (isTokenPayload(payload)) { + req.tokenPayload = { ...payload }; + } return next(); + } catch (err) { + return next( + new HttpError( + StatusCodes.UNAUTHORIZED, + 'Invalid token', + 'AuthenticateMiddleware', + ), + ); } - - return next( - new HttpError( - StatusCodes.UNAUTHORIZED, - 'Invalid token', - 'AuthenticateMiddleware', - ), - ); } } diff --git a/src/shared/modules/user/user-controller.ts b/src/shared/modules/user/user-controller.ts index 6d4ec94..6979ca3 100644 --- a/src/shared/modules/user/user-controller.ts +++ b/src/shared/modules/user/user-controller.ts @@ -13,6 +13,7 @@ import { HttpError } from '../../libs/rest/errors/http-error.js'; import { ValidateDTOMiddleware } from '../../libs/rest/middleware/validate-object.middleware.js'; import { CreateUserDto, LoginUserDTO } from './dto/user-dto.js'; import { AuthServiceInterface, UserNotFoundException } from '../auth/index.js'; +import { PrivateRouteMiddleware } from '../../libs/rest/middleware/private-route.middleware.js'; export class UserController extends BaseController { constructor( @@ -48,6 +49,7 @@ export class UserController extends BaseController { path: '/login', method: HttpMethod.Get, handler: this.checkAuthenticate, + middlewares: [new PrivateRouteMiddleware()], }); }