diff --git a/.DS_Store b/.DS_Store
deleted file mode 100644
index 540e828..0000000
Binary files a/.DS_Store and /dev/null differ
diff --git a/.vscode/settings.json b/.vscode/settings.json
deleted file mode 100644
index 88fb27f..0000000
--- a/.vscode/settings.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "eslint.enable": true
-}
diff --git a/Procfile b/Procfile
index 28fe750..0e339ba 100644
--- a/Procfile
+++ b/Procfile
@@ -1 +1,2 @@
web: npm run start
+clock: node server/dist/cron/index.js
diff --git a/client/.DS_Store b/client/.DS_Store
deleted file mode 100644
index 01ae780..0000000
Binary files a/client/.DS_Store and /dev/null differ
diff --git a/client/src/.DS_Store b/client/src/.DS_Store
deleted file mode 100644
index c97186b..0000000
Binary files a/client/src/.DS_Store and /dev/null differ
diff --git a/client/src/app/.DS_Store b/client/src/app/.DS_Store
deleted file mode 100644
index 3f73546..0000000
Binary files a/client/src/app/.DS_Store and /dev/null differ
diff --git a/client/src/app/actions/actiontype.js b/client/src/app/actions/actiontype.js
index 9d34e20..8086955 100644
--- a/client/src/app/actions/actiontype.js
+++ b/client/src/app/actions/actiontype.js
@@ -5,7 +5,7 @@ export const USER_LOG_IN_FAILURE = 'USER_LOG_IN_FAILURE';
export const USER_LOGGED_OUT = 'USER_LOGGED_OUT';
-export const FETCHING_BOOKS= 'FETCHING_BOOKS';
+export const FETCHING_BOOKS = 'FETCHING_BOOKS';
export const SIGN_UP_USER_FAILURE = 'SIGN_UP_USER_FAILURE';
@@ -17,7 +17,11 @@ export const FETCH_ALL_BOOKS = 'FETCH_ALL_BOOKS ';
export const FETCH_BOOKS_BY_USER_ID = 'FETCH_BOOKS_BY_USER_ID';
-export const FETCH_BOOKS_REJECTED= 'FETCH_BOOKS_REJECTED';
+export const FETCH_BOOKS_REJECTED = 'FETCH_BOOKS_REJECTED';
+
+export const FETCH_ALL_OVERDUE_BOOKS = 'FETCH_ALL_OVERDUE_BOOKS';
+
+export const FETCH_ALL_OVERDUE_REJECTED = 'FETCH_ALL_OVERDUE_REJECTED';
export const UPLOAD_TO_CLOUD_IMAGE_SUCCESS = 'UPLOAD_TO_CLOUD_IMAGE_SUCCESS';
diff --git a/client/src/app/actions/api.js b/client/src/app/actions/api.js
index 08fa0a3..c315131 100644
--- a/client/src/app/actions/api.js
+++ b/client/src/app/actions/api.js
@@ -13,6 +13,9 @@ export default {
fetchRecentBooks: (offset, limit) => axios
.get(`api/v1/auth/books/recentbooks?offset=${offset}&limit=${limit}`)
.then(res => res.data),
+ fetchOverdueBooks: (offset,limit) => axios
+ .get(`api/v1/users/getoverduebooks?offset=${offset}&limit=${limit}`)
+ .then(res=>res.data),
fetchbooksbyUserId: (offset, limit) => axios
.get(`api/v1/users/borrowedbooks?offset=${offset}&limit=${limit}&returned=false`)
.then(res => res.data),
diff --git a/client/src/app/actions/authenticate.js b/client/src/app/actions/authenticate.js
index 9724d0b..e9f244a 100644
--- a/client/src/app/actions/authenticate.js
+++ b/client/src/app/actions/authenticate.js
@@ -22,7 +22,7 @@ export const userLoggedIn = data =>
data
});
- export const userLogInFailure = error =>
+export const userLogInFailure = error =>
({
type: USER_LOG_IN_FAILURE,
error
@@ -41,16 +41,16 @@ export const userLoggedOut = user =>
});
/**
- * create action: userAuthFailure : user
+ * create action: sign : user
* @function userAuthFailure
* @param {object} response
* @returns {object} action: type and response
*/
-export const signUpUserFailure = (user) =>
-({
- type: SIGNUP_USER_FAILURE,
- user
-});
+export const signUpUserFailure = user =>
+ ({
+ type: SIGNUP_USER_FAILURE,
+ user
+ });
/**
* create action: signUpUserSuccess : user
@@ -59,10 +59,10 @@ export const signUpUserFailure = (user) =>
* @returns {object} action: type and response
*/
export const signUpUserSuccess = user =>
-({
- type: SIGNUP_USER_SUCCESS,
- user
-});
+ ({
+ type: SIGNUP_USER_SUCCESS,
+ user
+ });
/**
* async helper function: sign up user
@@ -75,10 +75,10 @@ export const signup = data => dispatch => api
.signup(data)
.then((user) => {
dispatch(signUpUserSuccess(user));
- dispatch(showSuccessNotification({user}));
+ dispatch(showSuccessNotification({ user }));
return user;
})
- .catch((error) =>{
+ .catch((error) => {
dispatch(showErrorNotification({ error }));
dispatch(signUpUserFailure(error));
});
@@ -93,17 +93,17 @@ export const login = credentials => dispatch => api
.user
.login(credentials)
.then((user) => {
- const token = user.data.token;
+ const token = user.data.token;
localStorage.setItem('token', token);
- dispatch(showSuccessNotification({user}));
+ dispatch(showSuccessNotification({ user }));
setAuthorizationToken(token);
dispatch(userLoggedIn(user.data));
})
- .catch(error =>{
+ .catch((error) => {
dispatch(showErrorNotification({ error }));
- dispatch(userLogInFailure(error))
- });
+ dispatch(userLogInFailure(error));
+ });
/**
* async helper function: log out user
diff --git a/client/src/app/actions/borrowbooks.js b/client/src/app/actions/borrowbooks.js
index cbfd244..39731ed 100644
--- a/client/src/app/actions/borrowbooks.js
+++ b/client/src/app/actions/borrowbooks.js
@@ -13,18 +13,16 @@ export const LoanBooksRejected = error => ({ type: BORROW_BOOKS_FAIL, error });
* @function BorrowBooks
* @returns {function} asynchronous action
*/
-export const borrowbooks = data => dispatch =>{
-return api
+export const borrowbooks = data => dispatch => api
.book
.loanbook(data)
- .then((response)=>{
- dispatch(LoanBooksSuccess(response))
- dispatch(showSuccessNotification(response))
- return (response)
+ .then((response) => {
+ dispatch(LoanBooksSuccess(response));
+ dispatch(showSuccessNotification(response));
+ return (response);
})
- .catch((error)=>{
- dispatch(showErrorNotification({error}))
- dispatch(LoanBooksRejected({error}))
- return ({error})
- })
-}
+ .catch((error) => {
+ dispatch(showErrorNotification({ error }));
+ dispatch(LoanBooksRejected({ error }));
+ return ({ error });
+ });
diff --git a/client/src/app/actions/fetchbooks.js b/client/src/app/actions/fetchbooks.js
index bbde5c5..efd9966 100644
--- a/client/src/app/actions/fetchbooks.js
+++ b/client/src/app/actions/fetchbooks.js
@@ -4,20 +4,17 @@ import {
FETCH_ALL_BOOKS,
FETCH_BOOKS_REJECTED,
FETCH_BOOKS_BY_USER_ID,
- FETCHING_BOOKS
+ FETCHING_BOOKS,
+ FETCH_ALL_OVERDUE_BOOKS
} from './actiontype';
import api from './api';
-
export const fetchBooksRejected = error => ({ type: FETCH_BOOKS_REJECTED, error });
export const fetchRecentBooks = books => ({ type: FETCH_ALL_RECENT_BOOKS, books });
export const fetchBooks = books => ({ type: FETCH_ALL_BOOKS, books });
export const fetchBooksByUserId = books => ({ type: FETCH_BOOKS_BY_USER_ID, books });
-
-export const fetchingBooks = state => ({
- type: FETCHING_BOOKS,
- state,
-});
+export const fetchingBooks = state => ({ type: FETCHING_BOOKS, state });
+export const fetchOverdueBooks = books => ({ type: FETCH_ALL_OVERDUE_BOOKS, books });
/**
* async helper function: log in user
@@ -35,10 +32,31 @@ export const fetchAllBooks = (offset, limit) => dispatch => api
return response;
})
.catch((error) => {
- dispatch(fetchBooksRejected ({ error }));
+ dispatch(showErrorNotification({ error }))
+ dispatch(fetchBooksRejected({ error }));
dispatch(fetchingBooks(false));
});
+ /**
+ * async helper function: log in user
+ * @function fetchOverdueBooks
+ * @param {integer} offset
+ * @param {integer} limit
+ * @returns {function} asynchronous action
+ */
+export const fetchOverdueBookstoDashboard = (offset, limit) => dispatch => api
+.book
+.fetchOverdueBooks(offset, limit)
+.then((response) => {
+ dispatch(fetchOverdueBooks(response));
+ return response;
+})
+.catch((error) => {
+ dispatch(showErrorNotification({ error }))
+ dispatch(fetchBooksRejected({ error }));
+});
+
+
/**
* async helper function: fetch books to go on the dashboard
* @function fetchBooksforDashboard
@@ -46,8 +64,7 @@ export const fetchAllBooks = (offset, limit) => dispatch => api
* @param {integer} limit
* @returns {function} asynchronous action
*/
-export const fetchBooksforDashboard = (offset, limit) => dispatch =>
-api
+export const fetchBooksforDashboard = (offset, limit) => dispatch => api
.book
.fetchRecentBooks(offset, limit)
.then((response) => {
@@ -56,7 +73,8 @@ api
return response;
})
.catch((error) => {
- dispatch(fetchBooksRejected ({ error }));
+ dispatch(showErrorNotification({ error }))
+ dispatch(fetchBooksRejected({ error }));
dispatch(fetchingBooks(false));
});
@@ -74,5 +92,6 @@ export const fetchAllBooksbyId = (offset, limit) => dispatch => api
dispatch(fetchBooksByUserId(response));
})
.catch((error) => {
+ dispatch(showErrorNotification({ error }))
dispatch(fetchBooksRejected({ error }));
});
diff --git a/client/src/app/actions/loanhistory.js b/client/src/app/actions/loanhistory.js
index 8391045..ce5eeab 100644
--- a/client/src/app/actions/loanhistory.js
+++ b/client/src/app/actions/loanhistory.js
@@ -1,12 +1,12 @@
import { showErrorNotification } from './notifications';
-import {
- LOAN_HISTORY_FAILURE,
- LOAN_HISTORY_SUCCESS
-} from './actiontype';
+import { LOAN_HISTORY_FAILURE, LOAN_HISTORY_SUCCESS } from './actiontype';
import api from './api';
-export const loanhistorySuccess = bookOperations => ({ type: LOAN_HISTORY_SUCCESS, bookOperations });
-export const loanhistoryFailure = error => ({ type: LOAN_HISTORY_SUCCESS, error });
+export const loanhistorySuccess = bookOperations => ({
+ type: LOAN_HISTORY_SUCCESS,
+ bookOperations
+});
+export const loanhistoryFailure = error => ({ type: LOAN_HISTORY_FAILURE, error });
/**
* async helper function: loan history
@@ -15,14 +15,14 @@ export const loanhistoryFailure = error => ({ type: LOAN_HISTORY_SUCCESS, error
* @param {integer} limit
* @returns {function} asynchronous action
*/
-export const loanhistory = (offset, limit) => dispatch =>
- api
+export const loanhistory = (offset, limit) => dispatch => api
.book
.loanhistory(offset, limit)
.then((response) => {
- dispatch(loanhistorySuccess(response))
+ dispatch(loanhistorySuccess(response));
return response;
})
.catch((error) => {
+ dispatch(showErrorNotification({ error }))
dispatch(loanhistoryFailure({ error }));
});
diff --git a/client/src/app/actions/notifications.js b/client/src/app/actions/notifications.js
index 02d628f..3bb0b96 100644
--- a/client/src/app/actions/notifications.js
+++ b/client/src/app/actions/notifications.js
@@ -1,10 +1,8 @@
-import { reducer as notifReducer, actions as notifActions, Notifs } from 'redux-notifications';
+import { actions as notifActions } from 'redux-notifications';
+import setAuthorizationToken from '../utils/setAuthorizationToken';
const { notifSend } = notifActions;
-import { Redirect, browserHistory } from 'react-router';
-import { React } from 'react';
-import setAuthorizationToken from '../utils/setAuthorizationToken';
-import logout from '../actions/authenticate';
+
/**
* @description async notifications: show error notification
@@ -40,7 +38,7 @@ export const showErrorNotification = ({ message, error }) => (dispatch) => {
*/
export const showSuccessNotification = ({ message, user }) => (dispatch) => {
dispatch(notifSend({
- message: message || user.data.message || data.message,
+ message: message || user.data.message,
kind: 'success',
dismissAfter: 2500
}));
diff --git a/client/src/app/actions/returnbooks.js b/client/src/app/actions/returnbooks.js
index 482b289..38e2be4 100644
--- a/client/src/app/actions/returnbooks.js
+++ b/client/src/app/actions/returnbooks.js
@@ -1,8 +1,5 @@
import { showErrorNotification, showSuccessNotification } from './notifications';
-import {
- RETURN_BOOKS_FAIL,
- RETURN_BOOKS_SUCCESS
-} from './actiontype';
+import { RETURN_BOOKS_FAIL, RETURN_BOOKS_SUCCESS } from './actiontype';
import api from './api';
export const ReturnBookSuccess = returnedBook => ({ type: RETURN_BOOKS_SUCCESS, returnedBook });
@@ -14,18 +11,13 @@ export const ReturnBookRejected = error => ({ type: RETURN_BOOKS_FAIL, error });
* @returns {function} asynchronous action
*/
export const returnbook = data => dispatch => api
-.book
-.returnbook(data)
-.then((response)=>{
- dispatch(ReturnBookSuccess(response.returnedBook))
- dispatch(showSuccessNotification(response))
-
-})
-.catch((error)=>{
- dispatch(showErrorNotification({error}))
- dispatch(ReturnBookRejected(error))
-})
-
-
-
-
+ .book
+ .returnbook(data)
+ .then((response) => {
+ dispatch(ReturnBookSuccess(response.returnedBook));
+ dispatch(showSuccessNotification(response));
+ })
+ .catch((error) => {
+ dispatch(showErrorNotification({ error }));
+ dispatch(ReturnBookRejected(error));
+ });
diff --git a/client/src/app/actions/uploadImage.js b/client/src/app/actions/uploadImage.js
index e47df09..b06b4f8 100644
--- a/client/src/app/actions/uploadImage.js
+++ b/client/src/app/actions/uploadImage.js
@@ -1,8 +1,8 @@
+import { showErrorNotification, showSuccessNotification } from './notifications';
import request from 'superagent';
-import { showErrorNotification } from './notifications';
-import { UPLOAD_TO_CLOUD_IMAGE_SUCCESS,
- UPLOAD_TO_CLOUD_IMAGE_FAILURE,
+import { UPLOAD_TO_CLOUD_IMAGE_SUCCESS,
+ UPLOAD_TO_CLOUD_IMAGE_FAILURE,
CLOUDINARY_UPLOAD_PRESET,
CLOUDINARY_UPLOAD_URL } from './actiontype';
@@ -12,14 +12,15 @@ export const UploadImageToCloudFailure = error => ({ type: UPLOAD_TO_CLOUD_IMAGE
export const imageUploadToCloud = (username, imageData) => (dispatch) => {
return request
.post(CLOUDINARY_UPLOAD_URL)
- .field({'upload_preset': CLOUDINARY_UPLOAD_PRESET})
+ .field({ upload_preset: CLOUDINARY_UPLOAD_PRESET })
.field('file', imageData)
.field('public_id', `${username}`)
.then((response) => {
- dispatch(UploadImageToCloud(response.body))
- return (response.body)
+ dispatch(UploadImageToCloud(response.body));
+ return (response.body);
})
- .catch(error => {
- UploadImageToCloudFailure(error)
+ .catch((error) => {
+ dispatch(showErrorNotification({ error }))
+ UploadImageToCloudFailure(error);
});
-}
+};
diff --git a/client/src/app/components/container/Dashboard.jsx b/client/src/app/components/container/Dashboard.jsx
index f164198..ca02113 100644
--- a/client/src/app/components/container/Dashboard.jsx
+++ b/client/src/app/components/container/Dashboard.jsx
@@ -16,8 +16,8 @@ Dashboard.defaultProps = {
};
-const mapStateToProps = state=> ({
- username: state.userReducer.user.username,
+const mapStateToProps = state => ({
+ username: state.userReducer.user.username,
firstname: state.userReducer.user.firstname,
email: state.userReducer.user.email
});
diff --git a/client/src/app/components/container/booklist/DisplayAllBooks.jsx b/client/src/app/components/container/booklist/DisplayAllBooks.jsx
index 1d71971..2b1875d 100644
--- a/client/src/app/components/container/booklist/DisplayAllBooks.jsx
+++ b/client/src/app/components/container/booklist/DisplayAllBooks.jsx
@@ -1,10 +1,10 @@
import React from 'react';
import { connect } from 'react-redux';
-import Book from '../../presentation/common/book/DisplayBook.jsx';
-import { fetchAllBooks } from '../../../actions/fetchbooks';
import { PropTypes } from 'prop-types';
-import { Row, Preloader, Pagination,Col } from 'react-materialize';
-import PaginationWrapper from '../common/Pagination.jsx'
+import { Row, Preloader, Col } from 'react-materialize';
+import Book from '../../presentation/common/book/DisplayBook.jsx';
+import { fetchAllBooks } from '../../../actions/fetchbooks';
+import PaginationWrapper from '../common/Pagination.jsx';
/**
* @description Component for Display Books on the Landing page for all users
@@ -12,85 +12,82 @@ import PaginationWrapper from '../common/Pagination.jsx'
* @extends {Component}
*/
class DisplayAllBooks extends React.Component {
- constructor(props) {
- super(props);
- this.state = {
- limit: 8,
- offset: 0
- };
- }
+ constructor(props) {
+ super(props);
+ this.state = {
+ limit: 8,
+ offset: 0
+ };
+ }
- /**
+ /**
* @description dispatch actions that help populate the dashboard with books
* fetch books for the dashboard
* @method componentDidMount
* @memberof DisplayLandingBooks
* @returns {void}
*/
- componentDidMount() {
- this.props.fetchAllBooks(this.state.offset, this.state.limit);
- }
- /**
+ componentDidMount() {
+ this.props.fetchAllBooks(this.state.offset, this.state.limit);
+ }
+ /**
* render Display All Books page component
* @method render
* @member Display All Books
* @returns {object} component
*/
- render() {
- if (!this.props.allBooksList) {
- return ;
- }
-
- const getAllBooks =
- this.props.allBooksList.books.map((book) => {
- return (
-
- );
- });
- const { pagination } = this.props.allBooksList;
- const config = { items: pagination.pageCount,
- activePage: pagination.page
- };
+ render() {
+ if (!this.props.allBooksList) {
+ return ;
+ }
+ const getAllBooks =
+this.props.allBooksList.books.map(book => (
+
+));
+ const { pagination } = this.props.allBooksList;
+ const config = {
+ items: pagination.pageCount,
+ activePage: pagination.page
+ };
- return (
-
-
-
- {
+ return (
+
+
+
+ {
[...getAllBooks]}
-
-
-
+
+
+
-
- );
- }
+
+ );
+ }
}
-DisplayAllBooks.propTypes = {
- allBooksList: PropTypes.object
-};
+// DisplayAllBooks.propTypes = {
+// allBooksList: PropTypes.
+// };
DisplayAllBooks.defaultProps = {
- allBooksList: null
+ allBooksList: null
};
-const mapStateToProps = (state) => {
- return {
- allBooksList: state.bookReducer.allBooksList,
- };
-};
+const mapStateToProps = state => ({
+ allBooksList: state.bookReducer.allBooksList,
+});
export default connect(mapStateToProps, { fetchAllBooks })(DisplayAllBooks);
diff --git a/client/src/app/components/container/booklist/DisplayAllBorrowedBooks.jsx b/client/src/app/components/container/booklist/DisplayBorrowedBooks.jsx
similarity index 58%
rename from client/src/app/components/container/booklist/DisplayAllBorrowedBooks.jsx
rename to client/src/app/components/container/booklist/DisplayBorrowedBooks.jsx
index 97340a9..be45476 100644
--- a/client/src/app/components/container/booklist/DisplayAllBorrowedBooks.jsx
+++ b/client/src/app/components/container/booklist/DisplayBorrowedBooks.jsx
@@ -1,9 +1,9 @@
import React from 'react';
import { connect } from 'react-redux';
import { PropTypes } from 'prop-types';
-import { Preloader,Pagination, Row, Col } from 'react-materialize';
-import PaginationWrapper from '../common/Pagination.jsx'
-import Book from '../../presentation/common/book/DisplayBook.jsx';
+import { Preloader, Row, Col } from 'react-materialize';
+import PaginationWrapper from '../common/Pagination.jsx';
+import Book from '../../presentation/common/book/DisplayBook.jsx';
import { fetchAllBooksbyId } from '../../../actions/fetchbooks';
import MessageforNoBooks from '../../presentation/messages/dashboardMessages/MessageforNoBooks.jsx';
@@ -19,7 +19,6 @@ class DisplayAllBorrowedBooks extends React.Component {
limit: 8,
offset: 0
};
-
}
/**
@@ -31,8 +30,8 @@ class DisplayAllBorrowedBooks extends React.Component {
*/
componentDidMount() {
- {this.state.isLoading &&
}
-
+ {this.state.isLoading && }
+ ;
this.props.fetchAllBooksbyId(this.state.offset, this.state.limit);
}
@@ -43,43 +42,43 @@ class DisplayAllBorrowedBooks extends React.Component {
* @returns {object} component
*/
render() {
-
if (!this.props.borrowedBooks || this.props.borrowedBooks.books.length === 0) {
return ;
}
- const getAllBooks = this.props.borrowedBooks.books.map((book) => {
- return (
-
- );
- });
+ const getAllBooks = this.props.borrowedBooks.books.map(book => (
+
+ ));
const { pagination } = this.props.borrowedBooks;
- const config = { items: pagination.pageCount,
- activePage: pagination.page
- };
+ const config = {
+ items: pagination.pageCount,
+ activePage: pagination.page
+ };
- return
+ return (
-
-
- {[...getAllBooks]}
-
-
+
+
+ {[...getAllBooks]}
+
+
-
-
- ;
+
+
+
);
}
}
DisplayAllBorrowedBooks.PropTypes = {
@@ -92,11 +91,9 @@ DisplayAllBorrowedBooks.defaultProps = {
};
-
-const mapStateToProps = ({bookReducer}) => ({
+const mapStateToProps = ({ bookReducer }) => ({
borrowedBooks: bookReducer.borrowedBooksList
});
export default connect(mapStateToProps, { fetchAllBooksbyId })(DisplayAllBorrowedBooks);
-
diff --git a/client/src/app/components/container/booklist/DisplayLandingBooks.jsx b/client/src/app/components/container/booklist/DisplayLandingBooks.jsx
index 5a7a653..508da55 100644
--- a/client/src/app/components/container/booklist/DisplayLandingBooks.jsx
+++ b/client/src/app/components/container/booklist/DisplayLandingBooks.jsx
@@ -1,7 +1,7 @@
import React from 'react';
import { connect } from 'react-redux';
import { PropTypes } from 'prop-types';
-import { Preloader,Row } from 'react-materialize';
+import { Preloader, Row } from 'react-materialize';
import Book from '../../presentation/common/book/DisplayBook.jsx';
import { fetchBooksforDashboard } from '../../../actions/fetchbooks';
@@ -34,9 +34,9 @@ class DisplayLandingBooks extends React.Component {
* @returns {void}
*/
componentDidMount() {
- this.setState({ isFetching: true })
+ this.setState({ isFetching: true });
if (this.props.books) {
- return
+ return;
}
this
.props
@@ -50,28 +50,29 @@ class DisplayLandingBooks extends React.Component {
*/
render() {
const fetchingState = this.props.isFetching ?
- : null;
-
+ : null;
const getAllBooks = (this
.props
- .books)?this
- .props
- .books
- .map(book => ()) : [];
+ .recentBooks) ? this
+ .props
+ .recentBooks
+ .map(book => ()) : [];
return (
-
+
- {[...getAllBooks]}
+
+ {[...getAllBooks]}
+
);
@@ -86,8 +87,8 @@ DisplayLandingBooks.defaultProps = {
books: null,
};
-const mapStateToProps = ({bookReducer })=> ({
- books: bookReducer.recentBooksList,
+const mapStateToProps = ({ bookReducer }) => ({
+ recentBooks: bookReducer.recentBooksList,
isFetching: bookReducer.fetchingBooks
});
diff --git a/client/src/app/components/container/booklist/DisplayOverdueBooks.jsx b/client/src/app/components/container/booklist/DisplayOverdueBooks.jsx
new file mode 100644
index 0000000..762fe49
--- /dev/null
+++ b/client/src/app/components/container/booklist/DisplayOverdueBooks.jsx
@@ -0,0 +1,96 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import { PropTypes } from 'prop-types';
+import { Preloader, Row, Col } from 'react-materialize';
+import PaginationWrapper from '../common/Pagination.jsx';
+import Book from '../../presentation/common/book/DisplayBook.jsx';
+import { fetchOverdueBookstoDashboard } from '../../../actions/fetchbooks';
+import MessageforNoOverdueBooks from '../../presentation/messages/dashboardMessages/MessageforNoOverdueBooks.jsx';
+
+/**
+ * @description Component for Display Books on the Landing page for all users
+ * @class DisplayLandingBooks
+ * @extends {Component}
+ */
+class DisplayOverdueBooks extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ limit: 8,
+ offset: 0
+ };
+ }
+
+ /**
+ * @description dispatch actions that help populate the dashboard with books
+ * fetch books for the current user
+ * @method componentDidMount
+ * @memberof LandingPage
+ * @returns {void}
+ */
+ componentDidMount() {
+
+ {this.state.isLoading &&
}
+
;
+ this.props.fetchOverdueBookstoDashboard (this.state.offset, this.state.limit);
+ }
+ /**
+ * render Landing page component
+ * @method render
+ * @member LandingPage
+ * @returns {object} component
+ */
+ render() {
+ if (!this.props.overdueBooks || this.props.overdueBooks.books.length === 0) {
+ return
;
+ }
+ const getAllBooks = this.props.overdueBooks.books.map(book => (
+
+ ));
+ const { pagination } = this.props.overdueBooks;
+
+ const config = {
+ items: pagination.pageCount,
+ activePage: pagination.page
+ };
+
+ return (
+
+
+
+ {[...getAllBooks]}
+
+
+
+
+
);
+ }
+}
+// DisplayOverdueBooks.PropTypes = {
+// overdueBooks: PropTypes.array
+// };
+
+DisplayOverdueBooks.defaultProps = {
+ overdueBooks: null,
+
+};
+
+
+const mapStateToProps = ({ bookReducer }) => ({
+ overdueBooks: bookReducer.overdueBooksList
+});
+
+export default connect(mapStateToProps, { fetchOverdueBookstoDashboard})(DisplayOverdueBooks);
diff --git a/client/src/app/components/container/common/Pagination.jsx b/client/src/app/components/container/common/Pagination.jsx
index 616a795..f0d9909 100644
--- a/client/src/app/components/container/common/Pagination.jsx
+++ b/client/src/app/components/container/common/Pagination.jsx
@@ -5,19 +5,23 @@ import { Pagination, Row } from 'react-materialize';
class PaginationWrapper extends React.Component {
pageLimit = (pagenumber, numberOfRecords) => {
let pageOffset;
- pageOffset = (pagenumber === 1) ? 0 : pagenumber - 1;
- return pageOffset * numberOfRecords;
+ pageOffset = (pagenumber === 1)
+ ? 0
+ : pagenumber - 1;
+ return pageOffset * numberOfRecords;
}
onSelect = (number) => {
- const { numberOfRecords } = this.props;
- this.props.fetch(this.pageLimit(number, numberOfRecords), numberOfRecords);
+ const {numberOfRecords} = this.props;
+ this
+ .props
+ .fetch(this.pageLimit(number, numberOfRecords), numberOfRecords);
}
- render (){
- return(
+ render() {
+ return (
-
+
)
}
@@ -26,13 +30,12 @@ class PaginationWrapper extends React.Component {
export default PaginationWrapper;
PaginationWrapper.defaultProps = {
- items: 0,
- activePage: 1,
- maxButtons: 5
+ items: 0,
+ activePage: 1
}
PaginationWrapper.proptypes = {
- items: PropTypes.number.isRequired,
+ items: PropTypes.number.isRequired,
activePage: PropTypes.number.isRequired,
- maxButtons: PropTypes.number.isRequired
-}
+ maxButtons: PropTypes.number.isRequired
+}
diff --git a/client/src/app/components/container/loanhistory/LoanHistory.jsx b/client/src/app/components/container/loanhistory/LoanHistory.jsx
index 794de1e..c83a39d 100644
--- a/client/src/app/components/container/loanhistory/LoanHistory.jsx
+++ b/client/src/app/components/container/loanhistory/LoanHistory.jsx
@@ -1,9 +1,9 @@
import React, { Component } from 'react';
+import { connect } from 'react-redux';
+import { Preloader } from 'react-materialize';
import PropTypes from 'prop-types';
import { loanhistory } from '../../../actions/loanhistory';
-import { connect } from 'react-redux';
-import { Pagination, Row ,Preloader } from 'react-materialize';
-import PaginationWrapper from '../common/Pagination.jsx'
+import PaginationWrapper from '../common/Pagination.jsx';
import LoanHistoryTable from '../../presentation/loanhistory/LoanHistoryTable.jsx';
/**
@@ -15,51 +15,53 @@ class LoanHistory extends React.Component {
constructor(props) {
super(props);
this.state = {
- limit: 3,
+ limit: 5,
offset: 0,
isLoading: false
};
-
}
- componentDidMount(){
- this.props
- .loanhistory(this.state.offset, this.state.limit)
+ componentDidMount() {
+ $("body").css("background-color","#ffff")
+ this.props
+ .loanhistory(this.state.offset, this.state.limit);
}
-
- render(){
+
+ render() {
if (!this.props.bookOperations) {
- return
;
- }
+ return
;
+ }
const { pagination } = this.props.bookOperations;
- const config = { items: pagination.pageCount,
- activePage: pagination.page
- };
- return(
+ const config = {
+ items: pagination.pageCount,
+ activePage: pagination.page
+ };
+ return (
+
+
+
);
}
-
}
-LoanHistory.PropTypes = {
- bookOperations: PropTypes.array
-
-};
+// LoanHistory.propTypes = {
+// bookOperations: PropTypes.array
+
+// };
LoanHistory.defaultProps = {
- bookOperations: null
+ bookOperations: null
};
-const mapStateToProps = state => ({
- bookOperations : state.bookReducer.bookOperations
+const mapStateToProps = state => ({
+ bookOperations: state.bookReducer.bookOperations
});
export default connect(mapStateToProps, { loanhistory })(LoanHistory);
diff --git a/client/src/app/components/presentation/Dashboard.jsx b/client/src/app/components/presentation/Dashboard.jsx
index 0263ccd..b6f2bc6 100644
--- a/client/src/app/components/presentation/Dashboard.jsx
+++ b/client/src/app/components/presentation/Dashboard.jsx
@@ -2,10 +2,11 @@ import React from 'react';
import { Row } from 'react-materialize';
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
import SideNav from '../presentation/common/SideNav/index.jsx';
-import DisplayAllBorrowedBooks from '../container/booklist/DisplayAllBorrowedBooks.jsx';
+import DisplayAllBorrowedBooks from '../container/booklist/DisplayBorrowedBooks.jsx';
import DisplayAllBooks from '../container/booklist/DisplayAllBooks.jsx';
import LoanHistoryTable from '../container/loanhistory/LoanHistory.jsx';
import MessageforNoOverdueBooks from '../presentation/messages/dashboardMessages/MessageforNoOverdueBooks.jsx';
+import DisplayOverdueBooks from '../container/booklist/DisplayOverdueBooks.jsx';
/**
* @description Show User Dashboard
@@ -38,7 +39,7 @@ const Dashoard = props =>
-
+
diff --git a/client/src/app/components/presentation/common/book/DisplayBook.jsx b/client/src/app/components/presentation/common/book/DisplayBook.jsx
index 308be4d..e339e82 100644
--- a/client/src/app/components/presentation/common/book/DisplayBook.jsx
+++ b/client/src/app/components/presentation/common/book/DisplayBook.jsx
@@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import ReactTooltip from 'react-tooltip';
import { Modal } from 'react-materialize';
-import DisplayModal from './DisplayBookModal.jsx';
+import DisplayBookModal from './DisplayBookModal.jsx';
/**
* @description Book component taking book props
@@ -29,12 +29,10 @@ const Book = books => (
src={books.image || 'http://res.cloudinary.com/digpnxufx/image/upload/c_scale,h_499,w_325/v1507822148' +
'/bookplaceholder_kdbixx.png'}
alt={books.title}/>
-
-
- {
+ if (!loanStatus)
+ return (
+
+
+
+ )
+ else
+ return (
+
+
+
+ )
+}
+
+showDatePicker =(loanStatus) =>{
+ if(!loanStatus)
+ return(
+
+
User's Loan Status:
Available to You
+
+
Specify Return Date:
+
+
+
+ )
+else
+ return(
+ User's Loan Status:
Loaned
+
+
+ )
+
+}
+
+
+
handleBorrowClick = (event) => {
event.preventDefault();
const dateString = this.state.returndate.format("YYYY-MM-DD")
@@ -54,67 +100,30 @@ class DisplayBookModal extends React.Component {
$(`#modal-${this.props.id}`).modal({opacity: 0})
})
}
+ render(){
+ if(!this.props.isAuthenticated){
+ return(
+
+ )
+ }
-
-render(){
+else{
const isBorrowed = (this.props.borrowedBooksList.books) ? this.props.borrowedBooksList.books.map(book => {
return (book.bookid)
}) : [];
- const loanstatus = isBorrowed.includes(this.state.bookId)
-
-if(!this.props.isAuthenticated){
-return(
-
-
-
-
-
-
![{this.props.title}/]({this.props.image})
-
-
-
-
Title: {this.props.title}
-
-
Author: {this.props.author}
-
Category: {this.props.category}
-
Description: {this.props.description}
-
-
-
-
-)
+ const loanStatus = isBorrowed.includes(this.state.bookId)
+
+ const bookModalActions = this.bookActions(loanStatus)
+ const chooseReturnDate = this.showDatePicker(loanStatus)
+
-}
-else{
return (
- {!loanstatus ?
- :
- }}>
-
-
-
-
-
![{this.props.title}/]({this.props.image})
-
-
-
-
Title: {this.props.title}
-
-
Author: {this.props.author}
-
Description: {this.props.description}
-
Loan Status: {!loanstatus?
Available
:
On Loan
}
-
Specify Return Date:
-
-
-
-
-
-
+
+ {
+ chooseReturnDate
+ }
+
)
}
}
@@ -130,3 +139,38 @@ const mapStateToProps = state => ({
export default connect(mapStateToProps,{returnbook,borrowbooks})(DisplayBookModal);
+
+
+class BookModal extends React.Component{
+ constructor(props) {
+ super(props);
+ }
+ render(){
+ const { id,image,author,category,description,title,header,actions } = this.props;
+ return(
+
+
+
+
+
+
![{title}/]({image})
+
+
+
+
Title: {title}
+
+
Author: {author}
+
Category: {category}
+
Description: {description}
+
+
+ {this.props.children}
+
+
+ )
+ }
+
+}
+
+
+
diff --git a/client/src/app/components/presentation/loanhistory/LoanHistoryTable.jsx b/client/src/app/components/presentation/loanhistory/LoanHistoryTable.jsx
index fd8b804..f5748a5 100644
--- a/client/src/app/components/presentation/loanhistory/LoanHistoryTable.jsx
+++ b/client/src/app/components/presentation/loanhistory/LoanHistoryTable.jsx
@@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';
-import { Row }from 'react-materialize';
+import { Row } from 'react-materialize';
import MessageforNoBooksHistory from '../messages/dashboardMessages/MessageforNoBooksHistory.jsx';
/**
@@ -10,32 +10,33 @@ import MessageforNoBooksHistory from '../messages/dashboardMessages/MessageforNo
* @returns {JSX} JSX representation of Books table
*/
const BorrowHistoryTable = (props) => {
- const rows = props.books && props.books.length ? props.books.map((book,index) => {
- return (
-
- {book.book.title || 'N/A'} |
- ![]({book.book.bookimage) |
- {book.book.author || 'N/A'} |
- {moment(book.createdAt).format('LLLL') || 'N/A'} |
- {moment(book.returndate).format('LLLL') || 'N/A'} |
- {book.userReturndate ? moment(book.userReturndate).format('LLLL') || 'N/A': "-"} |
- {book.returnstatus ? 'Returned' : 'Still Out on Loan'} |
-
- );
- }) : null;
+ const rows = props.books && props.books.length ? props.books.map((book, index) => (
+
+ {/* {book.book.title || 'N/A'} | */}
+ ![{book.book.title}]({book.book.bookimage) |
+ {/* {book.book.author || 'N/A'} | */}
+ {moment(book.createdAt).format('LL') || 'N/A'} |
+ {moment(book.returndate).format('LL') || 'N/A'} |
+ {book.userReturndate ? moment(book.userReturndate).format('LL') || 'N/A' : '-'} |
+ {book.returnstatus ? 'Returned' : 'Still Out on Loan'} |
+ {/* {moment(book.returndate) > moment() ? Overdue : '-'} | */}
+ {(moment(book.returndate) < moment() && book.returnstatus == false )? Overdue : '-'} |
+
+ )) : null;
return (rows ?
-
- Title |
- Cover |
- Authors |
+
+ {/* Title | */}
+ Book |
+ {/* Authors | */}
Date Borrowed |
Date To Be Returned |
User Return Date |
Status |
+ Overdue |
@@ -44,7 +45,7 @@ const BorrowHistoryTable = (props) => {
:
-
+
);
};
diff --git a/client/src/app/css/style.scss b/client/src/app/css/style.scss
index 6e93486..1343b49 100644
--- a/client/src/app/css/style.scss
+++ b/client/src/app/css/style.scss
@@ -3,7 +3,7 @@
// Variables
$font-stack: 'Coiny', sans-serif;
$font-stack2: 'Purple Purse', sans-serif;
-$base-orange: #f2751c;
+$base-orange: #fd6737;
$base-white: #ffffff;
$base-background: rgb(204, 204, 204);
$secondary-color: #0089ec;
@@ -31,15 +31,14 @@ nav ul a {
.side-nav {
z-index: 9 !important;
- height: 100% !important
+ height: 100% !important;
}
a {
font-family: $font-stack2;
- color: #ec3a00;
}
.nav-wrapper {
- background-color: $base-orange;
+ background-color: #fd6737;
}
body {
font-family: $font-stack2;
@@ -56,15 +55,17 @@ p {
font-family: $font-stack2;
}
-
/*
* Landing Page
*/
-.recent-books{
+.recent-books {
margin-left: 90px;
padding-top: 20px;
}
+.landing-page-image .card {
+ max-width: 85% !important;
+}
form p {
color: $secondary-color;
@@ -110,20 +111,26 @@ form p {
.book-modal {
padding: 10px;
}
-
+.loan-status-text{
+ color: $secondary-color;
+}
+.modal-image{
+ max-width : 90%;
+}
+.loan-status{
+ padding: 10px;
+}
.modal-title {
font-size: 1.8em;
}
.modal-image {
padding-left: 15px;
- max-width: 100%
-
+ max-width: 100%;
}
.loan-book {
padding-top: 10px;
-
}
.main-wrapper {
@@ -158,21 +165,22 @@ form p {
font-family: $font-stack2;
color: $base-white;
text-align: center;
- margin-left: 4rem;
- margin-right: 4rem;
+ margin-left: 4.5rem;
+ margin-right: 4.5rem;
border-radius: 4px;
font-size: 4.5em;
- padding: 40px;
+ padding: 30px;
background-color: rgba(0, 0, 0, 0.42);
}
.overlay-main {
- margin-top: -59vh;
+ margin-top: -56vh;
padding: 50px;
- width: calc(100% - 20px);
+ width: calc(100% - 10px);
max-width: 1600px;
background-color: $base-white;
clear: both;
+ border-radius: 8px;
}
.overlay {
clear: both;
@@ -196,8 +204,8 @@ form p {
* Footer css
*/
.footer-copyright {
- background-color: $base-orange !important;
min-height: 60px !important;
+ background-color: $base-orange !important;
}
footer {
height: 3rem;
@@ -366,6 +374,9 @@ footer {
margin-left: auto;
}
+.overdue{
+ color: $base-orange;
+}
.nobooks-message {
color: #aaa;
}
@@ -374,7 +385,8 @@ footer {
*
* Modal
* */
-.return-date, .loan-status p {
+.return-date,
+.loan-status p {
display: inline-flex;
}
@@ -382,6 +394,25 @@ footer {
margin-top: -15px;
}
+.react-datepicker__header {
+ background-color: $base-light-orange;
+}
+
+@media screen and (min-width: 776px) {
+ .overlay-main {
+ margin-top: -61vh;
+ }
+}
+@media screen and (max-width: 776px) {
+ .landing-page-image .card {
+ max-width: 73% !important;
+ margin-left: 10px;
+ }
+ .overlay-main {
+ margin-top: -51vh;
+ }
+}
+
@media screen and (max-width: 541px) {
.signup-wrapper {
width: auto;
@@ -400,6 +431,11 @@ footer {
}
}
+@media only screen and (min-width: 1078px) {
+ .overlay-main {
+ margin-top: -60vh;
+ }
+}
@media only screen and (min-width: 1600px) {
.overlay-main {
max-width: 100%;
diff --git a/client/src/app/img/.DS_Store b/client/src/app/img/.DS_Store
deleted file mode 100644
index 421a87d..0000000
Binary files a/client/src/app/img/.DS_Store and /dev/null differ
diff --git a/client/src/app/mainRoot.jsx b/client/src/app/mainRoot.jsx
index 61c20f2..616093f 100644
--- a/client/src/app/mainRoot.jsx
+++ b/client/src/app/mainRoot.jsx
@@ -9,6 +9,7 @@ import Dashboard from './components/container/Dashboard.jsx';
import Logout from './components/container/authentication/Logout.jsx';
import UserRoutes from './components/hoc/UserRoutes.jsx';
import '../app/css/style.scss';
+
/**
*
diff --git a/client/src/app/reducers/bookReducers.js b/client/src/app/reducers/bookReducers.js
index 223d499..fede32c 100644
--- a/client/src/app/reducers/bookReducers.js
+++ b/client/src/app/reducers/bookReducers.js
@@ -9,18 +9,16 @@ import {
RETURN_BOOKS_SUCCESS,
LOAN_HISTORY_FAILURE,
LOAN_HISTORY_SUCCESS,
- FETCHING_BOOKS
+ FETCHING_BOOKS,
+ FETCH_ALL_OVERDUE_BOOKS
} from '../actions/actiontype';
/**
* *
*
* @export
- * @param {boolean} [state={
- * books: [],
- * fetching: false,
- * fetched: false,
- * error: null
+ * @param {object} [state={
+ *
* }]
* @param {object} action
* @returns {object} state
@@ -33,6 +31,13 @@ export default function bookReducer(state = {
...state,
fetchingBooks: action.state
};
+ case FETCH_ALL_OVERDUE_BOOKS:
+ {
+ return {
+ ...state,
+ overdueBooksList: action.books
+ };
+ }
case FETCH_BOOKS_BY_USER_ID:
{
return {
@@ -86,7 +91,15 @@ export default function bookReducer(state = {
.borrowedBooksList
.books
.filter((book) => book.bookid !== action.returnedBook.id)
+ },
+ overdueBooksList:{
+ ...state.overdueBooksList,
+ books: state
+ .overdueBooksList
+ .books
+ .filter((book) => book.bookid !== action.returnedBook.id)
}
+
}
}
case RETURN_BOOKS_FAIL:
diff --git a/package.json b/package.json
index 39dcebe..fb629ad 100644
--- a/package.json
+++ b/package.json
@@ -10,14 +10,21 @@
"pretest": "rimraf server/dist && mkdir server/dist && npm run build && sequelize db:migrate:undo:all --env=test && sequelize db:migrate --env=test && sequelize db:seed:all --env=test ",
"test": "export NODE_ENV=test && mocha server/dist/test/index.spec.js --timeout 10000 && export NODE_ENV=development",
"client:test": "jest client/ --coverage",
- "coverage": "export NODE_ENV=test && npm run pretest && nyc --reporter=lcov --reporter=text --reporter=lcovonly mocha --compilers js:babel-register server/src/test/* --exclude 'server/dist/' ",
+ "coverage": "export NODE_ENV=test && npm run pretest && nyc --reporter=lcov --reporter=text --reporter=lcovonly mocha --compilers js:babel-register server/src/test/*",
"start:migrate": "sequelize db:migrate",
"undo:migrate": "sequelize db:migrate:undo:all",
"start:dev": "babel-node server/dist/bin/www.js",
"start:webdev": "webpack -d && webpack-dev-server && --content-base client/src/ --inline --hot",
- "start-prod": "babel server/src --out-dir server/dist --presets es2015 && npm run heroku-postbuild",
+ "start-prod": "babel server/src --out-dir server/dist --presets es2015 && npm run heroku-postbuild",
"heroku-postbuild": "rimraf client/dist && mkdir client/dist && npm run build && export NODE_ENV=production && webpack"
- },
+ },
+ "nyc": {
+ "exclude": [
+ "server/dist/",
+ "server/src/cron/",
+ "server/src/mailer/"
+ ]
+ },
"engines": {
"npm": "5.3.0",
"node": "7.4.0"
@@ -45,6 +52,7 @@
"cheerio": "^1.0.0-rc.2",
"colors": "^1.1.2",
"concurrently": "^3.5.0",
+ "cron": "^1.3.0",
"cross-env": "^5.0.5",
"css-loader": "^0.28.7",
"debug": "^3.0.0",
@@ -69,12 +77,15 @@
"jsonwebtoken": "^7.4.1",
"jwt-decode": "^2.2.0",
"lodash": "^4.17.4",
+ "maildev": "^1.0.0-rc3",
"materialize-css": "^0.100.2",
"mocha": "^3.5.0",
"moment": "^2.19.2",
"morgan": "^1.8.2",
"muicss": "^0.9.25",
+ "node-cron": "^1.2.1",
"node-sass": "^4.5.3",
+ "nodemailer-mailgun-transport": "^1.3.5",
"nodemon": "^1.11.0",
"pg": "^7.0.2",
"pg-hstore": "^2.3.2",
@@ -127,7 +138,7 @@
"eslint-plugin-import": "^2.8.0",
"git-flow": "^0.2.0",
"mocha-lcov-reporter": "^1.3.0",
- "nodemailer": "^4.1.0",
+ "nodemailer": "^4.4.0",
"nyc": "^11.1.0",
"path": "^0.12.7",
"prop-types": "^15.5.10",
diff --git a/server/.DS_Store b/server/.DS_Store
deleted file mode 100644
index 4a06079..0000000
Binary files a/server/.DS_Store and /dev/null differ
diff --git a/server/src/.DS_Store b/server/src/.DS_Store
deleted file mode 100644
index bd718c5..0000000
Binary files a/server/src/.DS_Store and /dev/null differ
diff --git a/server/src/app.js b/server/src/app.js
index 05e34fd..0ae372b 100644
--- a/server/src/app.js
+++ b/server/src/app.js
@@ -5,6 +5,9 @@ import path from 'path';
import dotenv from 'dotenv';
import routes from './routes';
import authenticate from './controllers/middleware/authenticate';
+import { sendSurchargeJob } from './cron/index';
+
+
dotenv.config();
const app = express();
@@ -27,7 +30,10 @@ app.use((req, res, next) => {
next();
});
+sendSurchargeJob();
+
+ // We got a new email!
app.use('/api/v1', authenticateRoutes, routes);
app.get('*', (req, res) => res.sendFile(path.join(__dirname, '../../client/dist/app/index.html')));
diff --git a/server/src/controllers/user.js b/server/src/controllers/user.js
index a904a7f..1cc8e02 100644
--- a/server/src/controllers/user.js
+++ b/server/src/controllers/user.js
@@ -70,7 +70,7 @@ export default {
* @returns {void|Response} status, send
*
*/
- signin(req, res) {
+ signIn(req, res) {
return User.findOne({
where: {
username: req.body.username
diff --git a/server/src/controllers/userbooks.js b/server/src/controllers/userbooks.js
index 1d98653..721c36c 100644
--- a/server/src/controllers/userbooks.js
+++ b/server/src/controllers/userbooks.js
@@ -15,82 +15,102 @@ export default {
* @returns {any} book
* @memmberOf UserBooks Controller
*/
- loanbook(req, res) {
+ loanBook(req, res) {
req.params.userId = req.user.id.id || req.user.id;
if (!req.body.returndate) {
- return res.status(404).send({ message: 'Please specify a valid return date' });
+ return res
+ .status(404)
+ .send({message: 'Please specify a valid return date'});
}
const returndate = req.body.returndate;
- if (toDate(returndate) < Date.now() || !toDate(returndate)) {
- return res.status(422).send({ message: 'Please provide a valid return date' });
+ if (toDate(returndate) <= Date.now() || !toDate(returndate) ) {
+
+ return res
+ .status(422)
+ .send({message: 'Please provide a valid return date'});
}
if (req.params.userId === '') {
- return res.send(404).send({ success: false, message: 'User does not exist' });
+ return res
+ .send(404)
+ .send({success: false, message: 'User does not exist'});
}
- User.findById(req.params.userId)
+ User
+ .findById(req.params.userId)
.then((user) => {
if (!user) {
- return res.status(404).send({ message: 'User does not exist' });
+ return res
+ .status(404)
+ .send({message: 'User does not exist'});
}
UserBooks.findOne({
- where: { userid: req.params.userId, bookid: req.body.bookId, returnstatus: false },
- include: [{ model: Books, as: 'book', required: true }]
- }).then((bookfound) => {
- if (bookfound) {
- return res.status(409).send({ success: false, message: 'You have already borrowed this book' });
- }
- UserBooks.create({
+ where: {
userid: req.params.userId,
bookid: req.body.bookId,
- returndate
- })
+ returnstatus: false
+ },
+ include: [
+ {
+ model: Books,
+ as: 'book',
+ required: true
+ }
+ ]
+ }).then((bookFound) => {
+ if (bookFound) {
+ return res
+ .status(409)
+ .send({success: false, message: 'You have already borrowed this book'});
+ }
+ UserBooks
+ .create({userid: req.params.userId, bookid: req.body.bookId, returndate})
.then(() => {
- Books.findOne({ where: { id: req.body.bookId } })
- .then((booktoborrow) => {
- if (!booktoborrow || booktoborrow.quantity === 0) {
- return res.status(404).send({
- success: false,
- message: "Sorry we can't find this book or all copies of this book are on loan"
- });
+ Books
+ .findOne({
+ where: {
+ id: req.body.bookId
+ }
+ })
+ .then((bookToBorrow) => {
+ if (!bookToBorrow || bookToBorrow.quantity === 0) {
+ return res
+ .status(404)
+ .send({success: false, message: "Sorry we can't find this book or all copies of this book are on loan"});
}
- booktoborrow
+ bookToBorrow
.update({
- quantity: (booktoborrow.quantity -= 1)
- })
- .then((borrowedbook) => {
+ quantity: (bookToBorrow.quantity -= 1)
+ })
+ .then((borrowedBook) => {
res
.status(201)
- .send({
- success: true,
- message: `${borrowedbook.title} succesfully loaned`
- });
+ .send({success: true, message: `${borrowedBook.title} succesfully loaned`});
})
.catch(() => {
res
.status(500)
- .send({ success: false, message: 'Error from the client end' });
+ .send({success: false, message: 'Error from the client end'});
});
})
.catch(() => {
- res.status(500).send({ success: false, message: 'Error from the client end' });
+ res
+ .status(500)
+ .send({success: false, message: 'Error from the client end'});
});
})
.catch(() => {
res
.status(404)
- .send({
- success: false,
- message:'There is a problem with this user or book, Please contact the administrator'
- });
+ .send({success: false, message: 'There is a problem with this user or book, Please contact the administrator'});
});
});
})
.catch((error) => {
- res.status(400).send({ success: false, message: ` ${error.message}` });
+ res
+ .status(400)
+ .send({success: false, message: ` ${error.message}`});
});
},
-
/**
* Route: GET: /users/getborrowedBooklist
* @description Get list of borrowed books
@@ -99,17 +119,22 @@ export default {
* @returns {any} book list
* @memmberOf UserBooks Controller
*/
- getborrowedBooklist(req, res) {
+ getBorrowedBookList(req, res) {
const offset = req.query.offset || 0;
const limit = req.query.limit || 3;
req.params.userId = req.user.id.id || req.user.id;
if (!req.query.returned) {
- return res.status(404).send({ message: 'Please specify a value for returned books' });
+ return res
+ .status(404)
+ .send({message: 'Please specify a value for returned books'});
}
return UserBooks.findAndCountAll({
where: {
userid: req.params.userId,
- returnstatus: req.query.returned.trim()
+ returnstatus: req
+ .query
+ .returned
+ .trim()
},
include: [
{
@@ -120,17 +145,19 @@ export default {
],
limit,
offset
- })
- .then((book) => {
- if (book.length === 0) {
- return res.status(404).send({ success: false, message: 'You have no books on your loan list' });
- }
- res.status(200).send({
+ }).then((book) => {
+ if (book.length === 0) {
+ return res
+ .status(404)
+ .send({success: false, message: 'You have no books on your loan list'});
+ }
+ res
+ .status(200)
+ .send({
books: book.rows,
pagination: paginationfunc(offset, limit, book)
});
- })
- .catch(error => res.status(400).send(error.message));
+ }).catch(error => res.status(400).send(error.message));
},
/**
@@ -141,7 +168,7 @@ export default {
* @returns {any} book
* @memmberOf UserBooks Controller
*/
- returnbook(req, res) {
+ returnBook(req, res) {
req.params.userId = req.user.id.id || req.user.id;
UserBooks.findOne({
where: {
@@ -156,65 +183,109 @@ export default {
required: true
}
]
- })
- .then((book) => {
- if (!book) {
- return res.status(409).send({ success: false, message: 'You did not borrow this book' });
+ }).then((book) => {
+ if (!book) {
+ return res
+ .status(409)
+ .send({success: false, message: 'You did not borrow this book'});
+ }
+ UserBooks.update({
+ returnstatus: true,
+ userReturndate: Date.now()
+ }, {
+ where: {
+ userid: req.params.userId,
+ bookid: req.body.bookId
}
- UserBooks.update(
- {
- returnstatus: true,
- userReturndate: Date.now()
- },
- {
- where: {
- userid: req.params.userId,
- bookid: req.body.bookId
- }
+ }).then(() => {
+ Books
+ .findOne({
+ where: {
+ id: req.body.bookId
}
- ).then(() => {
- Books.findOne({
- where: {
- id: req.body.bookId
- }
- }).then((bookToreturn) => {
+ })
+ .then((bookToreturn) => {
if (!bookToreturn) {
- return res.status(404).send({ message: 'The book is not in our library' });
+ return res
+ .status(404)
+ .send({message: 'The book is not in our library'});
}
bookToreturn
.update({
- quantity: bookToreturn.quantity + 1
- })
+ quantity: bookToreturn.quantity + 1
+ })
.then((returnedBook) => {
if (returnedBook.userReturndate > returnedBook.returndate) {
- res.status(201).send({
- success: true,
- message: `You have just returned ${returnedBook.title} late, A fine will be sent to you`,
- returnedBook
- });
+ res
+ .status(201)
+ .send({success: true, message: `You have just returned ${returnedBook.title} late, A fine will be sent to you`, returnedBook});
} else {
- res.status(201).send({
- success: true,
- message: `You have just returned ${returnedBook.title}`,
- returnedBook
- });
+ res
+ .status(201)
+ .send({success: true, message: `You have just returned ${returnedBook.title}`, returnedBook});
}
});
});
+ });
+ }).catch(error => res.status(500).send(error.message));
+ },
+
+ /**
+ * Route: GET: /users/overduebooks
+ * @description Get user overdue books
+ * @param {any} req
+ * @param {any} res
+ * @returns {any} book
+ * @memmberOf UserBooks Controller
+ */
+ getOverdueBooks(req, res) {
+ const offset = req.query.offset || 0;
+ const limit = req.query.limit || 3;
+ req.params.userId = req.user.id.id || req.user.id;
+ return UserBooks.findAndCountAll({
+ where: {
+ userid: req.params.userId,
+ returnstatus: false,
+ returndate: {
+ $lt: Date.now() - 24*60*60*1000
+ }
+ },
+ include: [
+ {
+ model: Books,
+ as: 'book',
+ required: true
+ }
+ ],
+ limit,
+ offset
+ }).
+ then((book) => {
+ if (book.length === 0) {
+ return res
+ .status(404)
+ .send({ message: 'You have no overdue books' });
+ }
+ res
+ .status(200)
+ .send({
+ books: book.rows,
+ pagination: paginationfunc(offset, limit, book)
});
- })
- .catch(error => res.status(500).send(error.message));
+ }).catch(error => res.status(400).send(error.message));
},
-/**
- * Route: PUT: /users/userhistory
+
+
+ /**
+ * Route: GET: /users/userhistory
* @description Get user loan history
* @param {any} req
* @param {any} res
* @returns {any} book
* @memmberOf UserBooks Controller
*/
- getHistory(req, res){
+ getLoanHistory(req, res) {
const offset = req.query.offset || 0;
const limit = req.query.limit || 3;
req.params.userId = req.user.id.id || req.user.id;
@@ -230,18 +301,23 @@ export default {
}
],
limit,
- offset
- })
- .then((book) => {
- if (book.length === 0) {
- return res.status(404).send({ success: false, message: 'You have no books on your loan list' });
- }
- res.status(200).send({
+ offset,
+ order: [
+ ['createdAt', 'DESC']
+ ]
+ }).then((book) => {
+ if (book.length === 0) {
+ return res
+ .status(404)
+ .send({ message: 'You have no books on your loan list' });
+ }
+ res
+ .status(200)
+ .send({
books: book.rows,
pagination: paginationfunc(offset, limit, book)
});
- })
- .catch(error => res.status(400).send(error.message));
+ }).catch(error => res.status(400).send(error.message));
}
};
diff --git a/server/src/cron/index.js b/server/src/cron/index.js
new file mode 100644
index 0000000..8b3614f
--- /dev/null
+++ b/server/src/cron/index.js
@@ -0,0 +1,20 @@
+import sendSurcharge from './sendSurcharge';
+import { CronJob } from 'cron';
+
+
+export const setCron = props => new CronJob(props);
+
+export const sendSurchargeJob = () =>
+ setCron({
+ cronTime: '50 15 * * 1-7',
+ onTick: sendSurcharge,
+ timeZone: 'Africa/Lagos',
+ start: true
+ });
+
+if (require.main === module) {
+ sendSurchargeJob();
+}
+export default {
+
+};
diff --git a/server/src/cron/sendSurcharge.js b/server/src/cron/sendSurcharge.js
new file mode 100644
index 0000000..4eddddf
--- /dev/null
+++ b/server/src/cron/sendSurcharge.js
@@ -0,0 +1,63 @@
+import moment from 'moment';
+import model from '../models';
+import { transporter, mailOptions } from '../mailer/mailer';
+
+const { UserBooks, User, Books } = model;
+
+const sendSurcharge = () => (UserBooks.findAll({
+ where: {
+ returnstatus: false,
+ returndate: {
+ $lt: Date.now() - 24 * 1000
+ }
+ },
+ include: [
+ {
+ model: Books,
+ as: 'book',
+ required: true
+ }, {
+ model: User
+
+ }
+
+ ]
+}).then((overdueBooks) => {
+ const bookIds = [];
+ const usernames = [];
+ const emails = [];
+ const bookTitles = [];
+ overdueBooks.forEach((book) => {
+ bookIds.push(book.book.id);
+ usernames.push(book.User.firstname);
+ emails.push(book.User.email);
+ bookTitles.push(book.book.title);
+ });
+
+ emails.forEach((email,index) => {
+ const to = email;
+ const bcc = null;
+ const subject = "Default on Returning Book";
+ const html = `
+ Hello ${usernames[index]},
+ This is to notify you that you have exceeded the borrowing duration
+ for one of our books you will be be sent a daily fine till you return the book
+ Please return the book ${bookTitles[index]}
+
Thank you for the understanding
+ Kind regards,
`;
+ transporter.sendMail(mailOptions(to, bcc, subject, html), function (error, info) {
+ if (error) {
+ console.log(error);
+ } else {
+ console.log('Email sent: ' + info.response);
+ }
+ })
+ });
+})
+.catch((error) => {
+ process.stdout.write(error.stack);
+ process.exit(0);
+})
+);
+
+export default sendSurcharge;
diff --git a/server/src/mailer/mailer.js b/server/src/mailer/mailer.js
index e69de29..b7ab5b6 100644
--- a/server/src/mailer/mailer.js
+++ b/server/src/mailer/mailer.js
@@ -0,0 +1,14 @@
+import nodemailer from 'nodemailer';
+require('dotenv').config();
+
+export const transporter = nodemailer.createTransport({
+ service: 'gmail',
+ auth: {
+ user: process.env.EMAIL_ADDRESS,
+ pass: process.env.EMAIL_PASSWORD
+ }
+});
+
+export const mailOptions = (to, bcc, subject, html) => ({
+ from: 'Mailer from library.com', to, bcc, subject, html
+});
diff --git a/server/src/routes/index.js b/server/src/routes/index.js
index 0a287ae..3b95a93 100644
--- a/server/src/routes/index.js
+++ b/server/src/routes/index.js
@@ -16,7 +16,7 @@ Router.get('/auth/books/recentbooks', BooksController.getAllBooks);
Router.post('/auth/users/signup', fieldValidationMiddleware, nullvalidationMiddleware, UserController.create);
-Router.post('/auth/users/signin', nullvalidationMiddleware, UserController.signin);
+Router.post('/auth/users/signin', nullvalidationMiddleware, UserController.signIn);
Router.post('/books', nullvalidationMiddleware, BooksController.create);
@@ -24,12 +24,14 @@ Router.put('/books/:bookId', nullvalidationMiddleware, BooksController.update);
Router.get('/books/', BooksController.getAllBooks);
-Router.post('/users/loanbook', authdecodeToken, UserBooksController.loanbook);
+Router.post('/users/loanbook', authdecodeToken, UserBooksController.loanBook);
-Router.put('/users/returnbook', authdecodeToken, UserBooksController.returnbook);
+Router.put('/users/returnbook', authdecodeToken, UserBooksController.returnBook);
-Router.get('/users/getloanhistory', authdecodeToken, UserBooksController.getHistory);
+Router.get('/users/getloanhistory', authdecodeToken, UserBooksController.getLoanHistory);
-Router.get('/users/borrowedbooks', authdecodeToken, UserBooksController.getborrowedBooklist);
+Router.get('/users/getoverduebooks', authdecodeToken, UserBooksController.getOverdueBooks);
+
+Router.get('/users/borrowedbooks', authdecodeToken, UserBooksController.getBorrowedBookList);
export default Router;