Skip to content

Commit

Permalink
Merge pull request #13 from Karlen-ll/module8-task1
Browse files Browse the repository at this point in the history
  • Loading branch information
keksobot authored Nov 6, 2024
2 parents 5f79426 + 0b3f309 commit e1a7b19
Show file tree
Hide file tree
Showing 39 changed files with 353 additions and 74 deletions.
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@ DB_NAME=six-cities

# UPLOAD
UPLOAD_DIRECTORY=/home/node/app/upload

# Auth
JWT_SECRET=secret
14 changes: 14 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"got": "14.4.2",
"http-status-codes": "2.3.0",
"inversify": "6.0.2",
"jose": "5.9.6",
"mime-types": "2.1.35",
"mongodb": "6.9.0",
"mongoose": "8.7.1",
Expand Down
4 changes: 3 additions & 1 deletion src/main.rest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'reflect-metadata';
import { Container } from 'inversify';

import { createRestApplicationContainer } from './rest/rest.container.js';
import { createAuthContainer } from './shared/modules/auth/index.js';
import { createUserContainer } from './shared/modules/user/index.js';
import { createOfferContainer } from './shared/modules/offer/index.js';
import { createCommentContainer } from './shared/modules/comment/index.js';
Expand All @@ -15,7 +16,8 @@ async function bootstrap() {
createUserContainer(),
createOfferContainer(),
createCommentContainer(),
createCategoryContainer()
createCategoryContainer(),
createAuthContainer()
);

const application = container.get<RestApplication>(Component.RestApplication);
Expand Down
9 changes: 7 additions & 2 deletions src/rest/rest.application.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { inject, injectable } from 'inversify';
import express, { Express } from 'express';

import { Controller, ExceptionFilter } from '../shared/libs/rest/index.js';
import { Controller, ExceptionFilter, ParseTokenMiddleware } from '../shared/libs/rest/index.js';
import { DatabaseClient } from '../shared/libs/database-client/index.js';
import { Config, RestSchema } from '../shared/libs/config/index.js';
import { Logger } from '../shared/libs/logger/index.js';
Expand All @@ -18,12 +18,14 @@ export class RestApplication {
@inject(Component.CommentController)
@inject(Component.CategoryController)
@inject(Component.ExceptionFilter)
@inject(Component.AuthExceptionFilter)
private readonly databaseClient: DatabaseClient,
private readonly userController: Controller,
private readonly offerController: Controller,
private readonly commentController: Controller,
private readonly categoryController: Controller,
private readonly appExceptionFilter: ExceptionFilter
private readonly appExceptionFilter: ExceptionFilter,
private readonly authExceptionFilter: ExceptionFilter
) {
this.server = express();
}
Expand All @@ -50,14 +52,17 @@ export class RestApplication {

private initMiddleware() {
this.logger.info('Инициализация middleware-ов');
const authenticateMiddleware = new ParseTokenMiddleware(this.config.get('JWT_SECRET'));
this.server.use(express.json());
this.server.use('/upload', express.static(this.config.get('UPLOAD_DIRECTORY')));
this.server.use(authenticateMiddleware.execute.bind(authenticateMiddleware));
this.logger.info('Инициализация middleware-ов завершена');
}

private initExceptionFilters() {
this.logger.info('Инициализация фильтров исключений');
this.server.use(this.appExceptionFilter.catch.bind(this.appExceptionFilter));
this.server.use(this.authExceptionFilter.catch.bind(this.authExceptionFilter));
this.logger.info('Инициализация фильтров исключений завершена');
}

Expand Down
7 changes: 7 additions & 0 deletions src/shared/libs/config/rest.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export type RestSchema = {
DB_PORT: string;
DB_NAME: string;
UPLOAD_DIRECTORY: string;
JWT_SECRET: string;
};

export const configRestSchema = convict<RestSchema>({
Expand Down Expand Up @@ -63,4 +64,10 @@ export const configRestSchema = convict<RestSchema>({
env: 'UPLOAD_DIRECTORY',
default: null,
},
JWT_SECRET: {
doc: 'Secret for sign JWT',
format: String,
env: 'JWT_SECRET',
default: null,
},
});
16 changes: 8 additions & 8 deletions src/shared/libs/rest/controller/base-controller.abstract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,19 +38,19 @@ export abstract class BaseController implements Controller {
this.logger.info(`Маршрут зарегистрирован: ${route.method.toUpperCase()} ${route.path}`);
}

public send<T>(res: Response, statusCode: number, data: T) {
res.type(this.DEFAULT_CONTENT_TYPE).status(statusCode).json(data);
public send<T>(response: Response, statusCode: number, data: T) {
response.type(this.DEFAULT_CONTENT_TYPE).status(statusCode).json(data);
}

public created<T>(res: Response, data: T): void {
this.send(res, StatusCodes.CREATED, data);
public created<T>(response: Response, data: T): void {
this.send(response, StatusCodes.CREATED, data);
}

public noContent<T>(res: Response, data: T): void {
this.send(res, StatusCodes.NO_CONTENT, data);
public noContent<T>(response: Response, data: T): void {
this.send(response, StatusCodes.NO_CONTENT, data);
}

public ok<T>(res: Response, data: T): void {
this.send(res, StatusCodes.OK, data);
public ok<T>(response: Response, data: T): void {
this.send(response, StatusCodes.OK, data);
}
}
8 changes: 4 additions & 4 deletions src/shared/libs/rest/controller/controller.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { Route } from '../types/index.js';
export interface Controller {
readonly router: Router;
addRoute(route: Route): void;
send<T>(res: Response, statusCode: number, data: T): void;
ok<T>(res: Response, data: T): void;
created<T>(res: Response, data: T): void;
noContent<T>(res: Response, data: T): void;
send<T>(response: Response, statusCode: number, data: T): void;
ok<T>(response: Response, data: T): void;
created<T>(response: Response, data: T): void;
noContent<T>(response: Response, data: T): void;
}
31 changes: 23 additions & 8 deletions src/shared/libs/rest/exception-filter/app-exception-filter.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Request, Response, NextFunction } from 'express';
import { NextFunction, Request, Response } from 'express';
import { inject, injectable } from 'inversify';

import { ExceptionFilter } from './exception-filter.interface.js';
Expand All @@ -13,22 +13,37 @@ export class AppExceptionFilter implements ExceptionFilter {
this.logger.info('Register AppExceptionFilter');
}

private handleHttpError(error: HttpError, _req: Request, res: Response, _next: NextFunction) {
private handleHttpError(
error: HttpError,
_request: Request,
response: Response,
_next: NextFunction
) {
this.logger.error(`[${error.detail}]: ${error.httpStatusCode}${error.message}`, error);

res.status(error.httpStatusCode).json(new Error(error.message));
response.status(error.httpStatusCode).json(new Error(error.message));
}

private handleOtherError(error: Error, _req: Request, res: Response, _next: NextFunction) {
private handleOtherError(
error: Error,
_request: Request,
response: Response,
_next: NextFunction
) {
this.logger.error(error);
res.status(StatusCodes.INTERNAL_SERVER_ERROR).json(new Error(error.message));
response.status(StatusCodes.INTERNAL_SERVER_ERROR).json(new Error(error.message));
}

public catch(error: Error | HttpError, req: Request, res: Response, next: NextFunction): void {
public catch(
error: Error | HttpError,
request: Request,
response: Response,
next: NextFunction
): void {
if (error instanceof HttpError) {
return this.handleHttpError(error, req, res, next);
return this.handleHttpError(error, request, response, next);
}

this.handleOtherError(error, req, res, next);
this.handleOtherError(error, request, response, next);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { NextFunction, Request, Response } from 'express';

export interface ExceptionFilter {
catch(error: Error, req: Request, res: Response, next: NextFunction): void;
catch(error: Error, request: Request, response: Response, next: NextFunction): void;
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ export class DocumentExistsMiddleware implements Middleware {
private readonly paramName: string
) {}

public async execute({ params }: Request, _res: Response, next: NextFunction): Promise<void> {
public async execute(
{ params }: Request,
_response: Response,
next: NextFunction
): Promise<void> {
const documentId = params[this.paramName];
const exists = await this.service.exists(documentId);

Expand Down
2 changes: 2 additions & 0 deletions src/shared/libs/rest/middleware/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ export * from './validate-dto.middleware.js';
export * from './validate-objectid.middleware.js';
export * from './document-exists.middleware.js';
export * from './upload-file.middleware.js';
export * from './parse-token.middleware.js';
export * from './private-route.middleware.js';
2 changes: 1 addition & 1 deletion src/shared/libs/rest/middleware/middleware.interface.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { NextFunction, Request, Response } from 'express';

export interface Middleware {
execute(req: Request, res: Response, next: NextFunction): void;
execute(request: Request, response: Response, next: NextFunction): void;
}
50 changes: 50 additions & 0 deletions src/shared/libs/rest/middleware/parse-token.middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { NextFunction, Request, Response } from 'express';
import { jwtVerify } from 'jose';
import { StatusCodes } from 'http-status-codes';
import { createSecretKey } from 'node:crypto';
import { ENCODING } from '../../../../constants/index.js';

import { Middleware } from './middleware.interface.js';
import { HttpError } from '../errors/index.js';
import { TokenPayload } from '../../../modules/auth/index.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) {}

public async execute(request: Request, _response: Response, next: NextFunction): Promise<void> {
const authorizationHeader = request.headers?.authorization?.split(' ');
if (!authorizationHeader) {
return next();
}

const [, token] = authorizationHeader;

try {
const { payload } = await jwtVerify(token, createSecretKey(this.jwtSecret, ENCODING));

if (isTokenPayload(payload)) {
request.tokenPayload = { ...payload };
return next();
} else {
throw new Error('Не корректный токен');
}
} catch {
return next(
new HttpError(StatusCodes.UNAUTHORIZED, 'Неверный токен', 'AuthenticateMiddleware')
);
}
}
}
19 changes: 19 additions & 0 deletions src/shared/libs/rest/middleware/private-route.middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { StatusCodes } from 'http-status-codes';
import { NextFunction, Request, Response } from 'express';

import { Middleware } from './middleware.interface.js';
import { HttpError } from '../errors/index.js';

export class PrivateRouteMiddleware implements Middleware {
public async execute(
{ tokenPayload }: Request,
_response: Response,
next: NextFunction
): Promise<void> {
if (!tokenPayload) {
throw new HttpError(StatusCodes.UNAUTHORIZED, 'Unauthorized', 'PrivateRouteMiddleware');
}

return next();
}
}
6 changes: 3 additions & 3 deletions src/shared/libs/rest/middleware/upload-file.middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ export class UploadFileMiddleware implements Middleware {
private fieldName: string
) {}

public async execute(req: Request, res: Response, next: NextFunction): Promise<void> {
public async execute(request: Request, response: Response, next: NextFunction): Promise<void> {
const storage = diskStorage({
destination: this.uploadDirectory,
filename: (_req, file, callback) => {
filename: (_request, file, callback) => {
const fileExtension = extension(file.mimetype);
const filename = randomUUID();
callback(null, `${filename}.${fileExtension}`);
Expand All @@ -23,6 +23,6 @@ export class UploadFileMiddleware implements Middleware {

const uploadSingleFileMiddleware = multer({ storage }).single(this.fieldName);

uploadSingleFileMiddleware(req, res, next);
uploadSingleFileMiddleware(request, response, next);
}
}
4 changes: 2 additions & 2 deletions src/shared/libs/rest/middleware/validate-dto.middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import { Middleware } from './middleware.interface.js';
export class ValidateDtoMiddleware implements Middleware {
constructor(private dto: ClassConstructor<object>) {}

public async execute({ body }: Request, res: Response, next: NextFunction): Promise<void> {
public async execute({ body }: Request, response: Response, next: NextFunction): Promise<void> {
const dtoInstance = plainToInstance(this.dto, body);
const errors = await validate(dtoInstance);

if (errors.length > 0) {
res.status(StatusCodes.BAD_REQUEST).send(errors);
response.status(StatusCodes.BAD_REQUEST).send(errors);
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { HttpError } from '../errors/index.js';
export class ValidateObjectIdMiddleware implements Middleware {
constructor(private param: string) {}

public execute({ params }: Request, _res: Response, next: NextFunction): void {
public execute({ params }: Request, _response: Response, next: NextFunction): void {
const objectId = params[this.param];

if (Types.ObjectId.isValid(objectId)) {
Expand Down
6 changes: 6 additions & 0 deletions src/shared/modules/auth/auth-service.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { LoginUserDto, UserEntity } from '../user/index.js';

export interface AuthService {
authenticate(user: UserEntity): Promise<string>;
verify(dto: LoginUserDto): Promise<UserEntity>;
}
18 changes: 18 additions & 0 deletions src/shared/modules/auth/auth.container.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Container } from 'inversify';

import { AuthService } from './auth-service.interface.js';
import { Component } from '../../types/index.js';
import { DefaultAuthService } from './default-auth.service.js';
import { ExceptionFilter } from '../../libs/rest/index.js';
import { AuthExceptionFilter } from './auth.exception-filter.js';

export function createAuthContainer() {
const authContainer = new Container();
authContainer.bind<AuthService>(Component.AuthService).to(DefaultAuthService).inSingletonScope();
authContainer
.bind<ExceptionFilter>(Component.AuthExceptionFilter)
.to(AuthExceptionFilter)
.inSingletonScope();

return authContainer;
}
Loading

0 comments on commit e1a7b19

Please sign in to comment.