From 34154b78567f097d371e87e4ea0d82f74dcbdc66 Mon Sep 17 00:00:00 2001 From: Eyad Hazem Date: Thu, 16 May 2024 17:07:12 +0300 Subject: [PATCH 01/21] Shortener model added This commit installs mongoose package, creates a folder for models and adds shortener model with its schema. --- models/shortener.js | 17 ++++ package-lock.json | 215 ++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 3 files changed, 233 insertions(+) create mode 100644 models/shortener.js diff --git a/models/shortener.js b/models/shortener.js new file mode 100644 index 0000000..ea8d409 --- /dev/null +++ b/models/shortener.js @@ -0,0 +1,17 @@ +const mongoose = require('mongoose'); +const Schema = mongoose.Schema; + +const shortenerSchema = new Schema({ + originalUrl: { + type: String, + required: true + }, + shortenedUrl: { + type: String, + required: true + } +}, { timestamps: true }); + +const Shortener = mongoose.model('Shortener', shortenerSchema); + +module.exports = Shortener; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 6fd9de3..7f113f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,12 +13,34 @@ "ejs": "^3.1.9", "express": "^4.18.2", "http-errors": "~1.6.3", + "mongoose": "^8.3.5", "morgan": "~1.9.1" }, "devDependencies": { "nodemon": "^3.0.1" } }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.7.tgz", + "integrity": "sha512-dCHW/oEX0KJ4NjDULBo3JiOaK5+6axtpBbS+ao2ZInoAL9/YRQLhXzSNAFz7hP4nzLkIqsfYAK/PDE3+XHny0Q==", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==" + }, + "node_modules/@types/whatwg-url": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.4.tgz", + "integrity": "sha512-lXCmTWSHJvf0TRSO58nm978b8HJ/EdsSsEKLd3ODHFjo+3VGAyyTp4v50nWvwtzBxSMQrVOK7tcuN0zGPLICMw==", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -195,6 +217,14 @@ "node": ">=8" } }, + "node_modules/bson": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.7.0.tgz", + "integrity": "sha512-w2IquM5mYzYZv6rs3uN2DZTOBe2a0zXLj53TGDqwF4l6Sz/XsISrisXOJihArF9+BZ6Cq/GjVht7Sjfmri7ytQ==", + "engines": { + "node": ">=16.20.1" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -823,6 +853,14 @@ "node": ">=10" } }, + "node_modules/kareem": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz", + "integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -843,6 +881,11 @@ "node": ">= 0.6" } }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==" + }, "node_modules/merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -897,6 +940,86 @@ "node": "*" } }, + "node_modules/mongodb": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.5.0.tgz", + "integrity": "sha512-Fozq68InT+JKABGLqctgtb8P56pRrJFkbhW0ux+x1mdHeyinor8oNzJqwLjV/t5X5nJGfTlluxfyMnOXNggIUA==", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.5", + "bson": "^6.4.0", + "mongodb-connection-string-url": "^3.0.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.1.tgz", + "integrity": "sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^13.0.0" + } + }, + "node_modules/mongoose": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.3.5.tgz", + "integrity": "sha512-2zqeAjHjCqT1o5HeUCvkE9tUHsXwemnwEZ2SKnUxsaP8p1a+UcSQSNbnSuOzUVePMwLETrsvLIRdFLjsNfCgWA==", + "dependencies": { + "bson": "^6.5.0", + "kareem": "2.6.3", + "mongodb": "6.5.0", + "mpath": "0.9.0", + "mquery": "5.0.0", + "ms": "2.1.3", + "sift": "17.1.3" + }, + "engines": { + "node": ">=16.20.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mongoose" + } + }, + "node_modules/mongoose/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, "node_modules/morgan": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.1.tgz", @@ -912,6 +1035,46 @@ "node": ">= 0.8.0" } }, + "node_modules/mpath": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", + "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mquery": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", + "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", + "dependencies": { + "debug": "4.x" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/mquery/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/mquery/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -1062,6 +1225,14 @@ "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", "dev": true }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "engines": { + "node": ">=6" + } + }, "node_modules/qs": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", @@ -1288,6 +1459,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sift": { + "version": "17.1.3", + "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz", + "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==" + }, "node_modules/simple-update-notifier": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", @@ -1300,6 +1476,14 @@ "node": ">=10" } }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, "node_modules/statuses": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", @@ -1352,6 +1536,17 @@ "nodetouch": "bin/nodetouch.js" } }, + "node_modules/tr46": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", + "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", + "dependencies": { + "punycode": "^2.3.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -1394,6 +1589,26 @@ "node": ">= 0.8" } }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz", + "integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==", + "dependencies": { + "tr46": "^4.1.1", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", diff --git a/package.json b/package.json index 4c3762d..c428a0b 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "ejs": "^3.1.9", "express": "^4.18.2", "http-errors": "~1.6.3", + "mongoose": "^8.3.5", "morgan": "~1.9.1" }, "devDependencies": { From 5ccffe1e250cc55d633e1c34b7fed8dbe115e8bb Mon Sep 17 00:00:00 2001 From: Eyad Hazem Date: Thu, 16 May 2024 17:38:49 +0300 Subject: [PATCH 02/21] Added request handler for new URLs form This commit handles POST requests from home page to create new url shortener and save it into database. --- routes/index.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/routes/index.js b/routes/index.js index 5890929..8f76fe8 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,8 +1,19 @@ const router = require('express').Router(); +const Shortener = require('../models/shortener'); /* GET home page. */ router.get('/', function (req, res, next) { res.render('index', { title: 'Express' }); }); +/* POST new url shortener from home page */ +router.post('/', async (req, res, next) => { + const shortener = new Shortener({ + originalUrl: req.body.urlInput, + shortenedUrl: req.body.aliasInput + }); + await shortener.save(); + res.redirect('/'); +}); + module.exports = router; From bb051378aad6f179ff30b65e41d9e9fdd3b3ad37 Mon Sep 17 00:00:00 2001 From: Eyad Hazem Date: Thu, 16 May 2024 18:07:38 +0300 Subject: [PATCH 03/21] Send URLs to home page This commit updates home page GET request handler to access urls from database. --- routes/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/routes/index.js b/routes/index.js index 8f76fe8..c832016 100644 --- a/routes/index.js +++ b/routes/index.js @@ -2,7 +2,9 @@ const router = require('express').Router(); const Shortener = require('../models/shortener'); /* GET home page. */ -router.get('/', function (req, res, next) { +router.get('/', async function (req, res, next) { + const urls = await Shortener.find({}); + res.locals.urls = urls; res.render('index', { title: 'Express' }); }); From a0646351debf61d78ba972e6bd7f76b9e45a3ec7 Mon Sep 17 00:00:00 2001 From: Eyad Hazem Date: Thu, 16 May 2024 18:20:50 +0300 Subject: [PATCH 04/21] Home page updated to show URLs form database This commit updates home page with ejs to get all URLs from database and show them in a table. --- views/index.ejs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/views/index.ejs b/views/index.ejs index 70b7faa..1d2858a 100644 --- a/views/index.ejs +++ b/views/index.ejs @@ -75,18 +75,12 @@ Original URL Shortened URL - - https://github.com/cat-backend-nodejs/nodejs-roadmap - http://localhost:3000/nodejs - - - https://stackoverflow.com/users/11936196/seif-el-din-sweilam - http://localhost:3000/stackoverflow - - - https://drive.google.com/drive/u/1/folders/1bpiBKNBd9n0Z79wrZvHVT1KyA9OBROLx - http://localhost:3000/himym - + <% for (url of urls) { %> + + url.originalUrl + url.shortenedUrl + + <% } %> From 78d52d1e0a870f79681e0d5f429ad989301cc1da Mon Sep 17 00:00:00 2001 From: Eyad Hazem Date: Thu, 16 May 2024 18:37:46 +0300 Subject: [PATCH 05/21] Syntax error fixed in home page This commit prints url and alias correctly with ejs. --- views/index.ejs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/views/index.ejs b/views/index.ejs index 1d2858a..0c50aab 100644 --- a/views/index.ejs +++ b/views/index.ejs @@ -77,8 +77,8 @@ <% for (url of urls) { %> - url.originalUrl - url.shortenedUrl + <%= url.originalUrl %> + <%= url.shortenedUrl %> <% } %> From e0075889e906945b0ec561f1c1f36f28e7ab14c3 Mon Sep 17 00:00:00 2001 From: Eyad Hazem Date: Thu, 16 May 2024 19:25:36 +0300 Subject: [PATCH 06/21] Added request handler for using an alias This commit handles GET requests to pages with shortened URL such that if this alias exists in database, the user will redirected to the original page and if this alias not exists, the error is handled with 404 status code and 'Not Found' message. --- routes/index.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/routes/index.js b/routes/index.js index c832016..2481b42 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,6 +1,18 @@ const router = require('express').Router(); const Shortener = require('../models/shortener'); +/* GET a page with alias. */ +router.get('/:alias', async function (req, res, next) { + const alias = req.params.alias; + const url = await Shortener.findOne({ shortenedUrl: alias }); + if (url) { + const originalUrl = url.originalUrl; + res.redirect(originalUrl); + } else { + res.status(404).send('Not Found'); + } +}); + /* GET home page. */ router.get('/', async function (req, res, next) { const urls = await Shortener.find({}); From 3916bd57b808266f3ea996e16fc64dda2e75520f Mon Sep 17 00:00:00 2001 From: Eyad Hazem Date: Thu, 16 May 2024 19:51:42 +0300 Subject: [PATCH 07/21] Connect to database This commit adds database URL and connects to it after starting the application --- app.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app.js b/app.js index 04be2f9..2b77ec1 100644 --- a/app.js +++ b/app.js @@ -3,8 +3,10 @@ const express = require('express'); const path = require('path'); const cookieParser = require('cookie-parser'); const logger = require('morgan'); +const mongoose = require('mongoose'); const indexRouter = require('./routes/index'); +const dbUrl = 'mongodb://localhost:27017/url-shortener' const app = express(); @@ -36,4 +38,13 @@ app.use(function (err, req, res, next) { res.render('error'); }); +// Connect to database +mongoose.connect(dbUrl) + .then(() => { + console.log('Connected to database'); + }) + .catch((err) => { + console.log(err.message); + }) + module.exports = app; From bd4b52ed2e3cc09c7398499dc918a153e187d7a8 Mon Sep 17 00:00:00 2001 From: Eyad Hazem Date: Thu, 16 May 2024 20:19:19 +0300 Subject: [PATCH 08/21] Error message updated --- routes/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes/index.js b/routes/index.js index 2481b42..923018f 100644 --- a/routes/index.js +++ b/routes/index.js @@ -9,7 +9,7 @@ router.get('/:alias', async function (req, res, next) { const originalUrl = url.originalUrl; res.redirect(originalUrl); } else { - res.status(404).send('Not Found'); + res.status(404).send('404 Not Found'); } }); From f194de297b599c9c4d61c0357ecc3ed52079918a Mon Sep 17 00:00:00 2001 From: Eyad Hazem Date: Thu, 16 May 2024 21:13:18 +0300 Subject: [PATCH 09/21] Domain sent to index.ejs This commit sends domain to index.ejs file. --- routes/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/routes/index.js b/routes/index.js index 923018f..fb25427 100644 --- a/routes/index.js +++ b/routes/index.js @@ -17,6 +17,7 @@ router.get('/:alias', async function (req, res, next) { router.get('/', async function (req, res, next) { const urls = await Shortener.find({}); res.locals.urls = urls; + res.locals.domain = `${req.protocol}://${req.get('host')}/`; res.render('index', { title: 'Express' }); }); From de3dcdcb5cea2a82d7cd79b59867e4c39cd98b9c Mon Sep 17 00:00:00 2001 From: Eyad Hazem Date: Thu, 16 May 2024 21:17:06 +0300 Subject: [PATCH 10/21] Home page updated This commit updated home page content and makes project dependent on domain. --- views/index.ejs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/views/index.ejs b/views/index.ejs index 0c50aab..49aed0e 100644 --- a/views/index.ejs +++ b/views/index.ejs @@ -77,8 +77,8 @@ <% for (url of urls) { %> - <%= url.originalUrl %> - <%= url.shortenedUrl %> + <%= url.originalUrl %> + <%= domain %><%= url.shortenedUrl %> <% } %> From 5708022cd79ec9cbdaf5d0b65e9dafdda5318184 Mon Sep 17 00:00:00 2001 From: Eyad Hazem Date: Thu, 16 May 2024 21:29:06 +0300 Subject: [PATCH 11/21] Script modified in package.json This commit changes "start" script to run nodemon. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c428a0b..d31a156 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.0.0", "private": true, "scripts": { - "start": "node ./bin/www" + "start": "nodemon ./bin/www" }, "dependencies": { "cookie-parser": "~1.4.4", From 1c0b20fa23746cee25ade9523c2f4da868c7b9bd Mon Sep 17 00:00:00 2001 From: Eyad Hazem Date: Fri, 17 May 2024 00:39:01 +0300 Subject: [PATCH 12/21] Validator for alias added This commmit validates the alias entered by user if it has not any special characters except '_' and has minimum length of 4 characters. --- routes/index.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/routes/index.js b/routes/index.js index fb25427..0c75174 100644 --- a/routes/index.js +++ b/routes/index.js @@ -21,8 +21,18 @@ router.get('/', async function (req, res, next) { res.render('index', { title: 'Express' }); }); +// A middleware to validate alias +let validator = function (req, res, next) { + const regex = /^[a-zA-Z0-9_]{4, }$/; + if (regex.test(req.body.aliasInput)) { + next(); + } else { + res.render('invalid'); + } +} + /* POST new url shortener from home page */ -router.post('/', async (req, res, next) => { +router.post('/', validator, async (req, res, next) => { const shortener = new Shortener({ originalUrl: req.body.urlInput, shortenedUrl: req.body.aliasInput From 5d7b496e02f2fe8bef7374a267c44c75f73c36cc Mon Sep 17 00:00:00 2001 From: Eyad Hazem Date: Fri, 17 May 2024 00:55:11 +0300 Subject: [PATCH 13/21] Invalid page added --- views/invalid.ejs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 views/invalid.ejs diff --git a/views/invalid.ejs b/views/invalid.ejs new file mode 100644 index 0000000..6daff81 --- /dev/null +++ b/views/invalid.ejs @@ -0,0 +1,20 @@ + + + + + + + Invalid + + + +

This alias is invalid

+

Your alias must match these conditions:

+
    +
  • Doesn't have special characters except '_' without quotes
  • +
  • Has length of 4 characters or more
  • +
+ Return to home page + + + \ No newline at end of file From f606511157cad7dec408a72e5f0ad08d098319e9 Mon Sep 17 00:00:00 2001 From: Eyad Hazem Date: Fri, 17 May 2024 01:04:53 +0300 Subject: [PATCH 14/21] Validator fixed This commit fixes validator regex. --- routes/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/routes/index.js b/routes/index.js index 0c75174..4fee4ec 100644 --- a/routes/index.js +++ b/routes/index.js @@ -23,11 +23,11 @@ router.get('/', async function (req, res, next) { // A middleware to validate alias let validator = function (req, res, next) { - const regex = /^[a-zA-Z0-9_]{4, }$/; - if (regex.test(req.body.aliasInput)) { + const regex = /^[a-zA-Z0-9_]+$/; + if (req.body.aliasInput.length > 3 && regex.test(req.body.aliasInput)) { next(); } else { - res.render('invalid'); + res.status(400).render('invalid'); } } From 0c8bc5fd305fa7af3dd69c74c8e71d6a9fcc5751 Mon Sep 17 00:00:00 2001 From: Eyad Hazem Date: Fri, 17 May 2024 01:11:43 +0300 Subject: [PATCH 15/21] Not available alias page added --- views/not-available.ejs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 views/not-available.ejs diff --git a/views/not-available.ejs b/views/not-available.ejs new file mode 100644 index 0000000..54bdce3 --- /dev/null +++ b/views/not-available.ejs @@ -0,0 +1,15 @@ + + + + + + + Invalid + + + +

This alias is already exists, try another one

+ Return to home page + + + \ No newline at end of file From 9e45b1eb895f843a7da1baec65d7e2f58c353610 Mon Sep 17 00:00:00 2001 From: Eyad Hazem Date: Fri, 17 May 2024 01:21:43 +0300 Subject: [PATCH 16/21] Check availability added This commit updates not-avaliable.ejs message and checks if the entered alias is available or not. --- routes/index.js | 11 ++++++++++- views/not-available.ejs | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/routes/index.js b/routes/index.js index 4fee4ec..1b6c1c0 100644 --- a/routes/index.js +++ b/routes/index.js @@ -31,8 +31,17 @@ let validator = function (req, res, next) { } } +// A middleware to check availability +let available = async function (req, res, next) { + if (await Shortener.findOne({ shortenedUrl: req.body.aliasInput })) { + res.status(400).render('not-available'); + } else { + next(); + } +} + /* POST new url shortener from home page */ -router.post('/', validator, async (req, res, next) => { +router.post('/', validator, available, async (req, res, next) => { const shortener = new Shortener({ originalUrl: req.body.urlInput, shortenedUrl: req.body.aliasInput diff --git a/views/not-available.ejs b/views/not-available.ejs index 54bdce3..548f951 100644 --- a/views/not-available.ejs +++ b/views/not-available.ejs @@ -8,7 +8,7 @@ -

This alias is already exists, try another one

+

This alias is already taken, try another one

Return to home page From a03d6d6d25892aa65a5690dd25d18c2c18c4cb52 Mon Sep 17 00:00:00 2001 From: Eyad Hazem Date: Fri, 17 May 2024 01:38:20 +0300 Subject: [PATCH 17/21] Delete button added --- views/index.ejs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/views/index.ejs b/views/index.ejs index 49aed0e..16de7e2 100644 --- a/views/index.ejs +++ b/views/index.ejs @@ -74,11 +74,13 @@ Original URL Shortened URL + Action <% for (url of urls) { %> <%= url.originalUrl %> <%= domain %><%= url.shortenedUrl %> + <% } %> From e9e68104fc7f27f798f6e699095b46b704ce627e Mon Sep 17 00:00:00 2001 From: Eyad Hazem Date: Fri, 17 May 2024 01:58:52 +0300 Subject: [PATCH 18/21] Delete button modified --- views/index.ejs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/views/index.ejs b/views/index.ejs index 16de7e2..c878ccd 100644 --- a/views/index.ejs +++ b/views/index.ejs @@ -80,7 +80,9 @@ <%= url.originalUrl %> <%= domain %><%= url.shortenedUrl %> - +
+ + <% } %> From 2a8e8bd98cc78531eb40eaf4f0e151d583fda1e7 Mon Sep 17 00:00:00 2001 From: Eyad Hazem Date: Fri, 17 May 2024 02:04:35 +0300 Subject: [PATCH 19/21] Delete url request handled This commit deletes url when button delete is pressed. --- routes/index.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/routes/index.js b/routes/index.js index 1b6c1c0..b970f11 100644 --- a/routes/index.js +++ b/routes/index.js @@ -50,4 +50,9 @@ router.post('/', validator, available, async (req, res, next) => { res.redirect('/'); }); +router.delete('/:Id', async (req, res, next) => { + await Shortener.findByIdAndDelete(req.params.Id); + res.redirect('/'); +}); + module.exports = router; From 241a2bb108dd2cac0e86a75d64b4efc1f66b11f6 Mon Sep 17 00:00:00 2001 From: Eyad Hazem Date: Fri, 17 May 2024 02:26:04 +0300 Subject: [PATCH 20/21] Delete url feature fixed This commit fixes deletion of url from app and database. --- routes/index.js | 3 ++- views/index.ejs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/routes/index.js b/routes/index.js index b970f11..b60b948 100644 --- a/routes/index.js +++ b/routes/index.js @@ -50,7 +50,8 @@ router.post('/', validator, available, async (req, res, next) => { res.redirect('/'); }); -router.delete('/:Id', async (req, res, next) => { +router.get('/:Id/delete', async (req, res, next) => { + console.log(req.params.Id); await Shortener.findByIdAndDelete(req.params.Id); res.redirect('/'); }); diff --git a/views/index.ejs b/views/index.ejs index c878ccd..7406581 100644 --- a/views/index.ejs +++ b/views/index.ejs @@ -80,7 +80,7 @@ <%= url.originalUrl %> <%= domain %><%= url.shortenedUrl %> -
+
From 8749a20f70120fab18d27079eef5ed8973090146 Mon Sep 17 00:00:00 2001 From: Eyad Hazem Date: Sun, 19 May 2024 03:30:03 +0300 Subject: [PATCH 21/21] README file updated This commit updates README.md file with a suitable documentation about project. --- README.md | 80 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 50 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 595dc02..8472994 100644 --- a/README.md +++ b/README.md @@ -1,46 +1,66 @@ -# URL Shortener App -This is starter code for URL Shortener Project. +# URL Shortener -Fork this repository and use the given files to start with. +A URL shortener application that allows users to convert long URLs into shorter, more manageable links. -## Idea +## Features -A URL shortener website is a service that converts long website addresses into shorter, more manageable links. Users input a lengthy URL, and the website generates a condensed version, making it easier to share and remember. +- **URL Shortening**: Convert long URLs into short, easy-to-share links. +- **Custom Aliases**: Optionally create custom aliases for shortened URLs. +- **Redirects**: Redirect short URLs to their original long URLs. +- **URL History**: Display a list of previously shortened URLs. +- **Delete URLs**: Delete existing shortened URLs. +- **Alias Validation**: Prevent the use of an alias if it already exists. +- **Alias Restrictions**: Restrict special characters in aliases, allowing only alphanumeric characters and underscores (`_`). -## Interface +## Installation -The application interface consists of one page which contains: +### Prerequisites -* A form to shorten the URL, which takes two inputs: - - the long version of the url - - the alias of the url (defaults to a random string) -* A table which contains the previously shortened URLs. +- Node.js +- MongoDB -## Short URLs +### Steps -The short URLs are written in this form: +1. Clone the repository: + ```bash + git clone https://github.com/eyad-hazem-elmorsy/url-shortener.git + ``` +2. Navigate to the project directory: + ```bash + cd url-shortener + ``` +3. Install the dependencies: + ```bash + npm install + ``` +4. Start the application: + ```bash + npm start + ``` -``` -http://localhost:3000/{alias} -``` +## Usage -## Application Logic +1. Open your browser and go to `http://localhost:3000`. +2. Enter the long URL you wish to shorten. +3. Provide a custom alias. +4. Click "Shorten" to generate the short URL. +5. Use the short URL to redirect to the original long URL. +6. Manage your URLs, including deleting them and checking for alias availability. -* When a client tries to access the short URL, they should be redirected to the original long URL. -* If the client accesses a URL which doesn't exist, a `404` error should be displayed. -* There's no required authentication or authorization to generate short URLs. +## Project Structure -## Project Criteria +- `bin/`: Application entry point. +- `models/`: URL schema definition. +- `routes/`: Route definitions. +- `views/`: EJS templates for rendering. +- `app.js`: Main application file. +- `package.json`: Project metadata and dependencies. -- [ ] The application runs locally without any crashes -- [ ] The application logic is implemented correctly -- [ ] The application uses server-side rendering -- [ ] The application uses a MongoDB database +## Contributing -## Project Evaluation (50 pts.) +Contributions are welcome! Please fork the repository and submit a pull request. -* Project Completeness (25 pts.) -* Clean Code and Modulation (15 pts.) -* Descriptive Git Commit Messages (10 pts.) -* Nice touches (5 pts. bonus) \ No newline at end of file +--- + +Developed by [Eyad Hazem](https://github.com/eyad-hazem-elmorsy/).