diff --git a/app.js b/app.js index 04be2f9..005cd47 100644 --- a/app.js +++ b/app.js @@ -3,11 +3,25 @@ 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(); +// Connect to database +mongoose.connect(dbUrl, { + useNewUrlParser: true, + useUnifiedTopology: true, +}) + .then(() => { + console.log('Connected to database'); + }) + .catch((err) => { + console.error('Database connection error:', err.message); + }); + // view engine setup app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'ejs'); @@ -21,12 +35,12 @@ app.use(express.static(path.join(__dirname, 'public'))); app.use('/', indexRouter); // catch 404 and forward to error handler -app.use(function (req, res, next) { +app.use((req, res, next) => { next(createError(404)); }); // error handler -app.use(function (err, req, res, next) { +app.use((err, req, res, next) => { // set locals, only providing error in development res.locals.message = err.message; res.locals.error = req.app.get('env') === 'development' ? err : {}; diff --git a/models/urlModel.js b/models/urlModel.js new file mode 100644 index 0000000..8e70cb4 --- /dev/null +++ b/models/urlModel.js @@ -0,0 +1,18 @@ +const mongoose = require('mongoose'); +const Schema = mongoose.Schema; + +const urlSchema = new Schema({ + longUrl: { + type: String, + required: true + }, + alias: { + type: String, + required: true, + unique: true + } +}, { timestamps: true }); + +const Url = mongoose.model('Url', urlSchema); + +module.exports = Url; diff --git a/package.json b/package.json index 4c3762d..b08598d 100644 --- a/package.json +++ b/package.json @@ -6,14 +6,15 @@ "start": "node ./bin/www" }, "dependencies": { - "cookie-parser": "~1.4.4", - "debug": "~2.6.9", - "ejs": "^3.1.9", - "express": "^4.18.2", - "http-errors": "~1.6.3", - "morgan": "~1.9.1" + "cookie-parser": "^1.4.5", + "debug": "^4.3.2", + "ejs": "^3.1.6", + "express": "^4.17.1", + "http-errors": "^1.8.0", + "mongoose": "^6.0.7", + "morgan": "^1.10.0" }, "devDependencies": { - "nodemon": "^3.0.1" + "nodemon": "^2.0.12" } } diff --git a/routes/index.js b/routes/index.js index 5890929..9b06576 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,8 +1,79 @@ -const router = require('express').Router(); +const express = require('express'); +const router = express.Router(); +const Url = require('../models/urlModel'); + +/* GET a page with alias. */ +router.get('/:alias', async function (req, res, next) { + try { + const alias = req.params.alias; + const url = await Url.findOne({ alias: alias }); + if (url) { + res.redirect(url.longUrl); + } else { + res.status(404).send('404 Not Found'); + } + } catch (err) { + next(err); + } +}); /* GET home page. */ -router.get('/', function (req, res, next) { - res.render('index', { title: 'Express' }); +router.get('/', async function (req, res, next) { + try { + const urls = await Url.find({}); + res.locals.urls = urls; + res.locals.domain = `${req.protocol}://${req.get('host')}/`; + res.render('index', { title: 'Express' }); + } catch (err) { + next(err); + } +}); + +// Middleware to validate alias +const validator = function (req, res, next) { + const regex = /^[a-zA-Z0-9_]+$/; + if (req.body.aliasInput.length > 3 && regex.test(req.body.aliasInput)) { + next(); + } else { + res.status(400).render('invalid'); + } +}; + +// Middleware to check availability +const available = async function (req, res, next) { + try { + if (await Url.findOne({ alias: req.body.aliasInput })) { + res.status(400).render('not-available'); + } else { + next(); + } + } catch (err) { + next(err); + } +}; + +/* POST new url shortener from home page */ +router.post('/', validator, available, async (req, res, next) => { + try { + const url = new Url({ + longUrl: req.body.urlInput, + alias: req.body.aliasInput, + }); + await url.save(); + res.redirect('/'); + } catch (err) { + next(err); + } +}); + +/* DELETE a shortened URL */ +router.get('/:id/delete', async (req, res, next) => { + try { + await Url.findByIdAndDelete(req.params.id); + res.redirect('/'); + } catch (err) { + next(err); + } }); module.exports = router; diff --git a/views/index.ejs b/views/index.ejs index 70b7faa..0419155 100644 --- a/views/index.ejs +++ b/views/index.ejs @@ -18,6 +18,12 @@ .form-container { margin-bottom: 20px; + text-align: center; + } + + .form-container label { + display: block; + margin-bottom: 5px; } .form-container input[type="text"] { @@ -25,6 +31,7 @@ height: 30px; padding: 5px; font-size: 14px; + margin-right: 10px; } .form-container input[type="submit"] { @@ -37,9 +44,14 @@ cursor: pointer; } + .form-container input[type="submit"]:hover { + background-color: #45a049; + } + table { border-collapse: collapse; width: 100%; + margin-top: 20px; } th, @@ -53,6 +65,22 @@ background-color: #4CAF50; color: white; } + + td form { + margin: 0; + } + + button { + background-color: #e74c3c; + color: white; + border: none; + padding: 5px 10px; + cursor: pointer; + } + + button:hover { + background-color: #c0392b; + } @@ -71,24 +99,28 @@
- - - - - - - - - - - - - - - - + + + + + + + + + <% urls.forEach(function(url) { %> + + + + + + <% }) %> +
Original URLShortened URL
https://github.com/cat-backend-nodejs/nodejs-roadmaphttp://localhost:3000/nodejs
https://stackoverflow.com/users/11936196/seif-el-din-sweilamhttp://localhost:3000/stackoverflow
https://drive.google.com/drive/u/1/folders/1bpiBKNBd9n0Z79wrZvHVT1KyA9OBROLxhttp://localhost:3000/himym
Original URLShortened URLAction
<%= url.longUrl %><%= domain %><%= url.alias %> +
+ +
+
- \ No newline at end of file + diff --git a/views/invalid.ejs b/views/invalid.ejs new file mode 100644 index 0000000..438db8c --- /dev/null +++ b/views/invalid.ejs @@ -0,0 +1,74 @@ + + + + + + + Invalid Alias + + + + +
+

Invalid Alias

+

Your alias must match these conditions:

+ + Return to Home Page +
+ + + diff --git a/views/not-available.ejs b/views/not-available.ejs new file mode 100644 index 0000000..d2c55b6 --- /dev/null +++ b/views/not-available.ejs @@ -0,0 +1,54 @@ + + + + + + + Alias Not Available + + + + +
+

This alias is already taken, try another one

+ Return to Home Page +
+ + +