From 1cdac1255ed517cfe613d707ad44fa87803d5e47 Mon Sep 17 00:00:00 2001 From: Ashwin R Date: Sat, 15 Oct 2022 14:07:20 +0530 Subject: [PATCH] Added what-wg url validation and create shortcut endpoint --- DDL.sql | 3 ++- package-lock.json | 28 +++++++++++++++++++++++ package.json | 2 ++ src/controllers/shortcut.controller.ts | 15 ++++++++++++- src/dtos/shortcut.dto.ts | 17 ++++++++++++++ src/interfaces/shortcuts.interface.ts | 1 + src/models/shortcut.model.ts | 9 ++++---- src/models/user.model.ts | 3 ++- src/routes/shortcut.route.ts | 4 +--- src/services/shortcut.service.ts | 31 ++++++++++++++++++++++++-- 10 files changed, 101 insertions(+), 12 deletions(-) create mode 100644 src/dtos/shortcut.dto.ts diff --git a/DDL.sql b/DDL.sql index 5788eb2..8696ed3 100644 --- a/DDL.sql +++ b/DDL.sql @@ -15,9 +15,10 @@ CREATE TABLE `shortcuts` ( `short_link` varchar(60) NOT NULL , `email_id` varchar(100) NOT NULL , + `url` varchar(100) NOT NULL , `description` varchar(250) NOT NULL , `created_dttm` datetime NOT NULL , - `update_dttm` datetime NOT NULL , + `updated_dttm` datetime NOT NULL , PRIMARY KEY (`short_link`), KEY `FK_2` (`email_id`), diff --git a/package-lock.json b/package-lock.json index 77cbc9e..01134e0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "ISC", "dependencies": { "@types/bcrypt": "^5.0.0", + "@types/whatwg-url": "^11.0.0", "bcrypt": "^5.1.0", "class-validator": "^0.13.2", "compression": "^1.7.4", @@ -26,6 +27,7 @@ "sequelize": "^6.25.1", "swagger-jsdoc": "^6.2.1", "swagger-ui-express": "^4.1.6", + "whatwg-url": "^5.0.0", "winston": "^3.7.2", "winston-daily-rotate-file": "^4.6.1" }, @@ -532,6 +534,19 @@ "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.7.7.tgz", "integrity": "sha512-jiEw2kTUJ8Jsh4A1K4b5Pkjj9Xz6FktLLOQ36ZVLRkmxFbpTvAV2VRoKMojz8UlZxNg/2dZqzpigH4JYn1bkQg==" }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-xTE1E+YF4aWPJJeUzaZI5DRntlkY3+BCVJi0axFptnjGmAoWxkyREIh/XMrfxVLejwQxMCfDXdICo0VLxThrog==" + }, + "node_modules/@types/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-4F6szvZP3FM5HvJAmcInXBfrAhvM4tLIc8MO1nXwabG5TZVOLxVmAXRpICqXYd3lBlomSRGmLCopYV+yTocgpQ==", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "5.40.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.40.0.tgz", @@ -6411,6 +6426,19 @@ "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.7.7.tgz", "integrity": "sha512-jiEw2kTUJ8Jsh4A1K4b5Pkjj9Xz6FktLLOQ36ZVLRkmxFbpTvAV2VRoKMojz8UlZxNg/2dZqzpigH4JYn1bkQg==" }, + "@types/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-xTE1E+YF4aWPJJeUzaZI5DRntlkY3+BCVJi0axFptnjGmAoWxkyREIh/XMrfxVLejwQxMCfDXdICo0VLxThrog==" + }, + "@types/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-4F6szvZP3FM5HvJAmcInXBfrAhvM4tLIc8MO1nXwabG5TZVOLxVmAXRpICqXYd3lBlomSRGmLCopYV+yTocgpQ==", + "requires": { + "@types/webidl-conversions": "*" + } + }, "@typescript-eslint/eslint-plugin": { "version": "5.40.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.40.0.tgz", diff --git a/package.json b/package.json index 24daa3d..ed99115 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ }, "dependencies": { "@types/bcrypt": "^5.0.0", + "@types/whatwg-url": "^11.0.0", "bcrypt": "^5.1.0", "class-validator": "^0.13.2", "compression": "^1.7.4", @@ -61,6 +62,7 @@ "sequelize": "^6.25.1", "swagger-jsdoc": "^6.2.1", "swagger-ui-express": "^4.1.6", + "whatwg-url": "^5.0.0", "winston": "^3.7.2", "winston-daily-rotate-file": "^4.6.1" } diff --git a/src/controllers/shortcut.controller.ts b/src/controllers/shortcut.controller.ts index f00f05c..5f5922f 100644 --- a/src/controllers/shortcut.controller.ts +++ b/src/controllers/shortcut.controller.ts @@ -1,3 +1,4 @@ +import { ShortcutData } from '@/interfaces/shortcuts.interface'; import { NextFunction, Request, Response } from 'express'; import { CreatedUserData, UserData } from '../interfaces/user.interface'; import shortcutService from '../services/shortcut.service'; @@ -8,7 +9,19 @@ class ShortcutController { public getShortcutsByEmail = async (req: Request, res: Response, next: NextFunction) => { try { const email: string = req.params.email; - const findOneUserData: UserData = await this.shortcutService.findUserDataByEmail(email); + const findOneUserData: UserData = await this.shortcutService.findShortcutDataByEmail(email); + + res.status(200).json({ data: findOneUserData, message: 'findOne' }); + } catch (error) { + next(error); + } + }; + + public createShortcut = async (req: Request, res: Response, next: NextFunction) => { + try { + const email: string = req.params.email; + const shortcut: ShortcutData = req.body; + const findOneUserData: UserData = await this.shortcutService.createShortcut(email, shortcut); res.status(200).json({ data: findOneUserData, message: 'findOne' }); } catch (error) { diff --git a/src/dtos/shortcut.dto.ts b/src/dtos/shortcut.dto.ts new file mode 100644 index 0000000..0ea8a5f --- /dev/null +++ b/src/dtos/shortcut.dto.ts @@ -0,0 +1,17 @@ +import { IsString, IsNotEmpty } from 'class-validator'; + +export class CreateShortcutDto { + + @IsString() + @IsNotEmpty() + public short_link: string; + + @IsString() + @IsNotEmpty() + public description: string; + + @IsString() + @IsNotEmpty() + public url: string; + +} diff --git a/src/interfaces/shortcuts.interface.ts b/src/interfaces/shortcuts.interface.ts index 8307c21..bf12202 100644 --- a/src/interfaces/shortcuts.interface.ts +++ b/src/interfaces/shortcuts.interface.ts @@ -2,6 +2,7 @@ export interface ShortcutData { short_link: string; email_id: string; description: string; + url: string; created_dttm: string; update_dttm: string; } diff --git a/src/models/shortcut.model.ts b/src/models/shortcut.model.ts index cfba54f..b5e10b4 100644 --- a/src/models/shortcut.model.ts +++ b/src/models/shortcut.model.ts @@ -10,15 +10,16 @@ const Shortcut = (sequelize, Sequelize, DataTypes) => { email_id: { type: DataTypes.STRING, }, - description: { - type: DataTypes.STRING + url: { + type: DataTypes.STRING, }, - created_dttm: { + description: { type: DataTypes.STRING } }, { - timestamps: false + createdAt: "created_dttm", + updatedAt: "updated_dttm" } ); return Shortcut; diff --git a/src/models/user.model.ts b/src/models/user.model.ts index a1bdc77..b23b6a6 100644 --- a/src/models/user.model.ts +++ b/src/models/user.model.ts @@ -15,7 +15,8 @@ const User = (sequelize, Sequelize, DataTypes) => { } }, { - timestamps: false + createdAt: "created_dttm", + updatedAt: "updated_dttm" } ); return User; diff --git a/src/routes/shortcut.route.ts b/src/routes/shortcut.route.ts index d87d72a..07edfc4 100644 --- a/src/routes/shortcut.route.ts +++ b/src/routes/shortcut.route.ts @@ -1,11 +1,8 @@ import { Router } from 'express'; import ShortcutController from '../controllers/shortcut.controller'; import { Routes } from '../interfaces/routes.interface'; -import validationMiddleware from '../middleware/validation.middleware'; import { verifyToken } from '../middleware/jwt.middleware'; -import { CreateUserDto, LoginUserDto } from '../dtos/users.dto'; - class ShortcutRoute implements Routes { public route = '/shortcuts'; public router = Router(); @@ -17,6 +14,7 @@ class ShortcutRoute implements Routes { private initializeRoutes() { this.router.get(`${this.route}/:email`, verifyToken, this.shortcutController.getShortcutsByEmail); + this.router.post(`${this.route}/:email`, verifyToken, this.shortcutController.createShortcut); } } diff --git a/src/services/shortcut.service.ts b/src/services/shortcut.service.ts index 5461f00..39c5d72 100644 --- a/src/services/shortcut.service.ts +++ b/src/services/shortcut.service.ts @@ -1,11 +1,15 @@ import { UserShortcut } from '../interfaces/user.interface'; import { HttpException } from '../utils/exception'; +import * as R from 'ramda'; +import * as whatwg from 'whatwg-url'; import Db from '../models'; import { nullCheck } from '../utils/ramda'; +import { ShortcutData } from '@/interfaces/shortcuts.interface'; +import { logger } from '../utils/logger'; class ShortcutService { - public async findUserDataByEmail(email: string): Promise { + public async findShortcutDataByEmail(email: string): Promise { if (nullCheck(email)) throw new HttpException(400, "email is undefined"); const resultData: UserShortcut = await Db.User.findAll({ @@ -17,8 +21,31 @@ class ShortcutService { email, } }); + const userShortcuts: UserShortcut = R.head(resultData); + if(!nullCheck(userShortcuts)) { + return userShortcuts; + } + throw new HttpException(204, "Shortcuts not found"); + } - return resultData; + public async createShortcut(email: string, shortcut: ShortcutData): Promise { + if (nullCheck(email)) throw new HttpException(400, "email is undefined"); + try { + const { short_link, description, url } = shortcut; + const validateResult = whatwg.parseURL(url); + if(R.equals(validateResult, 'failure')) { + throw new HttpException(400, "invalid url, please follow the what-wg url standard") + } + const createdShortcut = await Db.Shortcut.create({ + short_link, description, url, email_id: email, + created_dttm: new Date(), + }, { fields: ['short_link', 'description', 'url', 'email_id', 'created_dttm'] }); + + return createdShortcut; + } catch(e){ + logger.error(`Something went wrong ${e}`); + throw new HttpException(500, `Something went wrong ${e}`); + } }