diff --git a/client/components/auth/Login.jsx b/client/components/auth/Login.jsx
index 7f70e0f..cf8eb4b 100644
--- a/client/components/auth/Login.jsx
+++ b/client/components/auth/Login.jsx
@@ -30,7 +30,7 @@ class Login extends Component {
};
this.handleLogin = this.handleLogin.bind(this);
this.handleChange = this.handleChange.bind(this);
- this.responseGoogle = this.responseGoogle.bind(this);
+ this.handleGoogleLogin = this.handleGoogleLogin.bind(this);
}
/**
@@ -39,7 +39,7 @@ class Login extends Component {
* @memberof Login
* @returns {Undefined} redirects to dashboard
*/
- responseGoogle(response) {
+ handleGoogleLogin(response) {
const googleProfile = response.profileObj;
this.props.login(googleProfile);
}
@@ -66,7 +66,7 @@ class Login extends Component {
handleChange(event) {
event.preventDefault();
const formField = event.target.name;
- const user = Object.assign({}, this.state);
+ const user = { ...this.state };
if (event.target.value.trim()) {
user[formField] = event.target.value.trim();
}
@@ -136,8 +136,8 @@ class Login extends Component {
diff --git a/package.json b/package.json
index d5af79f..a1145f2 100644
--- a/package.json
+++ b/package.json
@@ -7,7 +7,7 @@
"job": "babel-node server/cronjob",
"cron": "node dist/server/cronjob",
"serve": "babel-node server/bin/www.js",
- "start": "cross-env NODE_ENV=production node dist/server/bin/www.js",
+ "start": "node dist/server/bin/www.js",
"start:debug": "node --inspect dist/server/bin/www.js",
"start:client": "webpack-dev-server --hot --config ./webpack.dev.config.js",
"start:dev": "nodemon server/bin/www --exec babel-node",
@@ -17,6 +17,7 @@
"pretest": "sequelize db:migrate:undo:all --env test && sequelize db:migrate --env test && cross-env NODE_ENV=test npm run seed:tables",
"test": "cross-env NODE_ENV=test npm run seed:join && npm run test:travis",
"migrate:dev": "sequelize db:migrate:undo:all --env development && sequelize db:migrate --env development",
+ "migrate": "sequelize db:migrate:undo:all --env production && sequelize db:migrate --env production",
"coverage": "NODE_ENV=test nyc report --reporter=text-lcov | coveralls",
"seed:tables": "cross-env NODE_ENV=test babel-node server/seeders/user.js && babel-node server/seeders/bookCategory.js && babel-node server/seeders/book.js",
"seed:join": "cross-env NODE_ENV=test babel-node server/seeders/borrowedBook.js",
diff --git a/server/config/firebase.js b/server/config/firebase.js
new file mode 100644
index 0000000..9a0e90a
--- /dev/null
+++ b/server/config/firebase.js
@@ -0,0 +1,10 @@
+// Initialize Firebase // firebase.initializeApp(config);
+// TODO: MOVE THESE TO ENVIRONMENT VARIABLES
+export default {
+ apiKey: 'AIzaSyCUQ8B5UajnVa04E6hFUETBzKWD_4XlnVw',
+ authDomain: 'hellobooks-180211.firebaseapp.com',
+ databaseURL: 'https://hellobooks-180211.firebaseio.com',
+ projectId: 'hellobooks-180211',
+ storageBucket: 'hellobooks-180211.appspot.com',
+ messagingSenderId: '701806023399'
+};
diff --git a/server/controllers/books.js b/server/controllers/bookController.js
similarity index 91%
rename from server/controllers/books.js
rename to server/controllers/bookController.js
index 3819dfe..93c3c74 100644
--- a/server/controllers/books.js
+++ b/server/controllers/bookController.js
@@ -1,30 +1,7 @@
import { Book, BorrowedBook, BookCategory, Notification } from '../models';
-/**
- * Fetch all books that match a catagory from database
- * @private
- * @param {object} req - express http request object
- * @param {object} res - express http response object
- * @return {object} - express http response object
- */
-const filterBooksByCategory = (req, res) => {
- const categoryId = req.query.category;
- Book.findAll({ where: { categoryId } })
- .then((books) => {
- const message = books.length ? '' :
- 'No books match the requested category';
- return res.status(200).send({
- books,
- message,
- });
- })
- .catch(error => res.status(500).send({
- error
- }));
-};
-
-export default {
+const bookController = {
/**
* Add new book category to library.
* @public
@@ -130,7 +107,7 @@ export default {
*/
getAllBooks(req, res) {
if (req.query.category) {
- return filterBooksByCategory(req, res);
+ return bookController.filterBooksByCategory(req, res);
}
Book.findAll()
.then((books) => {
@@ -147,6 +124,29 @@ export default {
.catch(error => res.status(500).send({ error }));
},
+ /**
+ * Fetch all books that match a catagory from database
+ * @private
+ * @param {object} req - express http request object
+ * @param {object} res - express http response object
+ * @return {object} - express http response object
+ */
+ filterBooksByCategory(req, res) {
+ const categoryId = req.query.category;
+ Book.findAll({ where: { categoryId } })
+ .then((books) => {
+ const message = books.length ? '' :
+ 'No books match the requested category';
+ return res.status(200).send({
+ books,
+ message,
+ });
+ })
+ .catch(error => res.status(500).send({
+ error
+ }));
+ },
+
/**
* Edit a book's metadata.
* @public
@@ -326,3 +326,5 @@ export default {
}));
}
};
+
+export default bookController;
diff --git a/server/controllers/userController.js b/server/controllers/userController.js
new file mode 100644
index 0000000..32ecc4f
--- /dev/null
+++ b/server/controllers/userController.js
@@ -0,0 +1,249 @@
+import bcrypt from 'bcrypt';
+import dotenv from 'dotenv';
+
+import { User, Book } from '../models';
+import { getJWT } from '../helpers/helpers';
+import { transporter, mailOptions } from '../config/mail';
+
+dotenv.config();
+
+
+const userController = {
+ /**
+ * Create new user account.
+ * It sends a an object containing a success boolean
+ * and a json web token or error
+ * @public
+ * @method
+ * @param {object} req - express http request object
+ * @param {object} res - express http response object
+ * @return {Object} - returns an http response object
+ */
+
+ createUser(req, res) {
+ const username = req.body.username;
+ const email = req.body.email;
+ return User.find({
+ where: { $or: [{ username }, { email }] }
+ }).then((existingUser) => {
+ if (existingUser && existingUser.username === username) {
+ return res.status(409).json({
+ message: 'username is taken',
+ });
+ }
+ if (existingUser && existingUser.email === email) {
+ return res.status(409).json({
+ message: 'email is associated with an account',
+ });
+ }
+ User.create(req.body)
+ .then((user) => {
+ const {
+ id,
+ isAdmin,
+ membershipType,
+ } = user;
+ const jwtOptions = { id, email, username, isAdmin, membershipType };
+ const token = getJWT(jwtOptions);
+ const { firstName, lastName } = user;
+ return res.status(201).json({
+ token,
+ id,
+ firstName,
+ lastName,
+ isAdmin,
+ message: `Welcome ${firstName}. This is your dashboard`,
+ });
+ })
+ .catch(error => res.status(400).send({
+ error
+ }));
+ })
+ .catch(error => res.status(500).send({
+ error
+ }));
+ },
+
+ /**
+ * Edit user Information
+ * @public
+ * @method
+ * @param {object} req - express http request object
+ * @param {object} res - express http response object
+ * @return {Object} - returns an http response object
+ */
+ updateUserInfo(req, res) {
+ const updateData = req.body;
+ updateData.passwordResetToken = null;
+ return User.findById(req.user.id)
+ .then((user) => {
+ user.update(updateData, { returning: true, plain: true })
+ .then(() => {
+ const {
+ id,
+ email,
+ username,
+ isAdmin,
+ membershipType,
+ } = user;
+ const jwtOptions = { id, email, username, isAdmin, membershipType };
+ const token = getJWT(jwtOptions);
+ const { firstName, lastName } = user;
+ return res.status(200).json({
+ token,
+ id,
+ firstName,
+ lastName,
+ isAdmin,
+ message: 'Your information was successfully updated',
+ });
+ }, (error) => {
+ res.status(500).send({
+ error,
+ });
+ });
+ })
+ .catch(error => res.status(500).send({
+ error,
+ }));
+ },
+
+ /**
+ * Get user data on sign in.
+ * It sends a an object containing a success boolean
+ * and a json web token or error
+ * @public
+ * @method
+ * @param {object} req - express http request object
+ * @param {object} res - express http response object
+ * @return {Object} - returns an http response object
+ */
+
+ getUser(req, res) {
+ const username = req.body.username;
+ const password = req.body.password;
+ return User.findOne({ where: { username } }).then((user) => {
+ if (!user) {
+ if (req.body.authId) {
+ return userController.createUser(req, res);
+ }
+ return res.status(403).send({
+ message: 'user does not exist',
+ });
+ }
+ bcrypt.compare(password, user.password).then((result) => {
+ if (!result) {
+ return res.status(403).send({
+ message: 'wrong username and password combination',
+ });
+ }
+ const {
+ id,
+ email,
+ isAdmin,
+ membershipType,
+ } = user;
+ const jwtOptions = { id, email, username, isAdmin, membershipType };
+ const token = getJWT(jwtOptions);
+ const { firstName, lastName } = user;
+ return res.status(200).json({
+ token,
+ id,
+ firstName,
+ lastName,
+ isAdmin,
+ message: `Welcome back ${firstName}`,
+ });
+ }).catch(error => res.status(500).send({
+ error,
+ }));
+ }).catch(error => res.status(400).send({
+ error
+ }));
+ },
+
+ /**
+ * Get list of books borrowed by specific user
+ * It sends a an object containing a success boolean
+ * and a data key, an array of borrowed books or an error
+ * Response can be filtered by returned status
+ * @public
+ * @method
+ * @param {object} req - express http request object
+ * @param {object} res - express http response object
+ * @return {Object} - returns an http rresponse object
+ */
+ getBorrowedBooks(req, res) {
+ const id = req.params.id;
+ User.findOne({
+ where: { id },
+ include: [{ model: Book }]
+ }).then((user) => {
+ let books;
+ if (req.query && req.query.returned === 'false') {
+ books = user.Books.filter(
+ book => book.BorrowedBook.returned === false
+ );
+ } else if (req.query && req.query.returned === 'true') {
+ books = user.Books.filter(
+ book => book.BorrowedBook.returned === true
+ );
+ } else {
+ books = user.Books;
+ }
+ return res.status(200).send({
+ books
+ });
+ })
+ .catch(error => res.status(500).send({
+ message: 'An error occured while fetching borrowing history',
+ error,
+ }));
+ },
+
+ passwordResetMail(req, res) {
+ return User.findOne({
+ where: { email: req.body.email },
+ attributes: ['id', 'email'],
+ plain: true,
+ })
+ .then((user) => {
+ if (!user) {
+ return res.status(404).send({
+ message: 'Email does not match any account in our records',
+ });
+ }
+ const BASE_URL = process.env.NODE_ENV === 'development' ?
+ 'http://localhost:8080' :
+ 'https://segunolalive-hellobooks.com';
+ const token = getJWT({ id: user.id }, '1h');
+ user.passwordResetToken = token;
+ user.save();
+ const to = user.email;
+ const bcc = null;
+ const subject = 'no-reply: Password reset link';
+ const html = `Use this link to reset your password.
+ ${BASE_URL}/reset-password?token=${token}}
+ This link is valid only for an hour
`;
+ transporter.sendMail(mailOptions(to, bcc, subject, html),
+ (err) => {
+ if (err) {
+ return res.status(500).send({
+ message: 'An error occured while sending you a link. Try again',
+ });
+ }
+ return res.status(200).send({
+ message: 'An password reset link has been sent to your email',
+ });
+ });
+ })
+ .catch(() => (
+ res.status(500).send({
+ message: 'An error occured while sending you a link. Try again',
+ })
+ ));
+ }
+};
+
+
+export default userController;
diff --git a/server/cronjob.js b/server/cronjob.js
index 34ba078..6bb9a50 100644
--- a/server/cronjob.js
+++ b/server/cronjob.js
@@ -1,9 +1,8 @@
-import sequelize from 'sequelize';
import { BorrowedBook, User } from './models';
import { borrowingDuration } from './helpers/borrowingLimits';
import { transporter, mailOptions } from './config/mail';
-const Op = sequelize.Op;
+
const timeLimit = borrowingDuration * 1000 * 60 * 60 * 24;
const defaulters = () => (
@@ -11,7 +10,7 @@ const defaulters = () => (
where: {
returned: false,
updatedAt: {
- [Op.lt]: new Date(new Date() - timeLimit)
+ $lt: new Date(new Date() - timeLimit)
}
},
attributes: ['userId']
@@ -20,7 +19,7 @@ const defaulters = () => (
const ids = borrowedBooks.map(book => book.userId);
return User.findAll({
where: {
- id: { [Op.in]: ids }
+ id: { $in: ids }
},
attributes: ['email']
})
diff --git a/server/routes/index.js b/server/routes/index.js
index f844bfd..901dd84 100644
--- a/server/routes/index.js
+++ b/server/routes/index.js
@@ -1,8 +1,8 @@
import express from 'express';
-import userController from '../controllers/users';
-import bookController from '../controllers/books';
+import userController from '../controllers/userController';
+import bookController from '../controllers/bookController';
import transactionController from '../controllers/transactionController';
import authenticate from '../middleware/authenticate';