#3 added util func to validate url on post request

parent 7b39228d
...@@ -2,3 +2,4 @@ DB_CONN_STRING="mongodb://localhost:27017" ...@@ -2,3 +2,4 @@ DB_CONN_STRING="mongodb://localhost:27017"
DB_NAME="linksDB" DB_NAME="linksDB"
LINKS_COLLECTION_NAME="links" LINKS_COLLECTION_NAME="links"
PORT='3000' PORT='3000'
BASE_URL='http://localhost:3000'
\ No newline at end of file
export const validateUrl = (url: string): boolean => {
const urlPattern: RegExp = new RegExp(
'^(https?:\\/\\/)?' +
'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' +
'((\\d{1,3}\\.){3}\\d{1,3}))' +
'(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' +
'(\\?[;&a-z\\d%_.~+=-]*)?' +
'(\\#[-a-z\\d_]*)?$',
'i'
);
return !!urlPattern.test(url);
};
...@@ -13,11 +13,13 @@ ...@@ -13,11 +13,13 @@
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^16.0.3", "dotenv": "^16.0.3",
"express": "^4.18.2", "express": "^4.18.2",
"mongodb": "^5.1.0" "mongodb": "^5.1.0",
"shortid": "^2.2.16"
}, },
"devDependencies": { "devDependencies": {
"@types/cors": "^2.8.13", "@types/cors": "^2.8.13",
"@types/express": "^4.17.17", "@types/express": "^4.17.17",
"@types/shortid": "^0.0.29",
"ts-node-dev": "^2.0.0" "ts-node-dev": "^2.0.0"
} }
}, },
...@@ -166,6 +168,12 @@ ...@@ -166,6 +168,12 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"node_modules/@types/shortid": {
"version": "0.0.29",
"resolved": "https://registry.npmjs.org/@types/shortid/-/shortid-0.0.29.tgz",
"integrity": "sha512-9BCYD9btg2CY4kPcpMQ+vCR8U6V8f/KvixYD5ZbxoWlkhddNF5IeZMVL3p+QFUkg+Hb+kPAG9Jgk4bnnF1v/Fw==",
"dev": true
},
"node_modules/@types/strip-bom": { "node_modules/@types/strip-bom": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz", "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz",
...@@ -931,6 +939,11 @@ ...@@ -931,6 +939,11 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
}, },
"node_modules/nanoid": {
"version": "2.1.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-2.1.11.tgz",
"integrity": "sha512-s/snB+WGm6uwi0WjsZdaVcuf3KJXlfGl2LcxgwkEwJF0D/BWzVWAZW/XY4bFaiR7s0Jk3FPvlnepg1H1b1UwlA=="
},
"node_modules/negotiator": { "node_modules/negotiator": {
"version": "0.6.3", "version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
...@@ -1204,6 +1217,14 @@ ...@@ -1204,6 +1217,14 @@
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
}, },
"node_modules/shortid": {
"version": "2.2.16",
"resolved": "https://registry.npmjs.org/shortid/-/shortid-2.2.16.tgz",
"integrity": "sha512-Ugt+GIZqvGXCIItnsL+lvFJOiN7RYqlGy7QE41O3YC1xbNSeDGIRO7xg2JJXIAj1cAGnOeC1r7/T9pgrtQbv4g==",
"dependencies": {
"nanoid": "^2.1.0"
}
},
"node_modules/side-channel": { "node_modules/side-channel": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
...@@ -1675,6 +1696,12 @@ ...@@ -1675,6 +1696,12 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"@types/shortid": {
"version": "0.0.29",
"resolved": "https://registry.npmjs.org/@types/shortid/-/shortid-0.0.29.tgz",
"integrity": "sha512-9BCYD9btg2CY4kPcpMQ+vCR8U6V8f/KvixYD5ZbxoWlkhddNF5IeZMVL3p+QFUkg+Hb+kPAG9Jgk4bnnF1v/Fw==",
"dev": true
},
"@types/strip-bom": { "@types/strip-bom": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz", "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz",
...@@ -2246,6 +2273,11 @@ ...@@ -2246,6 +2273,11 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
}, },
"nanoid": {
"version": "2.1.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-2.1.11.tgz",
"integrity": "sha512-s/snB+WGm6uwi0WjsZdaVcuf3KJXlfGl2LcxgwkEwJF0D/BWzVWAZW/XY4bFaiR7s0Jk3FPvlnepg1H1b1UwlA=="
},
"negotiator": { "negotiator": {
"version": "0.6.3", "version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
...@@ -2441,6 +2473,14 @@ ...@@ -2441,6 +2473,14 @@
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
}, },
"shortid": {
"version": "2.2.16",
"resolved": "https://registry.npmjs.org/shortid/-/shortid-2.2.16.tgz",
"integrity": "sha512-Ugt+GIZqvGXCIItnsL+lvFJOiN7RYqlGy7QE41O3YC1xbNSeDGIRO7xg2JJXIAj1cAGnOeC1r7/T9pgrtQbv4g==",
"requires": {
"nanoid": "^2.1.0"
}
},
"side-channel": { "side-channel": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
......
...@@ -14,11 +14,13 @@ ...@@ -14,11 +14,13 @@
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^16.0.3", "dotenv": "^16.0.3",
"express": "^4.18.2", "express": "^4.18.2",
"mongodb": "^5.1.0" "mongodb": "^5.1.0",
"shortid": "^2.2.16"
}, },
"devDependencies": { "devDependencies": {
"@types/cors": "^2.8.13", "@types/cors": "^2.8.13",
"@types/express": "^4.17.17", "@types/express": "^4.17.17",
"@types/shortid": "^0.0.29",
"ts-node-dev": "^2.0.0" "ts-node-dev": "^2.0.0"
} }
} }
import {ObjectId} from 'mongodb'; import {ObjectId} from 'mongodb';
export default class Link { export default class Link {
constructor(public originalUrl: string, public id?: ObjectId) {} constructor(
public originalUrl: string,
public shortUrl: string,
public id?: ObjectId
) {}
} }
...@@ -2,12 +2,15 @@ import express, {Router, Request, Response} from 'express'; ...@@ -2,12 +2,15 @@ import express, {Router, Request, Response} from 'express';
import {ObjectId} from 'mongodb'; import {ObjectId} from 'mongodb';
import {collections} from '../services/database.service'; import {collections} from '../services/database.service';
import Link from '../models/links'; import Link from '../models/links';
import shortid from 'shortid';
import {validateUrl} from '../../Util/util';
export const linksRouter: Router = express.Router(); export const linksRouter: Router = express.Router();
linksRouter.get('/', (req: Request, res: Response) => { linksRouter.get('/', async (req: Request, res: Response) => {
try { try {
res.send('Hello'); const links = await collections.links?.find({}).toArray();
res.status(200).send(links);
} catch (error: unknown) { } catch (error: unknown) {
const err = error as Error; const err = error as Error;
res.status(500).send(err.message); res.status(500).send(err.message);
...@@ -17,14 +20,37 @@ linksRouter.get('/', (req: Request, res: Response) => { ...@@ -17,14 +20,37 @@ linksRouter.get('/', (req: Request, res: Response) => {
linksRouter.post('/', async (req: Request, res: Response) => { linksRouter.post('/', async (req: Request, res: Response) => {
try { try {
const newLink = req.body as Link; const newLink = req.body as Link;
const result = await collections.links?.insertOne(newLink);
if (result) { if (!validateUrl(newLink.originalUrl)) {
res res.status(400).send('Invalid url link');
.status(201) return;
.send(`Successfully created a new link with id ${result.insertedId}`); }
} else {
res.status(500).send('Failed to create a new link.'); const originalLinkExists = await collections.links?.findOne({
originalUrl: newLink.originalUrl,
});
if (originalLinkExists) {
res.status(200).send(originalLinkExists);
return;
}
let shortUrl;
while (true) {
const urlId = shortid.generate();
shortUrl = `${process.env.BASE_URL}/${urlId}`;
const shortUlrExists = await collections.links?.findOne({
shortUrl: shortUrl,
});
if (!shortUlrExists) {
break;
}
} }
newLink.shortUrl = shortUrl;
await collections.links?.insertOne(newLink);
res.status(200).send(newLink);
} catch (err: unknown) { } catch (err: unknown) {
const error = err as Error; const error = err as Error;
res.status(500).send(error.message); res.status(500).send(error.message);
......
...@@ -5,6 +5,7 @@ declare global { ...@@ -5,6 +5,7 @@ declare global {
DB_CONN_STRING: string; DB_CONN_STRING: string;
DB_NAME: string; DB_NAME: string;
LINKS_COLLECTION_NAME: string; LINKS_COLLECTION_NAME: string;
BASE_URL: string;
} }
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment