-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Сервер наносит ответный удар #8
Changes from all commits
be22b79
3d1b7ec
b1b468e
d2796b7
2dcf58a
95bf420
c928a0f
1f0ddcb
eabbb9f
56454a1
f2795a8
d87148d
2ae7712
4408d29
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,28 @@ | ||
import {inject, injectable} from 'inversify'; | ||
import express, {Express} from 'express'; | ||
|
||
import {Logger} from '../shared/libs/logger/index.js'; | ||
import {Config, RestSchema} from '../shared/libs/config/index.js'; | ||
import {Component} from '../shared/types/index.js'; | ||
import {DatabaseClient} from '../shared/libs/database-client/index.js'; | ||
import {getMongoURI} from '../shared/helpers/index.js'; | ||
import {CommentService} from '../shared/modules/comment/index.js'; | ||
import {Controller, ExceptionFilter} from '../shared/libs/rest/index.js'; | ||
|
||
@injectable() | ||
export class RestApplication { | ||
private readonly server: Express; | ||
|
||
constructor( | ||
@inject(Component.Logger) private readonly logger: Logger, | ||
@inject(Component.Config) private readonly config: Config<RestSchema>, | ||
@inject(Component.DatabaseClient) private readonly databaseClient: DatabaseClient, | ||
@inject(Component.CommentService) private readonly commentService: CommentService, | ||
@inject(Component.CommentController) private readonly commentController: Controller, | ||
@inject(Component.ExceptionFilter) private readonly appExceptionFilter: ExceptionFilter, | ||
@inject(Component.UserController) private readonly userController: Controller, | ||
@inject(Component.OfferController) private readonly offerController: Controller, | ||
|
||
) { | ||
this.server = express(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Express по умолчанию добавляет во все ответы заголовок this.server = express();
this.server.disable('x-powered-by'); |
||
} | ||
|
||
private async _initDb() { | ||
|
@@ -29,6 +37,25 @@ export class RestApplication { | |
return this.databaseClient.connect(mongoUri); | ||
} | ||
|
||
private async _initServer() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
const port = this.config.get('PORT'); | ||
this.server.listen(port); | ||
} | ||
|
||
private async _initControllers() { | ||
this.server.use('/comments', this.commentController.router); | ||
this.server.use('/users', this.userController.router); | ||
this.server.use('/offers', this.offerController.router); | ||
} | ||
|
||
private async _initMiddleware() { | ||
this.server.use(express.json()); | ||
} | ||
|
||
private async _initExceptionFilters() { | ||
this.server.use(this.appExceptionFilter.catch.bind(this.appExceptionFilter)); | ||
} | ||
|
||
public async init() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. В стартовых логах в терминале начинает сложно ориентироваться. Можно подумать о том, чтобы сообщение-заголовок группы, например Init controllers, перекрасить в отдельный цвет |
||
this.logger.info('Application initialization'); | ||
this.logger.info(`Get value from env $PORT: ${this.config.get('PORT')}`); | ||
|
@@ -37,12 +64,20 @@ export class RestApplication { | |
await this._initDb(); | ||
this.logger.info('Init database completed'); | ||
|
||
const rrr = await this.commentService.create({ | ||
text: 'fdfdfd', | ||
rating: 3, | ||
offerId: '66e554ee0c19a0f76db1b955', | ||
authorId: '66e554ee0c19a0f76db1b953' | ||
}); | ||
console.log(rrr); | ||
this.logger.info('Init app-level middleware'); | ||
await this._initMiddleware(); | ||
this.logger.info('App-level middleware initialization completed'); | ||
|
||
this.logger.info('Init controllers'); | ||
await this._initControllers(); | ||
this.logger.info('Controller initialization completed'); | ||
|
||
this.logger.info('Init exception filters'); | ||
await this._initExceptionFilters(); | ||
this.logger.info('Exception filters initialization completed'); | ||
|
||
this.logger.info('Try to init server...'); | ||
await this._initServer(); | ||
this.logger.info(`Server started on http://localhost:${this.config.get('PORT')}`); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { injectable } from 'inversify'; | ||
import asyncHandler from 'express-async-handler'; | ||
import { StatusCodes } from 'http-status-codes'; | ||
import { Response, Router } from 'express'; | ||
import { Controller } from './controller.interface.js'; | ||
import { Logger } from '../../logger/index.js'; | ||
import { Route } from '../types/route.interface.js'; | ||
|
||
const DEFAULT_CONTENT_TYPE = 'application/json'; | ||
|
||
@injectable() | ||
export abstract class BaseController implements Controller { | ||
private readonly _router: Router; | ||
|
||
constructor( | ||
protected readonly logger: Logger | ||
) { | ||
this._router = Router(); | ||
} | ||
|
||
get router() { | ||
return this._router; | ||
} | ||
|
||
public addRoute(route: Route) { | ||
const wrapperAsyncHandler = asyncHandler(route.handler.bind(this)); | ||
this._router[route.method](route.path, wrapperAsyncHandler); | ||
this.logger.info(`Route registered: ${route.method.toUpperCase()} ${route.path}`); | ||
} | ||
|
||
public send<T>(res: Response, statusCode: number, data: T): void { | ||
res | ||
.type(DEFAULT_CONTENT_TYPE) | ||
.status(statusCode) | ||
.json(data); | ||
} | ||
|
||
public created<T>(res: Response, data: T): void { | ||
this.send(res, StatusCodes.CREATED, data); | ||
} | ||
|
||
public noContent<T>(res: Response, data: T): void { | ||
this.send(res, StatusCodes.NO_CONTENT, data); | ||
} | ||
|
||
public ok<T>(res: Response, data: T): void { | ||
this.send(res, StatusCodes.OK, data); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { Response, Router } from 'express'; | ||
import { Route } from '../types/route.interface.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; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
export class HttpError extends Error { | ||
public httpStatusCode!: number; | ||
public detail?: string; | ||
|
||
constructor(httpStatusCode: number, message: string, detail?: string) { | ||
super(message); | ||
|
||
this.httpStatusCode = httpStatusCode; | ||
this.message = message; | ||
this.detail = detail; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './http-error.js'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import {inject, injectable} from 'inversify'; | ||
import {StatusCodes} from 'http-status-codes'; | ||
import {NextFunction, Request, Response} from 'express'; | ||
import {ExceptionFilter} from './exception-filter.interface.js'; | ||
import {Component} from '../../../types/index.js'; | ||
import {Logger} from '../../logger/index.js'; | ||
import {HttpError} from '../errors/index.js'; | ||
import {createErrorObject} from '../../../helpers/index.js'; | ||
|
||
@injectable() | ||
export class AppExceptionFilter implements ExceptionFilter { | ||
constructor( | ||
@inject(Component.Logger) private readonly logger: Logger | ||
) { | ||
this.logger.info('Register AppExceptionFilter'); | ||
} | ||
|
||
private handleHttpError(error: HttpError, _req: Request, res: Response, _next: NextFunction) { | ||
this.logger.error(`[${error.detail}]: ${error.httpStatusCode} — ${error.message}`, error); | ||
res | ||
.status(error.httpStatusCode) | ||
.json(createErrorObject(error.message)); | ||
} | ||
|
||
private handleOtherError(error: Error, _req: Request, res: Response, _next: NextFunction) { | ||
this.logger.error(error.message, error); | ||
res | ||
.status(StatusCodes.INTERNAL_SERVER_ERROR) | ||
.json(createErrorObject(error.message)); | ||
} | ||
|
||
public catch(error: Error | HttpError, req: Request, res: Response, next: NextFunction): void { | ||
if (error instanceof HttpError) { | ||
return this.handleHttpError(error, req, res, next); | ||
} | ||
this.handleOtherError(error, req, res, next); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { NextFunction, Request, Response } from 'express'; | ||
|
||
export interface ExceptionFilter { | ||
catch(error: Error, req: Request, res: Response, next:NextFunction): void; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
export * from './types/http-method.enum.js'; | ||
export * from './types/route.interface.js'; | ||
export * from './controller/controller.interface.js'; | ||
export * from './controller/base-controller.abstract.js'; | ||
export * from './exception-filter/exception-filter.interface.js'; | ||
export * from './exception-filter/app-exception-filter.js'; | ||
export * from './types/request.params.type.js'; | ||
export * from './types/request-body.type.js'; | ||
export * from './errors/index.js'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
export enum HttpMethod { | ||
Get = 'get', | ||
Post = 'post', | ||
Delete = 'delete', | ||
Patch = 'patch', | ||
Put = 'put', | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export type RequestBody = Record<string, unknown>; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export type RequestParams = Record<string, unknown>; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import { NextFunction, Request, Response } from 'express'; | ||
import { HttpMethod } from './http-method.enum.js'; | ||
|
||
export interface Route { | ||
path: string; | ||
method: HttpMethod; | ||
handler: (req: Request, res: Response, next: NextFunction) => void; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import {inject, injectable} from 'inversify'; | ||
import {Request, Response} from 'express'; | ||
import {BaseController, HttpMethod} from '../../libs/rest/index.js'; | ||
import {Logger} from '../../libs/logger/index.js'; | ||
import {Component} from '../../types/index.js'; | ||
import {CommentService} from './comment-service.interface.js'; | ||
import {CommentRdo} from './rdo/comment.rdo.js'; | ||
import {fillDTO} from '../../helpers/index.js'; | ||
import {CreateCommentDto} from './dto/create-comment.dto.js'; | ||
|
||
@injectable() | ||
export class CommentController extends BaseController { | ||
constructor( | ||
@inject(Component.Logger) protected readonly logger: Logger, | ||
@inject(Component.CommentService) private readonly commentService: CommentService, | ||
) { | ||
super(logger); | ||
|
||
this.logger.info('Register routes for CommentController…'); | ||
|
||
this.addRoute({path: '/:offerId', method: HttpMethod.Get, handler: this.index}); | ||
this.addRoute({path: '/:offerId', method: HttpMethod.Post, handler: this.create}); | ||
} | ||
|
||
public async index(req: Request, res: Response): Promise<void> { | ||
const comments = await this.commentService.findByOfferId(req.params.id); | ||
const responseData = fillDTO(CommentRdo, comments); | ||
this.ok(res, responseData); | ||
} | ||
|
||
public async create( | ||
{body}: Request<Record<string, unknown>, Record<string, unknown>, CreateCommentDto>, | ||
res: Response | ||
): Promise<void> { | ||
const comment = await this.commentService.create(body); | ||
this.created(res, fillDTO(CommentRdo, comment)); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
POST http://localhost:4000/comments/6709458a0ba2080e9b726af3 HTTP/1.1 | ||
Content-Type: application/json | ||
|
||
{ | ||
"text": "test", | ||
"rating": "1", | ||
"authorId": "6709458a0ba2080e9b726af1", | ||
"offerId": "6717e369c934030b401bf05e" | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
В shared/helpers/offer.ts ts ругается из-за того, что в типизации не почистил следом. Нужно до-урегулировать отличия. В конце концов можно вроде изначально прокидывать 0, потом пусть пересчитывается