From 2f7b4d21c9ac9984a572fdf2215c3f5ec8613377 Mon Sep 17 00:00:00 2001 From: Sergii Mykyteiek Date: Tue, 5 Oct 2021 09:53:59 +0300 Subject: [PATCH] LT-2: Implement stack get, add endpoints --- package-lock.json | 89 ++++++++++++++++++- package.json | 6 +- src/main.ts | 10 +-- src/modules/stack/stack.controller.ts | 33 +++++++ src/modules/stack/stack.dto.ts | 8 ++ src/modules/stack/stack.interfaces.ts | 11 +++ src/modules/stack/stack.model.ts | 24 +++++ src/modules/stack/stack.repository.ts | 24 +++++ src/modules/stack/stack.router.ts | 18 ++++ src/modules/stack/stack.service.ts | 20 +++++ src/modules/stack/validation/index.ts | 1 + .../validation/stack.validation-service.ts | 16 ++++ 12 files changed, 253 insertions(+), 7 deletions(-) create mode 100644 src/modules/stack/stack.controller.ts create mode 100644 src/modules/stack/stack.dto.ts create mode 100644 src/modules/stack/stack.interfaces.ts create mode 100644 src/modules/stack/stack.model.ts create mode 100644 src/modules/stack/stack.repository.ts create mode 100644 src/modules/stack/stack.router.ts create mode 100644 src/modules/stack/stack.service.ts create mode 100644 src/modules/stack/validation/index.ts create mode 100644 src/modules/stack/validation/stack.validation-service.ts diff --git a/package-lock.json b/package-lock.json index f36c8ee..6013a29 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,10 +8,14 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "class-transformer": "^0.4.0", + "class-validator": "^0.13.1", "cors": "^2.8.5", "dotenv": "^10.0.0", "express": "^4.17.1", - "morgan": "^1.10.0" + "morgan": "^1.10.0", + "reflect-metadata": "^0.1.13", + "typedi": "^0.10.0" }, "devDependencies": { "@types/express": "^4.17.13", @@ -163,6 +167,11 @@ "@types/node": "*" } }, + "node_modules/@types/validator": { + "version": "13.6.3", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.6.3.tgz", + "integrity": "sha512-fWG42pMJOL4jKsDDZZREnXLjc3UE0R8LOJfARWYg6U966rxDT7TYejYzLnUF5cvSObGg34nd0+H2wHHU5Omdfw==" + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -475,6 +484,21 @@ "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", "dev": true }, + "node_modules/class-transformer": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.4.0.tgz", + "integrity": "sha512-ETWD/H2TbWbKEi7m9N4Km5+cw1hNcqJSxlSYhsLsNjQzWWiZIYA1zafxpK9PwVfaZ6AqR5rrjPVUBGESm5tQUA==" + }, + "node_modules/class-validator": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.13.1.tgz", + "integrity": "sha512-zWIeYFhUitvAHBwNhDdCRK09hWx+P0HUwFE8US8/CxFpMVzkUK8RJl7yOIE+BVu2lxyPNgeOaFv78tLE47jBIg==", + "dependencies": { + "@types/validator": "^13.1.3", + "libphonenumber-js": "^1.9.7", + "validator": "^13.5.2" + } + }, "node_modules/cli-boxes": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", @@ -1139,6 +1163,11 @@ "node": ">=8" } }, + "node_modules/libphonenumber-js": { + "version": "1.9.35", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.9.35.tgz", + "integrity": "sha512-6ok1JD4GcU7owpbp07WJZlxoGdlY538OCgN9fmOlWwCzqPNLzra7tvaFz7NJP8Hcmp1lkm97wNZ2hICN9uGylg==" + }, "node_modules/lowercase-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", @@ -1587,6 +1616,11 @@ "node": ">=8.10.0" } }, + "node_modules/reflect-metadata": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" + }, "node_modules/registry-auth-token": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz", @@ -1886,6 +1920,11 @@ "is-typedarray": "^1.0.0" } }, + "node_modules/typedi": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/typedi/-/typedi-0.10.0.tgz", + "integrity": "sha512-v3UJF8xm68BBj6AF4oQML3ikrfK2c9EmZUyLOfShpJuItAqVBHWP/KtpGinkSsIiP6EZyyb6Z3NXyW9dgS9X1w==" + }, "node_modules/typescript": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.3.tgz", @@ -1991,6 +2030,14 @@ "node": ">= 0.4.0" } }, + "node_modules/validator": { + "version": "13.6.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.6.0.tgz", + "integrity": "sha512-gVgKbdbHgtxpRyR8K0O6oFZPhhB5tT1jeEHZR0Znr9Svg03U0+r9DXWMrnRAB+HtCStDQKlaIZm42tVsVjqtjg==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -2202,6 +2249,11 @@ "@types/node": "*" } }, + "@types/validator": { + "version": "13.6.3", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.6.3.tgz", + "integrity": "sha512-fWG42pMJOL4jKsDDZZREnXLjc3UE0R8LOJfARWYg6U966rxDT7TYejYzLnUF5cvSObGg34nd0+H2wHHU5Omdfw==" + }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -2438,6 +2490,21 @@ "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", "dev": true }, + "class-transformer": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.4.0.tgz", + "integrity": "sha512-ETWD/H2TbWbKEi7m9N4Km5+cw1hNcqJSxlSYhsLsNjQzWWiZIYA1zafxpK9PwVfaZ6AqR5rrjPVUBGESm5tQUA==" + }, + "class-validator": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.13.1.tgz", + "integrity": "sha512-zWIeYFhUitvAHBwNhDdCRK09hWx+P0HUwFE8US8/CxFpMVzkUK8RJl7yOIE+BVu2lxyPNgeOaFv78tLE47jBIg==", + "requires": { + "@types/validator": "^13.1.3", + "libphonenumber-js": "^1.9.7", + "validator": "^13.5.2" + } + }, "cli-boxes": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", @@ -2948,6 +3015,11 @@ "package-json": "^6.3.0" } }, + "libphonenumber-js": { + "version": "1.9.35", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.9.35.tgz", + "integrity": "sha512-6ok1JD4GcU7owpbp07WJZlxoGdlY538OCgN9fmOlWwCzqPNLzra7tvaFz7NJP8Hcmp1lkm97wNZ2hICN9uGylg==" + }, "lowercase-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", @@ -3284,6 +3356,11 @@ "picomatch": "^2.2.1" } }, + "reflect-metadata": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" + }, "registry-auth-token": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz", @@ -3506,6 +3583,11 @@ "is-typedarray": "^1.0.0" } }, + "typedi": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/typedi/-/typedi-0.10.0.tgz", + "integrity": "sha512-v3UJF8xm68BBj6AF4oQML3ikrfK2c9EmZUyLOfShpJuItAqVBHWP/KtpGinkSsIiP6EZyyb6Z3NXyW9dgS9X1w==" + }, "typescript": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.3.tgz", @@ -3582,6 +3664,11 @@ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" }, + "validator": { + "version": "13.6.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.6.0.tgz", + "integrity": "sha512-gVgKbdbHgtxpRyR8K0O6oFZPhhB5tT1jeEHZR0Znr9Svg03U0+r9DXWMrnRAB+HtCStDQKlaIZm42tVsVjqtjg==" + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/package.json b/package.json index 2b7564a..b701f6a 100644 --- a/package.json +++ b/package.json @@ -4,10 +4,14 @@ "description": "", "main": "main.js", "dependencies": { + "class-transformer": "^0.4.0", + "class-validator": "^0.13.1", "cors": "^2.8.5", "dotenv": "^10.0.0", "express": "^4.17.1", - "morgan": "^1.10.0" + "morgan": "^1.10.0", + "reflect-metadata": "^0.1.13", + "typedi": "^0.10.0" }, "scripts": { "start": "ts-node ./src/main.ts", diff --git a/src/main.ts b/src/main.ts index b992263..eaaa383 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,8 +1,10 @@ +import 'reflect-metadata'; require('dotenv').config(); -import express, { Request, Response } from 'express'; +import express from 'express'; const cors = require("cors"); const morgan = require("morgan"); import { getConfig } from './config'; +import { stackRouter } from './modules/stack/stack.router'; const app = express(); @@ -10,11 +12,9 @@ async function start() { const config = getConfig(); app.use(express.json()); app.use(cors()) - app.use(morgan('combained')); + app.use(morgan('combined')); - app.get('/', (req: Request, res: Response) => { - res.send('Application works!'); - }); + app.use('/stack', stackRouter); app.listen(config.PORT, () => { console.log(`Application started on port ${config.PORT}!`); diff --git a/src/modules/stack/stack.controller.ts b/src/modules/stack/stack.controller.ts new file mode 100644 index 0000000..f46acbb --- /dev/null +++ b/src/modules/stack/stack.controller.ts @@ -0,0 +1,33 @@ +import { Request, Response } from 'express'; +import { Service } from 'typedi'; +import { StackService } from './stack.service'; +import { StackResponse } from './stack.dto'; +import { StackValidationService } from './validation'; + +@Service() +export class StackController { + constructor( + private readonly stackService: StackService, + private readonly validationService: StackValidationService, + ) {} + + async add(req: Request, res: Response): Promise> { + const errors = await this.validationService.validateBody(req.body); + if (errors.length > 0) { + return res.status(400).send({ errors }); + } + + const result = this.stackService.add(req.body); + + return res.status(201).json(result); + } + + async get(req: Request, res: Response): Promise> { + if (this.stackService.isEmpty()) { + return res.status(204).send(); + } + const result = this.stackService.get(); + + return res.status(200).json(result); + } +} \ No newline at end of file diff --git a/src/modules/stack/stack.dto.ts b/src/modules/stack/stack.dto.ts new file mode 100644 index 0000000..350a585 --- /dev/null +++ b/src/modules/stack/stack.dto.ts @@ -0,0 +1,8 @@ +import { IsNotEmpty } from 'class-validator'; + +export class StackDto { + @IsNotEmpty() + data: any; +} + +export class StackResponse extends StackDto {} \ No newline at end of file diff --git a/src/modules/stack/stack.interfaces.ts b/src/modules/stack/stack.interfaces.ts new file mode 100644 index 0000000..3e999e5 --- /dev/null +++ b/src/modules/stack/stack.interfaces.ts @@ -0,0 +1,11 @@ +import { StackDto } from './stack.dto'; + +export interface IStackModel { + push(item: any); + pop(); + getLength(); +} + +export interface IValidateBody { + validateBody(body: StackDto); +} \ No newline at end of file diff --git a/src/modules/stack/stack.model.ts b/src/modules/stack/stack.model.ts new file mode 100644 index 0000000..04f49fd --- /dev/null +++ b/src/modules/stack/stack.model.ts @@ -0,0 +1,24 @@ +import { IStackModel } from './stack.interfaces'; +import { Service } from 'typedi'; + +@Service() +export class StackModel implements IStackModel { + private store: any[]; + constructor() { + this.store = []; + } + + push(item: any) { + this.store.push(item); + + return item; + } + + pop() { + return this.store.pop(); + } + + getLength() { + return this.store.length; + } +} \ No newline at end of file diff --git a/src/modules/stack/stack.repository.ts b/src/modules/stack/stack.repository.ts new file mode 100644 index 0000000..59bd8fb --- /dev/null +++ b/src/modules/stack/stack.repository.ts @@ -0,0 +1,24 @@ +import { StackModel } from './stack.model'; +import { StackDto, StackResponse } from './stack.dto'; +import { Service } from 'typedi'; + +@Service() +export class StackRepository { + constructor(private readonly store: StackModel) {} + + add(dto: StackDto): StackResponse { + const data = this.store.push(dto.data); + + return { data } + } + + get(): StackResponse { + const data = this.store.pop(); + + return { data } + } + + isEmpty(): boolean { + return !this.store.getLength(); + } +} diff --git a/src/modules/stack/stack.router.ts b/src/modules/stack/stack.router.ts new file mode 100644 index 0000000..ddd7b46 --- /dev/null +++ b/src/modules/stack/stack.router.ts @@ -0,0 +1,18 @@ +import { Router } from 'express'; +import { Container } from 'typedi'; +import { StackController } from './stack.controller'; + +const stackRouter = Router(); +const stackController = Container.get(StackController); + +stackRouter.post( + '/', + stackController.add.bind(stackController), +); + +stackRouter.get( + '/', + stackController.get.bind(stackController), +); + +export { stackRouter } \ No newline at end of file diff --git a/src/modules/stack/stack.service.ts b/src/modules/stack/stack.service.ts new file mode 100644 index 0000000..bb9be4b --- /dev/null +++ b/src/modules/stack/stack.service.ts @@ -0,0 +1,20 @@ +import { StackDto, StackResponse } from './stack.dto'; +import { StackRepository } from './stack.repository'; +import { Service } from 'typedi'; + +@Service() +export class StackService { + constructor(private readonly stackRepository: StackRepository) {} + + add(dto: StackDto): StackResponse { + return this.stackRepository.add(dto) + } + + get(): StackResponse { + return this.stackRepository.get(); + } + + isEmpty(): boolean { + return this.stackRepository.isEmpty(); + } +} \ No newline at end of file diff --git a/src/modules/stack/validation/index.ts b/src/modules/stack/validation/index.ts new file mode 100644 index 0000000..e84c87d --- /dev/null +++ b/src/modules/stack/validation/index.ts @@ -0,0 +1 @@ +export * from './stack.validation-service'; diff --git a/src/modules/stack/validation/stack.validation-service.ts b/src/modules/stack/validation/stack.validation-service.ts new file mode 100644 index 0000000..b8d4b40 --- /dev/null +++ b/src/modules/stack/validation/stack.validation-service.ts @@ -0,0 +1,16 @@ +import { validate } from 'class-validator'; +import { plainToClass } from 'class-transformer'; +import { Service } from 'typedi'; +import { IValidateBody } from '../stack.interfaces'; +import { StackDto } from '../stack.dto'; + +@Service() +export class StackValidationService implements IValidateBody { + async validateBody(body: StackDto) { + const item = plainToClass(StackDto, body); + const errors = await validate(item); + + return errors; + } + +} \ No newline at end of file -- GitLab