Skip to content

Commit

Permalink
Merge 7e3e6b1 into 97c54d9
Browse files Browse the repository at this point in the history
  • Loading branch information
olusoladavid committed Aug 2, 2018
2 parents 97c54d9 + 7e3e6b1 commit 2023ab2
Show file tree
Hide file tree
Showing 7 changed files with 570 additions and 8 deletions.
194 changes: 194 additions & 0 deletions server/controllers/entryController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
import { validationResult } from 'express-validator/check';
import { query } from '../db/index';
import validate from '../utils/validate';

class entryController {
/**
* Get all of a user's diary entries
* Requires auth token to be passed in authorization header
* @static
* @param {*} req - Client request object
* @param {*} res - Server response object
* @returns {object} token
* @memberof userController
*/
static getAllEntries(req, res) {
let { limit, page } = req.query;
// validate queries
limit = validate.isNumber(limit) ? limit : 20;
page = validate.isNumber(page) ? page : 0;
// get entries
query(
`SELECT entries.id, entries.title, entries.content, entries.created_on, entries.updated_on,
entries.is_favorite FROM entries INNER JOIN users ON entries.user_id=users.id WHERE users.email=$1
LIMIT $2 OFFSET $3`,
[req.authorizedUser.email, limit, page * limit],
(err, result) => {
if (err) {
console.log(err);
return res.status(500).json({ error: { message: 'An error occurred on the server' } });
}
return res.status(200).json({ entries: result.rows, meta: { limit, page } });
},
);
}

/**
* @description Creates a new entry by a user
*
* @static
* @param {*} req - Request object with title, content, is_favorite properties
* @param {*} res - Response object
* @returns {object} response
* @memberof entryController
*/
static addEntry(req, res) {
// validate entry fields - 400
const errorsFound = validationResult(req);
if (!errorsFound.isEmpty()) {
return res.status(400).json({ error: { message: errorsFound.array()[0].msg } });
}

const entry = {
title: req.body.title,
content: req.body.content,
isFavorite: req.body.is_favorite,
};
// add entry to database
query(
`INSERT INTO entries (user_id, title, content, is_favorite)
VALUES ((SELECT id from users WHERE email=$1), $2, $3, $4) RETURNING id, title, content, is_favorite, created_on`,
[req.authorizedUser.email, entry.title, entry.content, entry.isFavorite],
(err, result) => {
if (err) {
console.log(err);
return res.status(500).json({ error: { message: 'An error occurred on the server' } });
}
return res.status(201).json(result.rows[0]);
},
);
}

/**
* @description Fetches a single user entry by id
*
* @static
* @param {*} req - Request object with param 'id'
* @param {*} res - Response object
* @memberof entryController
*/
static getEntry(req, res) {
query(
`SELECT entries.id, entries.title, entries.content, entries.created_on, entries.is_favorite FROM entries
INNER JOIN users
ON entries.user_id = users.id
WHERE users.email=$1 AND entries.id=$2`, [req.authorizedUser.email, req.params.id],
(err, result) => {
if (err) {
console.log(err);
return res.status(500).json({ error: { message: 'An error occurred on the server' } });
}
if (!result.rowCount) {
return res.status(404).json({ error: { message: 'Entry not found' } });
}
return res.status(200).json(result.rows[0]);
},
);
}

/**
* @description Modifies a previously created entry not later than 24 hours
*
* @static
* @param {*} req - Request object
* @param {*} res - Response object
* @returns {obj} response
* @memberof entryController
*/
static modifyEntry(req, res) {
// validate entry fields - 400
const errorsFound = validationResult(req);
if (!errorsFound.isEmpty()) {
return res.status(400).json({ error: { message: errorsFound.array()[0].msg } });
}

// deconstruct request body
let { title, content } = req.body;
title = title || null;
content = content || null;
// check if isFavorite is defined
const isFavorite = validate.booleanOrNull(req.body.is_favorite);

// check if entry is already older than a day
query(
`SELECT entries.created_on FROM entries
INNER JOIN users
ON entries.user_id = users.id
WHERE users.email=$1 AND entries.id=$2`, [req.authorizedUser.email, req.params.id],
(err, result) => {
if (err) {
console.log(err);
return res.status(500).json({ error: { message: 'An error occurred on the server' } });
}
if (!result.rowCount) {
return res.status(404).json({ error: { message: 'Entry not found' } });
}
const createdOn = new Date(result.rows[0].created_on).getTime();
const hoursSinceCreated = (Date.now() - createdOn) / (1000 * 60 * 60);
if (hoursSinceCreated > 24) {
return res.status(403).json({
error: { message: 'Cannot update entry after 24 hours' },
});
}
},
);
// add entry to database
query(
`UPDATE entries SET
title = COALESCE($1, title), content = COALESCE($2, content),
is_favorite = COALESCE($3, is_favorite)
FROM users
WHERE entries.user_id=users.id
AND users.email=$4
AND entries.id=$5 RETURNING entries.id, entries.title, entries.content, entries.is_favorite, entries.created_on`,
[title, content, isFavorite, req.authorizedUser.email, req.params.id],
(err, result) => {
if (err) {
console.log(err);
return res.status(500).json({ error: { message: 'An error occurred on the server' } });
}
return res.status(200).json(result.rows[0]);
},
);
}

/**
* @description Deletes a single user entry by id
*
* @static
* @param {*} req - Request object with param 'id'
* @param {*} res - Response object
* @memberof entryController
*/
static deleteEntry(req, res) {
query(
`DELETE FROM entries
USING users
WHERE entries.user_id=users.id
AND users.email=$1
AND entries.id=$2`, [req.authorizedUser.email, req.params.id],
(err, result) => {
if (err) {
console.log(err);
return res.status(500).json({ error: { message: 'An error occurred on the server' } });
}
if (!result.rowCount) {
return res.status(404).json({ error: { message: 'Entry not found' } });
}
return res.status(204).json();
},
);
}
}

export default entryController;
25 changes: 25 additions & 0 deletions server/controllers/userController.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,31 @@ class userController {
});
});
}

/**
* @description Fetches user profile
*
* @static
* @param {*} req - Request object
* @param {*} res - Response object
* @memberof userController
*/
static getProfile(req, res) {
query(
`SELECT COUNT(*) FROM entries
INNER JOIN users
ON entries.user_id = users.id
WHERE users.email=$1`, [req.authorizedUser.email],
(err, result) => {
if (err) {
console.log(err);
return res.status(500).json({ error: { message: 'An error occurred on the server' } });
}
console.log(result.rows);
return res.status(200).json({ email: req.authorizedUser.email, entriesCount: result.rows[0].count });
},
);
}
}

export default userController;
24 changes: 21 additions & 3 deletions server/routes/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import express from 'express';
import userController from '../controllers/userController';
import entryController from '../controllers/entryController';
import verifyToken from '../utils/verifyToken';
import validate from '../utils/validate';

const router = express.Router();
Expand All @@ -13,8 +15,24 @@ router.get('/', (req, res) => {
router.post('/auth/signup', validate.signupInputs, userController.createUser);

/* Login user */
router.post('/auth/login',
validate.loginInputs,
userController.loginUser);
router.post('/auth/login', validate.loginInputs, userController.loginUser);

/* GET all user entries */
router.get('/entries', verifyToken, entryController.getAllEntries);

/* POST a new entry */
router.post('/entries', verifyToken, validate.newEntry, entryController.addEntry);

/* GET a single entry */
router.get('/entries/:id', verifyToken, entryController.getEntry);

/* PUT new data in existing entry */
router.put('/entries/:id', verifyToken, validate.modifyEntry, entryController.modifyEntry);

/* DELETE a single entry */
router.delete('/entries/:id', verifyToken, entryController.deleteEntry);

/* GET user profile */
router.get('/profile', verifyToken, userController.getProfile);

export default router;
24 changes: 24 additions & 0 deletions server/utils/validate.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,30 @@ const validate = {
.isString()
.withMessage('Your password is invalid'),
],
newEntry: [
check('title')
.isString()
.withMessage('Title should be a string'),
check('content')
.isString()
.withMessage('Content should be a string'),
check('is_favorite')
.isBoolean()
.withMessage('isFavorite property of a story should be boolean'),
],
isNumber: number => !Number.isNaN(Number(number)),
modifyEntry: [
check('title')
.isString()
.optional(),
check('content')
.isString()
.optional(),
check('is_favorite')
.isBoolean()
.optional(),
],
booleanOrNull: bool => (typeof bool === 'undefined' ? null : bool),
};

export default validate;
24 changes: 24 additions & 0 deletions server/utils/verifyToken.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import jwt from 'jsonwebtoken';
import dotenv from 'dotenv';

dotenv.config();

const verifyToken = (req, res, next) => {
try {
if (!req.headers.authorization) {
return res
.status(401)
.json({ error: { message: 'Authorization failed. Please provide a token' } });
}
const token = req.headers.authorization.split(' ')[1];
const authorizedUser = jwt.verify(token, process.env.SECRET_KEY);
req.authorizedUser = authorizedUser;
next();
} catch (error) {
res
.status(401)
.json({ error: { message: 'Authorization failed. Your token is invalid or expired' } });
}
};

export default verifyToken;
8 changes: 4 additions & 4 deletions test/sampleData.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,19 @@ const sampleData = {
validEntry: {
title: 'Cool Title',
content: 'Awesome content',
isFavorite: true,
is_favorite: true,
},
invalidEntry: {
title: 5, // title should be a string
title: '5',
content: 'Awesome Content',
isFavorite: 'false', // isFavorite should be boolean
is_favorite: 5, // isFavorite should be boolean
},
incompleteValidEntry: {
title: 'Cool Title',
content: 'Lovely Content',
},
incompleteInvalidEntry: {
isFavorite: 'true',
is_favorite: 'true',
},
invalidEntryId: 0,
};
Expand Down
Loading

0 comments on commit 2023ab2

Please sign in to comment.