Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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 : {};
Expand Down
18 changes: 18 additions & 0 deletions models/urlModel.js
Original file line number Diff line number Diff line change
@@ -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;
15 changes: 8 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
77 changes: 74 additions & 3 deletions routes/index.js
Original file line number Diff line number Diff line change
@@ -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;
66 changes: 49 additions & 17 deletions views/index.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,20 @@

.form-container {
margin-bottom: 20px;
text-align: center;
}

.form-container label {
display: block;
margin-bottom: 5px;
}

.form-container input[type="text"] {
width: 300px;
height: 30px;
padding: 5px;
font-size: 14px;
margin-right: 10px;
}

.form-container input[type="submit"] {
Expand All @@ -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,
Expand All @@ -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;
}
</style>
</head>

Expand All @@ -71,24 +99,28 @@

<div class="table-container">
<table id="urlTable">
<tr>
<th>Original URL</th>
<th>Shortened URL</th>
</tr>
<tr>
<td><a href="https://github.com/cat-backend-nodejs/nodejs-roadmap">https://github.com/cat-backend-nodejs/nodejs-roadmap</a></td>
<td><a href="http://localhost:3000/nodejs">http://localhost:3000/nodejs</a></td>
</tr>
<tr>
<td><a href="https://stackoverflow.com/users/11936196/seif-el-din-sweilam">https://stackoverflow.com/users/11936196/seif-el-din-sweilam</a></td>
<td><a href="http://localhost:3000/stackoverflow">http://localhost:3000/stackoverflow</a></td>
</tr>
<tr>
<td><a href="https://drive.google.com/drive/u/1/folders/1bpiBKNBd9n0Z79wrZvHVT1KyA9OBROLx">https://drive.google.com/drive/u/1/folders/1bpiBKNBd9n0Z79wrZvHVT1KyA9OBROLx</a></td>
<td><a href="http://localhost:3000/himym">http://localhost:3000/himym</a></td>
</tr>
<thead>
<tr>
<th>Original URL</th>
<th>Shortened URL</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<% urls.forEach(function(url) { %>
<tr>
<td><a href="<%= url.longUrl %>" target="_blank"><%= url.longUrl %></a></td>
<td><a href="/<%= url.alias %>" target="_blank"><%= domain %><%= url.alias %></a></td>
<td>
<form id="deleteUrl" method="GET" action="/<%= url._id %>/delete">
<button type="submit">Delete</button>
</form>
</td>
</tr>
<% }) %>
</tbody>
</table>
</div>
</body>

</html>
</html>
74 changes: 74 additions & 0 deletions views/invalid.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Invalid Alias</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: #f8f8f8;
color: #333;
}

.container {
max-width: 600px;
margin: 50px auto;
padding: 20px;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
text-align: center;
}

h1 {
color: #e74c3c;
}

h2 {
color: #c0392b;
}

ul {
text-align: left;
margin: 20px 0;
padding: 0;
list-style-type: none;
}

ul li {
margin-bottom: 10px;
}

a {
display: inline-block;
margin-top: 20px;
padding: 10px 20px;
color: #fff;
background-color: #3498db;
text-decoration: none;
border-radius: 5px;
}

a:hover {
background-color: #2980b9;
}
</style>
</head>

<body>
<div class="container">
<h1>Invalid Alias</h1>
<h2>Your alias must match these conditions:</h2>
<ul>
<li>Does not contain special characters except '_'</li>
<li>Has a length of 4 characters or more</li>
</ul>
<a href="/">Return to Home Page</a>
</div>
</body>

</html>
Loading