Skip to content

Commit

Permalink
Merge pull request #183 from seyiogunjuyigbe/refactor/docs
Browse files Browse the repository at this point in the history
Refactor/docs
  • Loading branch information
seyiogunjuyigbe authored Apr 3, 2020
2 parents 96dc736 + 0ae2e25 commit 59d7542
Show file tree
Hide file tree
Showing 11 changed files with 70 additions and 50 deletions.
17 changes: 16 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,21 @@ If you've created a new branch to work on rather than working directly on `Devel
7. Finally, push your newly merged feature branch to the remote github server for backup.
<pre>git push origin your-feature-branch</pre>

## Code Structrure & Readability

##### This will be the file and folder structure

src
├── config
├── constants.js
├── controllers
├── database
├── helpers
├── middlewares
├── models
├── routes
├── services
├── tests
└── views
<hr/>


23 changes: 3 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,10 @@ Not just a link shortener but branded and can track engagements.
### Getting Started
Below are instructions to kick start AutoMart in your local server.


**First off, you must have node/npm installed. Install the latest node version [here](https://nodejs.org/en/download/). Not to worry, the npm package comes along with the node package**

### Installation


1. Clone this repository by running this on your terminal: `git clone https://github.com/hngi/node-url-shortener.git`
2. Navigate to the project's directory with: `cd node-url-shortener`
3. Run `npm install` to install dependencies
Expand All @@ -36,30 +34,15 @@ Not just a link shortener but branded and can track engagements.
##### Test Driven
Tests are written with mocha, chai-http and chai.

##### This will be the file and folder structure

src
├── config
├── constants.js
├── controllers
├── database
├── helpers
├── middlewares
├── models
├── routes
├── services
├── tests
└── views
<hr/>

#### Stack:
* Bootstrap
* Node
* Mongodb


#### License

ISC License


### API
1. Trim a long URL

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"express": "^4.17.1",
"express-device": "^0.4.2",
"express-session": "1.16.2",
"express-validator": "^6.4.0",
"geoip-lite": "^1.3.8",
"mongoose": "^5.7.5",
"morgan": "^1.9.1",
Expand Down
10 changes: 2 additions & 8 deletions src/controllers/urlController.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import UrlShorten from "../models/UrlShorten";
import nanoid from "nanoid";
import { DOMAIN_NAME } from "../config/constants";
import { respondWithWarning } from '../helpers/responseHandler';
import { getMetric } from '../middlewares/getMetrics';

Expand All @@ -13,22 +12,17 @@ import { getMetric } from '../middlewares/getMetrics';
export const trimUrl = async (req, res) => {
try {
let {expiry_date, custom_url} = req.body;

let newUrlCode;

// this line is there because production server fails to detect our
// DOMAIN_NAME config variable
const domain_name = DOMAIN_NAME ? DOMAIN_NAME : 'trim.ng'
const domain_name = "https://"+req.headers.host

//If the user submitted a custom url, use it. This has been validated by an earlier middleware.
if (custom_url) newUrlCode = encodeURIComponent(custom_url); //Sanitize the string as a valid uri comp. first.
else newUrlCode = nanoid(5); //If no custom url is provided, generate a random one.

const newTrim = new UrlShorten({
long_url: req.url,
clipped_url: `${domain_name}/${newUrlCode}`,
urlCode: newUrlCode,
created_by: req.cookies.userID
created_by: req.cookies.userID || req.cookies['connect.sid']
});

// Date validation has been done already
Expand Down
9 changes: 9 additions & 0 deletions src/middlewares/validate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const {validationResult} = require('express-validator');
module.exports = (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
let error = {}; errors.array().map((err) => error[err.param] = err.msg);
return res.status(400).json({error});
}
next();
};
19 changes: 9 additions & 10 deletions src/middlewares/validateUrl.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,19 @@ import { respondWithWarning } from '../helpers/responseHandler';
*/
export const stripUrl = async (req, res, next) => {
const { long_url, expiry_date, custom_url } = req.body;

if(new Date(long_url).getTime() < new Date().getTime()) return respondWithWarning(res,400,'Expiry date must be in the future')
const schema = Joi.object({
url: Joi.string().regex(
/(http(s)?:\/\/.)?(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/
).error(new Error('Enter a valid URL')),
expiry: Joi.date().iso().greater(new Date()).allow('').error(new Error('Expiry date must be in the future')),
custom_url: Joi.string().regex(/^[A-Za-z0-9_.\-~]{0,}$/).error(new Error('custom URL must contain only alphanumeric, period(.), hyphen(-), underscore(_) and tilde(~) characters')),
});
const validationOptions = {
allowUnknown: true, // allow unknown keys that will be ignored
stripUnknown: true, // remove unknown keys from the validated data
};

const { error } = await schema.validate({ url: long_url, expiry: expiry_date, custom_url }, validationOptions);
const { error } = await schema.validate({ url: long_url, expiry: new Date(expiry_date), custom_url }, validationOptions);
if (error) {
const result = respondWithWarning(res, 400, error.message);
return result;
Expand All @@ -40,12 +39,12 @@ export const stripUrl = async (req, res, next) => {
* @param {*} next
*/
export const validateOwnDomain = (req, res, next) => {
// The strippedUrl already contains the hostname, so match it against our own...
// The strippedUrl already contains the hostname, so match it against our own...
if (
req.url.startsWith(DOMAIN_NAME) ||
req.url.startsWith(`https://${DOMAIN_NAME}`) ||
req.url.startsWith(`http://${DOMAIN_NAME}`) ||
req.url.startsWith(`www.${DOMAIN_NAME}`)
req.body.long_url.startsWith(req.headers.host) ||
req.body.long_url.startsWith(`https://${req.headers.host}`) ||
req.body.long_url.startsWith(`http://${req.headers.host}`) ||
req.body.long_url.startsWith(`www.${req.headers.host}`)
) {
const result = respondWithWarning(res, 400, "Cannot trim an already generated URL");
return result;
Expand All @@ -61,8 +60,8 @@ export const validateOwnDomain = (req, res, next) => {
*/
export const urlAlreadyTrimmedByUser = (req, res, next) => {
const searchParams = {
long_url: req.url,
created_by: req.cookies.userID
long_url: req.url || req.body.long_url,
created_by: req.cookies.userID || req.cookies['connect.sid']
};

UrlShorten.findOne(searchParams, (error, retrievedClip) => {
Expand Down
10 changes: 6 additions & 4 deletions src/routes/routes.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
const validate = require('../middlewares/validate');
const { check } = require('express-validator');
import {
aboutPage,
renderLandingPage,
Expand All @@ -18,11 +20,11 @@ import { getUrlClickMetrics } from '../controllers/metricsController';
export const initRoutes = app => {
app.get("/", validateCookie, renderLandingPage);
app.get("/about", (req, res) => res.status(200).render("about"));
app.post("/", stripUrl, validateOwnDomain, urlAlreadyTrimmedByUser, customUrlExists, trimUrl);
app.get("/about", aboutPage);

app.get("/docs", (req,res)=>res.status(200).redirect("https://documenter.getpostman.com/view/4447136/SzYaWe6j?version=latest"));
app.post("/", [check('long_url').isString().not().isEmpty().withMessage('Long url cannot be empty'),
check('expiry_date').optional().matches(/([12]\d{3}[-\/](0[1-9]|1[0-2])[-\/](0[1-9]|[12]\d|3[01]))/).withMessage('Date format has to be yyyy-mm-dd or yyyy/mm/dd')]
,validate, validateOwnDomain, stripUrl, urlAlreadyTrimmedByUser, customUrlExists, trimUrl);
app.get("/:id", getUrlAndUpdateCount);

app.get('/metrics/:urlShortenId', getUrlClickMetrics);
app.all("*", (req, res) => res.status(404).render("error"));
};
8 changes: 8 additions & 0 deletions src/views/about.ejs
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
<% include partials/header %>
<title>
About Trim
</title>
</head>

<!-- Body -->

<body>
<header>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="/"><img src="https://res.cloudinary.com/leankhan/image/upload/v1570985383/trim/trim-logo-2.png" alt="trim-logo" class="nav-logo"></a>
Expand Down
7 changes: 7 additions & 0 deletions src/views/error.ejs
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
<% include partials/header %>
<title>
Error... Not Found
</title>
</head>

<!-- Body -->

<body>
<div class="error-wrapper">
<h1 class="mb-3">Oops! Page not found.</h1>

Expand Down
8 changes: 8 additions & 0 deletions src/views/index.ejs
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
<% include partials/header %>
<title>
Trim — Shorten your links, elegantly
</title>
</head>

<!-- Body -->

<body>
<style>
.copy:hover {
color: #fff;
Expand Down
8 changes: 1 addition & 7 deletions src/views/partials/header.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
<!-- Header -->

<head>
<title>
Trim — Shorten your links, elegantly
</title>

<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
Expand Down Expand Up @@ -123,9 +121,5 @@ All for free :)"
<!-- JAVASCRIPT -->
<!-- <script type="module" src="/js/index.js"></script> -->
<script async src="https://app.appzi.io/bootstrap/bundle.js?token=GPqqk"></script>
</head>

<!-- Body -->

<body>

0 comments on commit 59d7542

Please sign in to comment.