From ea265e9059175592891386dbf192f1d766df8e93 Mon Sep 17 00:00:00 2001 From: Sergii Mykyteiek Date: Tue, 5 Oct 2021 13:21:24 +0300 Subject: [PATCH] LT-3: Implement ttl add, get endpoints --- package-lock.json | 27 ++++++++++++++ package.json | 1 + src/main.ts | 2 + src/modules/stack/stack.service.ts | 2 +- .../validation/stack.validation-service.ts | 2 +- src/modules/ttl/ttl.controller.ts | 37 +++++++++++++++++++ src/modules/ttl/ttl.dto.ts | 13 +++++++ src/modules/ttl/ttl.interfaces.ts | 5 +++ src/modules/ttl/ttl.repository.ts | 28 ++++++++++++++ src/modules/ttl/ttl.router.ts | 18 +++++++++ src/modules/ttl/ttl.service.ts | 16 ++++++++ src/modules/ttl/validation/index.ts | 1 + .../ttl/validation/ttl.validation-service.ts | 16 ++++++++ 13 files changed, 166 insertions(+), 2 deletions(-) create mode 100644 src/modules/ttl/ttl.controller.ts create mode 100644 src/modules/ttl/ttl.dto.ts create mode 100644 src/modules/ttl/ttl.interfaces.ts create mode 100644 src/modules/ttl/ttl.repository.ts create mode 100644 src/modules/ttl/ttl.router.ts create mode 100644 src/modules/ttl/ttl.service.ts create mode 100644 src/modules/ttl/validation/index.ts create mode 100644 src/modules/ttl/validation/ttl.validation-service.ts diff --git a/package-lock.json b/package-lock.json index 6013a29..4413fc1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "dotenv": "^10.0.0", "express": "^4.17.1", "morgan": "^1.10.0", + "node-ttl": "^0.2.0", "reflect-metadata": "^0.1.13", "typedi": "^0.10.0" }, @@ -268,6 +269,11 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, + "node_modules/async": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.1.tgz", + "integrity": "sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg==" + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1333,6 +1339,14 @@ "node": ">= 0.6" } }, + "node_modules/node-ttl": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/node-ttl/-/node-ttl-0.2.0.tgz", + "integrity": "sha512-bZVpmzWt4IuA3DWTZJEElLMQkYPmjUBi3q4XdBZ1dy1QLsDWLU7GMNhr/loLDFhqwlbQftQsb4xrPyCkv8UUyQ==", + "dependencies": { + "async": "*" + } + }, "node_modules/nodemon": { "version": "2.0.13", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.13.tgz", @@ -2326,6 +2340,11 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, + "async": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.1.tgz", + "integrity": "sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg==" + }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -3141,6 +3160,14 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" }, + "node-ttl": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/node-ttl/-/node-ttl-0.2.0.tgz", + "integrity": "sha512-bZVpmzWt4IuA3DWTZJEElLMQkYPmjUBi3q4XdBZ1dy1QLsDWLU7GMNhr/loLDFhqwlbQftQsb4xrPyCkv8UUyQ==", + "requires": { + "async": "*" + } + }, "nodemon": { "version": "2.0.13", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.13.tgz", diff --git a/package.json b/package.json index b701f6a..e68f09a 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "dotenv": "^10.0.0", "express": "^4.17.1", "morgan": "^1.10.0", + "node-ttl": "^0.2.0", "reflect-metadata": "^0.1.13", "typedi": "^0.10.0" }, diff --git a/src/main.ts b/src/main.ts index eaaa383..30b1891 100644 --- a/src/main.ts +++ b/src/main.ts @@ -5,6 +5,7 @@ const cors = require("cors"); const morgan = require("morgan"); import { getConfig } from './config'; import { stackRouter } from './modules/stack/stack.router'; +import { ttlRouter } from './modules/ttl/ttl.router'; const app = express(); @@ -15,6 +16,7 @@ async function start() { app.use(morgan('combined')); app.use('/stack', stackRouter); + app.use('/ttl', ttlRouter); app.listen(config.PORT, () => { console.log(`Application started on port ${config.PORT}!`); diff --git a/src/modules/stack/stack.service.ts b/src/modules/stack/stack.service.ts index bb9be4b..f0bba6d 100644 --- a/src/modules/stack/stack.service.ts +++ b/src/modules/stack/stack.service.ts @@ -1,6 +1,6 @@ +import { Service } from 'typedi'; import { StackDto, StackResponse } from './stack.dto'; import { StackRepository } from './stack.repository'; -import { Service } from 'typedi'; @Service() export class StackService { diff --git a/src/modules/stack/validation/stack.validation-service.ts b/src/modules/stack/validation/stack.validation-service.ts index b8d4b40..a841594 100644 --- a/src/modules/stack/validation/stack.validation-service.ts +++ b/src/modules/stack/validation/stack.validation-service.ts @@ -8,7 +8,7 @@ import { StackDto } from '../stack.dto'; export class StackValidationService implements IValidateBody { async validateBody(body: StackDto) { const item = plainToClass(StackDto, body); - const errors = await validate(item); + const errors = await validate(item, { forbidNonWhitelisted: true, whitelist: true }); return errors; } diff --git a/src/modules/ttl/ttl.controller.ts b/src/modules/ttl/ttl.controller.ts new file mode 100644 index 0000000..300ae4f --- /dev/null +++ b/src/modules/ttl/ttl.controller.ts @@ -0,0 +1,37 @@ +import { Request, Response } from 'express'; +import { Service } from 'typedi'; +import { TtlService } from './ttl.service'; +import { TtlResponse } from './ttl.dto'; +import { TtlValidationService } from './validation'; + +@Service() +export class TtlController { + constructor( + private readonly ttlService: TtlService, + private readonly validationService: TtlValidationService, + ) {} + + 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 data = this.ttlService.get(req.body.key); + if (data) { + return res.status(409).json({ message: 'Key already exist' }); + } + + const result = this.ttlService.add(req.body); + + return res.status(201).json(result); + } + + get(req: Request, res: Response): Response { + const result = this.ttlService.get(req.params.key); + if (!result) { + return res.status(404).json({ message: 'Not found key' }) + } + + return res.status(200).json(result); + } +} diff --git a/src/modules/ttl/ttl.dto.ts b/src/modules/ttl/ttl.dto.ts new file mode 100644 index 0000000..938cda7 --- /dev/null +++ b/src/modules/ttl/ttl.dto.ts @@ -0,0 +1,13 @@ +import { IsString, IsNotEmpty } from 'class-validator'; + +export class TtlDto { + @IsNotEmpty() + @IsString() + key: string; + + @IsNotEmpty() + value: any; +} + + +export class TtlResponse extends TtlDto {} diff --git a/src/modules/ttl/ttl.interfaces.ts b/src/modules/ttl/ttl.interfaces.ts new file mode 100644 index 0000000..38a7063 --- /dev/null +++ b/src/modules/ttl/ttl.interfaces.ts @@ -0,0 +1,5 @@ +import { TtlDto } from './ttl.dto'; + +export interface IValidateBody { + validateBody(body: TtlDto); +} \ No newline at end of file diff --git a/src/modules/ttl/ttl.repository.ts b/src/modules/ttl/ttl.repository.ts new file mode 100644 index 0000000..6463611 --- /dev/null +++ b/src/modules/ttl/ttl.repository.ts @@ -0,0 +1,28 @@ +const NodeTtl = require('node-ttl'); +import { Service } from 'typedi'; +import { TtlDto, TtlResponse } from './ttl.dto'; +import { getConfig } from '../../config'; + +@Service() +export class TtlRepository { + private readonly store; + private readonly config; + constructor() { + this.store = new NodeTtl(); + this.config = getConfig(); + } + + add(dto: TtlDto): TtlResponse { + this.store.push(dto.key, dto.value, null, this.config.TTL_EXPIRATION); + + return dto; + } + + get(key: string): TtlResponse { + const value = this.store.get(key); + if (!value) { + return value; + } + return { key, value } + } +} diff --git a/src/modules/ttl/ttl.router.ts b/src/modules/ttl/ttl.router.ts new file mode 100644 index 0000000..935a0ee --- /dev/null +++ b/src/modules/ttl/ttl.router.ts @@ -0,0 +1,18 @@ +import { Router } from 'express'; +import { Container } from 'typedi'; +import { TtlController } from './ttl.controller'; + +const ttlRouter = Router(); +const ttlController = Container.get(TtlController); + +ttlRouter.get( + '/:key', + ttlController.get.bind(ttlController), +); + +ttlRouter.post( + '/', + ttlController.add.bind(ttlController), +); + +export { ttlRouter } \ No newline at end of file diff --git a/src/modules/ttl/ttl.service.ts b/src/modules/ttl/ttl.service.ts new file mode 100644 index 0000000..292fdc0 --- /dev/null +++ b/src/modules/ttl/ttl.service.ts @@ -0,0 +1,16 @@ +import { Service } from 'typedi'; +import { TtlRepository } from './ttl.repository'; +import { TtlDto, TtlResponse } from './ttl.dto'; + +@Service() +export class TtlService { + constructor(private readonly ttlRepository: TtlRepository) {} + + add(dto: TtlDto): TtlResponse { + return this.ttlRepository.add(dto); + } + + get(key: string): TtlResponse { + return this.ttlRepository.get(key); + } +} \ No newline at end of file diff --git a/src/modules/ttl/validation/index.ts b/src/modules/ttl/validation/index.ts new file mode 100644 index 0000000..7d7e552 --- /dev/null +++ b/src/modules/ttl/validation/index.ts @@ -0,0 +1 @@ +export * from './ttl.validation-service'; diff --git a/src/modules/ttl/validation/ttl.validation-service.ts b/src/modules/ttl/validation/ttl.validation-service.ts new file mode 100644 index 0000000..3a5a9d3 --- /dev/null +++ b/src/modules/ttl/validation/ttl.validation-service.ts @@ -0,0 +1,16 @@ +import { validate } from 'class-validator'; +import { plainToClass } from 'class-transformer'; +import { Service } from 'typedi'; +import { IValidateBody } from '../ttl.interfaces'; +import { TtlDto } from '../ttl.dto'; + +@Service() +export class TtlValidationService implements IValidateBody { + async validateBody(body: TtlDto) { + const item = plainToClass(TtlDto, body); + const errors = await validate(item, { forbidNonWhitelisted: true, whitelist: true }); + + return errors; + } + +} \ No newline at end of file -- GitLab