diff --git a/.gitignore b/.gitignore index 40b878d..3ec544c 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -node_modules/ \ No newline at end of file +node_modules/ +.env \ No newline at end of file diff --git a/app.js b/app.js index 04be2f9..f0cce75 100644 --- a/app.js +++ b/app.js @@ -3,6 +3,9 @@ const express = require('express'); const path = require('path'); const cookieParser = require('cookie-parser'); const logger = require('morgan'); +const { validateUrlInput } = require('./middlewares/urlMiddleware'); + + const indexRouter = require('./routes/index'); @@ -12,13 +15,23 @@ const app = express(); app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'ejs'); + app.use(logger('dev')); app.use(express.json()); app.use(express.urlencoded({ extended: false })); app.use(cookieParser()); app.use(express.static(path.join(__dirname, 'public'))); +require('dotenv').config() +const mongoose = require('mongoose') +const connectDB = require('./config/dbConfig') + app.use('/', indexRouter); +//app.use('/shorten', indexRouter); + + +// Connect to MongoDB +connectDB(); // catch 404 and forward to error handler app.use(function (req, res, next) { diff --git a/bin/www b/bin/www index 512a090..b1f6bb9 100644 --- a/bin/www +++ b/bin/www @@ -8,6 +8,7 @@ var app = require('../app'); var debug = require('debug')('url-shortener-app:server'); var http = require('http'); + /** * Get port from environment and store in Express. */ @@ -15,6 +16,8 @@ var http = require('http'); var port = normalizePort(process.env.PORT || '3000'); app.set('port', port); + + /** * Create HTTP server. */ diff --git a/config/dbConfig.js b/config/dbConfig.js new file mode 100644 index 0000000..b0c8717 --- /dev/null +++ b/config/dbConfig.js @@ -0,0 +1,14 @@ +require('dotenv').config() +const mongoose = require('mongoose') + +const connectDB = async () => { + try { + await mongoose.connect(process.env.DATABASE_URI, { + }) + console.log('MongoDB connected') + } catch (error) { + console.error(error.message) + process.exit(1) + } +} +module.exports = connectDB; \ No newline at end of file diff --git a/controllers/urlController.js b/controllers/urlController.js new file mode 100644 index 0000000..8e7ace4 --- /dev/null +++ b/controllers/urlController.js @@ -0,0 +1,67 @@ +const URL = require('../models/urlSchema'); +const { validateUrlInput } = require('../middlewares/urlMiddleware'); + +exports.shortenUrl = async (req, res) => { + const { originalUrl, alias } = req.validatedData; + + try { + let url; + if (alias) { + url = await URL.findOne({ alias }); + if (url) { + return res.status(400).json({ message: 'Alias already in use' }); + } + } + + const shortUrl = `http://localhost:3000/${alias}`; + url = new URL({ originalUrl, shortUrl, alias }); + await url.save(); + + res.status(201).json(url); + } catch (error) { + return res.status(500).render('error', { message: error.message, error: { status: 500 } }); + } +}; + +exports.redirectToOriginalUrl = async (req, res) => { + const shortUrl = req.params.alias; + + try { + const url = await URL.findOneAndUpdate( + { alias: shortUrl }, + { $inc: { clickCount: 1 } }, + { new: true } + ); + if (url) { + return res.redirect(url.originalUrl); + } else { + return res.status(404).render('error', { message: 'URL not found', error: { status: 404 } }); + } + } catch (error) { + return res.status(500).render('error', { message: error.message, error: { status: 500 } }); + } +}; + +exports.getUrlStats = async (req, res) => { + const shortUrl = req.params.alias; + + try { + const url = await URL.findOne({ alias: shortUrl }); + if (url) { + res.render('stats', { originalUrl: url.originalUrl, clickCount: url.clickCount }); + } else { + return res.status(404).render('error', { message: 'URL not found', error: { status: 404 } }); + } + } catch (error) { + return res.status(500).render('error', { message: error.message, error: { status: 500 } }); + } +}; + +exports.getAllUrls = async (req, res) => { + try { + const urls = await URL.find(); + res.json(urls); + } catch (error) { + return res.status(500).render('error', { message: error.message, error: { status: 500 } }); + } +}; \ No newline at end of file diff --git a/middlewares/urlMiddleware.js b/middlewares/urlMiddleware.js new file mode 100644 index 0000000..3a3d799 --- /dev/null +++ b/middlewares/urlMiddleware.js @@ -0,0 +1,20 @@ +const validUrl = require('valid-url'); + +const validateUrlInput = (req, res, next) => { + const { originalUrl, alias } = req.body; + if (!originalUrl) { + return res.status(400).json({ message: 'Original URL is required' }); + } + if(!alias) { + req.body.alias = Math.random().toString(36).substring(7); + } + if (!validUrl.isUri(originalUrl)) { + return res.status(400).json({ message: 'Invalid original URL' }); + } + req.validatedData = { originalUrl, alias }; + next(); +}; + +module.exports = { + validateUrlInput, +}; diff --git a/models/urlSchema.js b/models/urlSchema.js new file mode 100644 index 0000000..c944ec3 --- /dev/null +++ b/models/urlSchema.js @@ -0,0 +1,30 @@ +const mongoose = require('mongoose') + +const urlSchema = new mongoose.Schema({ + originalUrl: { + type: String, + required: true + }, + shortUrl: { + type: String, + required: true, + unique: true + }, + alias: String, + createdAt: { + type: Date, + default: Date.now + }, + updatedAt: { + type: Date, + default: Date.now + }, + clickCount:{ + type: Number, + default: 0 + } +}); + +const URL = mongoose.model('URL', urlSchema); + +module.exports = URL; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 6fd9de3..a1dddc4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,15 +10,41 @@ "dependencies": { "cookie-parser": "~1.4.4", "debug": "~2.6.9", + "dotenv": "^16.4.4", "ejs": "^3.1.9", "express": "^4.18.2", "http-errors": "~1.6.3", - "morgan": "~1.9.1" + "mongodb": "^6.3.0", + "mongoose": "^8.1.3", + "morgan": "~1.9.1", + "shortid": "^2.2.16", + "valid-url": "^1.0.9" }, "devDependencies": { "nodemon": "^3.0.1" } }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.4.tgz", + "integrity": "sha512-8zJ8N1x51xo9hwPh6AWnKdLGEC5N3lDa6kms1YHmFBoRhTpJR6HG8wWk0td1MVCu9cD4YBrvjZEtd5Obw0Fbnw==", + "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 +221,14 @@ "node": ">=8" } }, + "node_modules/bson": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.3.0.tgz", + "integrity": "sha512-balJfqwwTBddxfnidJZagCBPP/f48zj9Sdp3OJswREOgsJzHiQSaOIAtApSgDQFYgHqAvFkp53AFSqjMDZoTFw==", + "engines": { + "node": ">=16.20.1" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -385,6 +419,17 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/dotenv": { + "version": "16.4.4", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.4.tgz", + "integrity": "sha512-XvPXc8XAQThSjAbY6cQ/9PcBXmFoWuw1sQ3b8HqUCR6ziGXjkTi//kB9SWa2UwqlgdAIuRqAa/9hVljzPehbYg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -823,6 +868,14 @@ "node": ">=10" } }, + "node_modules/kareem": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.5.1.tgz", + "integrity": "sha512-7jFxRVm+jD+rkq3kY0iZDJfsO2/t4BBPeEb2qKn2lR/9KhuksYk5hxzfRYWMPV8P/x2d0kHD306YyWLzjjH+uA==", + "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 +896,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 +955,86 @@ "node": "*" } }, + "node_modules/mongodb": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.3.0.tgz", + "integrity": "sha512-tt0KuGjGtLUhLoU263+xvQmPHEGTw5LbcNC73EoFRYgSHwZt5tsoJC110hDyO1kjQzpgNrpdcSza9PknWN4LrA==", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.0", + "bson": "^6.2.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.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.0.tgz", + "integrity": "sha512-t1Vf+m1I5hC2M5RJx/7AtxgABy1cZmIPQRMXw+gEIPn/cZNF3Oiy+l0UIypUwVB5trcWHq3crg2g3uAR9aAwsQ==", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^13.0.0" + } + }, + "node_modules/mongoose": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.1.3.tgz", + "integrity": "sha512-a5MajZSDJiQgy0iQcR+MIpFe7zehGJI4doJ6Dh1MvnGh8/HNNhr5pn07RPA86KCTjP2vuKdffpFmvXxcHiUOjw==", + "dependencies": { + "bson": "^6.2.0", + "kareem": "2.5.1", + "mongodb": "6.3.0", + "mpath": "0.9.0", + "mquery": "5.0.0", + "ms": "2.1.3", + "sift": "16.0.1" + }, + "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,11 +1050,56 @@ "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", "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": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -1062,6 +1245,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", @@ -1275,6 +1466,15 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" }, + "node_modules/shortid": { + "version": "2.2.16", + "resolved": "https://registry.npmjs.org/shortid/-/shortid-2.2.16.tgz", + "integrity": "sha512-Ugt+GIZqvGXCIItnsL+lvFJOiN7RYqlGy7QE41O3YC1xbNSeDGIRO7xg2JJXIAj1cAGnOeC1r7/T9pgrtQbv4g==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dependencies": { + "nanoid": "^2.1.0" + } + }, "node_modules/side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -1288,6 +1488,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sift": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/sift/-/sift-16.0.1.tgz", + "integrity": "sha512-Wv6BjQ5zbhW7VFefWusVP33T/EM0vYikCaQ2qR8yULbsilAT8/wQaXvuQ3ptGLpoKx+lihJE3y2UTgKDyyNHZQ==" + }, "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 +1505,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 +1565,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", @@ -1386,6 +1610,11 @@ "node": ">= 0.4.0" } }, + "node_modules/valid-url": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz", + "integrity": "sha512-QQDsV8OnSf5Uc30CKSwG9lnhMPe6exHtTXLRYX8uMwKENy640pU+2BgBL0LRbDh/eYRahNCS7aewCx0wf3NYVA==" + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -1394,6 +1623,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..7f36fd0 100644 --- a/package.json +++ b/package.json @@ -8,10 +8,15 @@ "dependencies": { "cookie-parser": "~1.4.4", "debug": "~2.6.9", + "dotenv": "^16.4.4", "ejs": "^3.1.9", "express": "^4.18.2", "http-errors": "~1.6.3", - "morgan": "~1.9.1" + "mongodb": "^6.3.0", + "mongoose": "^8.1.3", + "morgan": "~1.9.1", + "shortid": "^2.2.16", + "valid-url": "^1.0.9" }, "devDependencies": { "nodemon": "^3.0.1" diff --git a/public/script.js b/public/script.js new file mode 100644 index 0000000..6b4ff8f --- /dev/null +++ b/public/script.js @@ -0,0 +1,57 @@ +document.addEventListener('DOMContentLoaded', async () => { + const form = document.getElementById('urlForm'); + const table = document.getElementById('urlTable'); + const urlInput = document.getElementById('urlInput'); + const aliasInput = document.getElementById('aliasInput'); + + // Fetch existing URLs when the page loads + await fetchExistingUrls(); + + form.addEventListener('submit', async (e) => { + e.preventDefault(); + const url = urlInput.value; + const alias = aliasInput.value; + try { + const response = await fetch('/shorten', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ originalUrl: url, alias: alias }) + }); + + if (!response.ok) { + throw new Error('Failed to shorten URL'); + } + + const data = await response.json(); + appendRow(data.originalUrl, data.shortUrl); + } catch (error) { + console.log(error.message); + alert('Failed to shorten URL'); + } + }); + + async function fetchExistingUrls() { + try { + const response = await fetch('/urls'); + if (!response.ok) { + throw new Error('Failed to fetch existing URLs'); + } + const data = await response.json(); + data.forEach(({ originalUrl, shortUrl }) => appendRow(originalUrl, shortUrl)); + } catch (error) { + console.log(error.message); + alert('Failed to fetch existing URLs'); + } + } + + function appendRow(originalUrl, shortenedUrl) { + const row = table.insertRow(-1); + const originalUrlCell = row.insertCell(0); + const shortenedUrlCell = row.insertCell(1); + + originalUrlCell.innerHTML = `${originalUrl}`; + shortenedUrlCell.innerHTML = `${shortenedUrl}`; + } +}); diff --git a/routes/index.js b/routes/index.js index 5890929..457e445 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,8 +1,18 @@ const router = require('express').Router(); +const { validateUrlInput } = require('../middlewares/urlMiddleware'); +const urlController = require('../controllers/urlController'); + + /* GET home page. */ router.get('/', function (req, res, next) { res.render('index', { title: 'Express' }); }); +router.get('/urls', urlController.getAllUrls); +router.post('/shorten', validateUrlInput, urlController.shortenUrl); +router.get('/:alias', urlController.redirectToOriginalUrl); +router.get('/:alias/stats', urlController.getUrlStats); + + module.exports = router; diff --git a/views/error.ejs b/views/error.ejs index 7cf94ed..0349f88 100644 --- a/views/error.ejs +++ b/views/error.ejs @@ -1,3 +1,4 @@ +
<%= error.stack %>diff --git a/views/stats.ejs b/views/stats.ejs new file mode 100644 index 0000000..5e6f6c9 --- /dev/null +++ b/views/stats.ejs @@ -0,0 +1,52 @@ + + + + + +
Shortened URL: <%= originalUrl %>
+Click Count: <%= clickCount %>
+ +