├── . eslintignore ├── .babelrc ├── client ├── __tests__ │ ├── __mocks__ │ │ ├── styleMocks.js │ │ ├── fileMock.js │ │ └── mockConfig.js │ ├── components │ │ ├── utils │ │ │ ├── setAuthorizationToken.spec.js │ │ │ └── Pagination.spec.jsx │ │ ├── presentation │ │ │ ├── common │ │ │ │ ├── Footer.spec.jsx │ │ │ │ ├── modal │ │ │ │ │ ├── BookDetailForm.spec.jsx │ │ │ │ │ └── TextInput.spec.jsx │ │ │ │ └── book │ │ │ │ │ └── DisplayBook.spec.jsx │ │ │ ├── messages │ │ │ │ ├── MessageforNoBooks.spec.jsx │ │ │ │ ├── MessageforNoBooksHistory.spec.jsx │ │ │ │ └── MessageforNoCatBooks.spec.jsx │ │ │ └── authentication │ │ │ │ ├── SignIn.spec.jsx │ │ │ │ └── SignUp.spec.jsx │ │ └── container │ │ │ ├── booklists │ │ │ ├── Loader.spec.jsx │ │ │ ├── SearchBooks.spec.jsx │ │ │ ├── DisplayRecentBooks.spec.jsx │ │ │ ├── DisplayAllBooks.spec.jsx │ │ │ └── DisplayBorrowedBooks.spec.jsx │ │ │ ├── categories │ │ │ ├── CategoriesDropdownList.spec.jsx │ │ │ ├── CategoriesSideBar.spec.jsx │ │ │ ├── CategoriesOptionList.spec.jsx │ │ │ └── CategoriesWrapper.spec.jsx │ │ │ ├── common │ │ │ └── Dashboard.spec.jsx │ │ │ └── modal │ │ │ └── AddBookModal.spec.jsx │ ├── shim.js │ ├── __config__ │ │ └── setup.js │ ├── actions │ │ ├── users │ │ │ ├── uploadImage.spec.js │ │ │ ├── loanHistory.spec.js │ │ │ ├── searchBooks.spec.js │ │ │ ├── returnBooks.spec.js │ │ │ ├── borrowBooks.spec.js │ │ │ ├── changePassword.spec.js │ │ │ └── authenticate.spec.js │ │ └── admin │ │ │ ├── getUserList.spec.js │ │ │ ├── getSelectedUser.spec.js │ │ │ ├── getUserLevelList.spec.js │ │ │ ├── getAdminNotifications.spec.js │ │ │ └── changeLevel.spec.js │ └── reducers │ │ └── notifierReducer.spec.js └── src │ ├── app │ ├── img │ │ ├── bg.jpg │ │ └── 404.jpg │ ├── store │ │ ├── configStore.js │ │ ├── configStore.prod.js │ │ └── configStore.dev.js │ ├── components │ │ ├── container │ │ │ ├── header │ │ │ │ └── Header.jsx │ │ │ ├── LandingPage.jsx │ │ │ ├── booklist │ │ │ │ ├── Loader.jsx │ │ │ │ └── DisplayRecentBooks.jsx │ │ │ ├── categories │ │ │ │ ├── CategoriesAdminTab.jsx │ │ │ │ ├── CategoriesDropdownList.jsx │ │ │ │ ├── CategoriesSideBarList.jsx │ │ │ │ └── CategoriesOptionsList.jsx │ │ │ ├── common │ │ │ │ ├── Dashboard.jsx │ │ │ │ └── Pagination.jsx │ │ │ ├── authentication │ │ │ │ └── Logout.jsx │ │ │ ├── notification │ │ │ │ ├── NotificationTable.jsx │ │ │ │ └── NotificationTab.jsx │ │ │ ├── userlists │ │ │ │ └── container │ │ │ │ │ └── UserListTab.jsx │ │ │ └── loanhistory │ │ │ │ └── LoanHistory.jsx │ │ ├── presentation │ │ │ ├── messages │ │ │ │ ├── WelcomeMessage.jsx │ │ │ │ └── dashboardMessages │ │ │ │ │ ├── MessageforNoOverdueBooks.jsx │ │ │ │ │ ├── MessageforNoBooks.jsx │ │ │ │ │ ├── NoNotifications.jsx │ │ │ │ │ ├── MessageforNoCatBooks.jsx │ │ │ │ │ └── MessageforNoBooksHistory.jsx │ │ │ ├── common │ │ │ │ ├── Preloader │ │ │ │ │ └── ShowProgressBar.jsx │ │ │ │ ├── Footer.jsx │ │ │ │ ├── SideNav │ │ │ │ │ ├── UserView.jsx │ │ │ │ │ └── index.jsx │ │ │ │ ├── header │ │ │ │ │ ├── NavigationBar.jsx │ │ │ │ │ └── Header.jsx │ │ │ │ ├── book │ │ │ │ │ ├── BaseBookModal.jsx │ │ │ │ │ └── SearchBooks.jsx │ │ │ │ └── modal │ │ │ │ │ └── form │ │ │ │ │ └── TextInput.jsx │ │ │ ├── LandingPage.jsx │ │ │ ├── UserDashboard.jsx │ │ │ ├── admin │ │ │ │ └── AdminDashboard.jsx │ │ │ └── loanhistory │ │ │ │ └── LoanHistoryTable.jsx │ │ ├── Root.jsx │ │ └── hoc │ │ │ ├── UserRoutes.jsx │ │ │ ├── GuestRoutes.jsx │ │ │ └── AdminRoutes.jsx │ ├── utils │ │ ├── setAuthorizationToken.js │ │ └── localSave.js │ ├── reducers │ │ ├── rootReducers.js │ │ ├── notifierReducers.js │ │ ├── imageReducers.js │ │ ├── categoryReducers.js │ │ └── userReducers.js │ ├── actions │ │ ├── borrowBooks.js │ │ ├── index.js │ │ ├── changePassword.js │ │ ├── admin │ │ │ ├── getSelectedUser.js │ │ │ ├── getUserLevelList.js │ │ │ ├── addCategory.js │ │ │ ├── changeUserLevel.js │ │ │ ├── deleteCategory.js │ │ │ ├── getAdminNotifications.js │ │ │ ├── getUserList.js │ │ │ ├── editCategory.js │ │ │ └── books.js │ │ ├── searchBooks.js │ │ ├── returnBooks.js │ │ ├── loanHistory.js │ │ ├── uploadImage.js │ │ ├── notifications.js │ │ └── fetchCategories.js │ ├── index.jsx │ └── MainRoot.jsx │ └── index.html ├── Procfile ├── templates ├── img │ ├── bg.jpg │ ├── book1.png │ ├── book2.jpg │ ├── Schema.png │ ├── profile.jpg │ └── evolution-2-e1427283314331.jpg ├── js │ └── data-toggle.js └── stylesheets │ ├── bootstrap-imageupload.min.css │ └── styles.css ├── .hound.yml ├── apidocs ├── fonts │ ├── slate.eot │ ├── slate.ttf │ ├── slate.woff │ ├── slate.woff2 │ └── slate.svg └── images │ ├── logo.png │ └── navbar.png ├── .editorconfig ├── screenshots └── hellobooks-screenshot.png ├── server └── src │ ├── test │ ├── index.spec.js │ ├── notifications.spec.js │ ├── helpers │ │ └── usrToken.js │ └── model.spec.js │ ├── controllers │ ├── index.js │ ├── helpers │ │ └── pagination.js │ ├── middleware │ │ ├── checkAdmin.js │ │ ├── validators │ │ │ ├── fieldValidations.js │ │ │ ├── nullValidation.js │ │ │ └── validators.js │ │ └── checkGoogleAuth.js │ └── notifications.js │ ├── mailer │ └── mailer.js │ ├── cron │ ├── index.js │ └── sendSurcharge.js │ ├── models │ ├── categories.js │ ├── notifications.js │ ├── userLevel.js │ ├── index.js │ ├── userBooks.js │ └── books.js │ ├── config │ └── config.js │ ├── migrations │ ├── 20171125192622-create-categories.js │ ├── 20171125194210-create-notifications.js │ ├── 20171125184821-create-userlevel.js │ ├── 20170818172848-create-user-books.js │ ├── 20170818172635-create-books.js │ └── 20170818172333-create-user.js │ ├── seeders │ ├── seedLevels.js │ ├── seedUsers.js │ └── seedCategories.js │ ├── app.js │ └── bin │ └── www.js ├── .coverage.yml ├── .sequelizerc ├── .gitignore ├── .env.sample ├── nightwatch.config.js ├── .codeclimate.yml ├── e2e-tests ├── landing.spec.js ├── selectors.js ├── dashboard.spec.js ├── login.spec.js ├── signup.spec.js └── library.spec.js ├── LICENSE.md ├── nightwatch.json ├── .github └── PULL_REQUEST_TEMPLATE.md ├── .travis.yml ├── .eslintrc └── .sass-lint.yml /. eslintignore: -------------------------------------------------------------------------------- 1 | /server/test 2 | /dist 3 | server/dist -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { "presets": ["es2015", "react", "stage-2"] 2 | } 3 | -------------------------------------------------------------------------------- /client/__tests__/__mocks__/styleMocks.js: -------------------------------------------------------------------------------- 1 | module.exports = {}; 2 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: npm run start 2 | clock: node server/dist/cron/index.js 3 | -------------------------------------------------------------------------------- /client/__tests__/__mocks__/fileMock.js: -------------------------------------------------------------------------------- 1 | module.exports = 'image-file'; 2 | -------------------------------------------------------------------------------- /templates/img/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benfluleck/HelloBooks/HEAD/templates/img/bg.jpg -------------------------------------------------------------------------------- /.hound.yml: -------------------------------------------------------------------------------- 1 | eslint: 2 | enabled: true 3 | config_file: .eslintrc 4 | ignore_file: .eslintignore -------------------------------------------------------------------------------- /apidocs/fonts/slate.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benfluleck/HelloBooks/HEAD/apidocs/fonts/slate.eot -------------------------------------------------------------------------------- /apidocs/fonts/slate.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benfluleck/HelloBooks/HEAD/apidocs/fonts/slate.ttf -------------------------------------------------------------------------------- /apidocs/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benfluleck/HelloBooks/HEAD/apidocs/images/logo.png -------------------------------------------------------------------------------- /templates/img/book1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benfluleck/HelloBooks/HEAD/templates/img/book1.png -------------------------------------------------------------------------------- /templates/img/book2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benfluleck/HelloBooks/HEAD/templates/img/book2.jpg -------------------------------------------------------------------------------- /apidocs/fonts/slate.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benfluleck/HelloBooks/HEAD/apidocs/fonts/slate.woff -------------------------------------------------------------------------------- /apidocs/fonts/slate.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benfluleck/HelloBooks/HEAD/apidocs/fonts/slate.woff2 -------------------------------------------------------------------------------- /apidocs/images/navbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benfluleck/HelloBooks/HEAD/apidocs/images/navbar.png -------------------------------------------------------------------------------- /client/src/app/img/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benfluleck/HelloBooks/HEAD/client/src/app/img/bg.jpg -------------------------------------------------------------------------------- /templates/img/Schema.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benfluleck/HelloBooks/HEAD/templates/img/Schema.png -------------------------------------------------------------------------------- /templates/img/profile.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benfluleck/HelloBooks/HEAD/templates/img/profile.jpg -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [{package.json,.travis.yml}] 4 | indent_style = space 5 | indent_size = 2 6 | -------------------------------------------------------------------------------- /client/src/app/img/404.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benfluleck/HelloBooks/HEAD/client/src/app/img/404.jpg -------------------------------------------------------------------------------- /screenshots/hellobooks-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benfluleck/HelloBooks/HEAD/screenshots/hellobooks-screenshot.png -------------------------------------------------------------------------------- /templates/img/evolution-2-e1427283314331.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benfluleck/HelloBooks/HEAD/templates/img/evolution-2-e1427283314331.jpg -------------------------------------------------------------------------------- /server/src/test/index.spec.js: -------------------------------------------------------------------------------- 1 | require('./books.spec'); 2 | require('./userBooks.spec'); 3 | require('./model.spec'); 4 | require('./categories.spec'); 5 | require('./authenticate.spec'); 6 | require('./notifications.spec'); 7 | require('./user.spec'); 8 | -------------------------------------------------------------------------------- /client/src/app/store/configStore.js: -------------------------------------------------------------------------------- 1 | import configDev from './configStore.dev'; 2 | import configProd from './configStore.prod'; 3 | 4 | if (process.env.NODE_ENV === 'production') { 5 | module.exports = configProd; 6 | } else { 7 | module.exports = configDev; 8 | } 9 | -------------------------------------------------------------------------------- /.coverage.yml: -------------------------------------------------------------------------------- 1 | engines: 2 | duplication: 3 | enabled: false 4 | config: 5 | languages: 6 | javascript: 7 | mass_threshold: 20 8 | ratings: 9 | paths: 10 | - "**.js" 11 | exclude_paths: 12 | - "dist/" 13 | - "node_modules/" 14 | - "api-docs/" -------------------------------------------------------------------------------- /.sequelizerc: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | "config": path.resolve('./server/dist/config', 'config.js'), 5 | "models-path": path.resolve('./server/dist/models'), 6 | "seeders-path": path.resolve('./server/dist/seeders'), 7 | "migrations-path": path.resolve('./server/dist/migrations') 8 | }; 9 | -------------------------------------------------------------------------------- /server/src/controllers/index.js: -------------------------------------------------------------------------------- 1 | import User from './user'; 2 | import Books from './books'; 3 | import UserBooks from './userBooks'; 4 | import Category from './category'; 5 | import Notifications from './notifications'; 6 | 7 | export default { 8 | User, 9 | Books, 10 | UserBooks, 11 | Category, 12 | Notifications 13 | }; 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | */node_modules/* 2 | node_modules/* 3 | node_modules/ 4 | server/config/* 5 | package-lock.json 6 | */coverage/* 7 | */nyc_output/* 8 | coverage 9 | */.nyc_output/* 10 | .coveralls.yml 11 | .env 12 | .nyc_output 13 | npm-debug.log 14 | coverage/ 15 | client/dist 16 | server/dist 17 | .vscode 18 | .DS_Store 19 | reports 20 | logs 21 | -------------------------------------------------------------------------------- /.env.sample: -------------------------------------------------------------------------------- 1 | JWT_SECRET=sampleJWT 2 | DBUSERNAME=sampledb 3 | DBPASSWORD=password 4 | DB=sampledb 5 | DBADDRESS=SampleIpaddress 6 | DBDIALECT=Sampledialect 7 | TESTDB=sampletestdb 8 | ISBNRANDOM_MIN_ID =test_random_id 9 | ISBNRANDOM_MAX_ID=test_random_id 10 | DATABASE_URL=databaseurl 11 | NODE_ENV=environment 12 | DEFAULT_BOOK_COVER=bookimage 13 | PROFILE_PIC=userimage 14 | -------------------------------------------------------------------------------- /nightwatch.config.js: -------------------------------------------------------------------------------- 1 | require('babel-core/register'); 2 | const fs = require('fs'); 3 | 4 | module.exports = ((settings) => { 5 | const seleniumServerFileName = 6 | fs.readdirSync('node_modules/selenium-standalone/.selenium/selenium-server/'); 7 | settings.selenium.server_path += seleniumServerFileName; 8 | return settings; 9 | })(require('./nightwatch.json')); 10 | -------------------------------------------------------------------------------- /client/__tests__/components/utils/setAuthorizationToken.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import setAuthorizationToken from 3 | '../../../src/../src/app/utils/setAuthorizationToken'; 4 | 5 | describe('# Set Authorization Token', () => { 6 | it('should set axios header when token is passed', () => { 7 | setAuthorizationToken('token-is-here'); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /client/__tests__/__mocks__/mockConfig.js: -------------------------------------------------------------------------------- 1 | 2 | import axios from 'axios'; 3 | import configureMockStore from 'redux-mock-store'; 4 | import thunk from 'redux-thunk'; 5 | import MockAdapter from 'axios-mock-adapter'; 6 | 7 | 8 | export const middlewares = [thunk]; 9 | export const mockStore = configureMockStore(middlewares); 10 | 11 | export const mock = new MockAdapter(axios); 12 | 13 | -------------------------------------------------------------------------------- /client/src/app/components/container/header/Header.jsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import Header from '../../presentation/common/header/Header'; 3 | 4 | 5 | const mapStateToProps = state => ({ 6 | 7 | isAuthenticated: !!state.userReducer.isAuthenticated, 8 | tokenExists: !!localStorage.getItem('token') 9 | 10 | }); 11 | 12 | export default connect(mapStateToProps)(Header); 13 | -------------------------------------------------------------------------------- /templates/js/data-toggle.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | $('[data-toggle="tooltip"]').tooltip() 3 | }) 4 | 5 | // Initialize popover component 6 | $(function() { 7 | $('[data-toggle="popover"]').popover() 8 | }) 9 | 10 | $('.img-upload').imgupload({ 11 | 12 | allowedFormats: ["jpg", "jpeg", "png", "gif"], 13 | 14 | previewWidth: 250, 15 | 16 | previewHeight: 250, 17 | 18 | maxFileSizeKb: 2048 19 | 20 | }); -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | engines: 2 | duplication: 3 | enabled: true 4 | config: 5 | languages: 6 | javascript: 7 | mass_threshold: 500 8 | checks: 9 | method-lines: 10 | config: 11 | threshold: 60 12 | complex-logic: 13 | config: 14 | threshold: 8 15 | method-complexity: 16 | config: 17 | threshold: 8 18 | return-statements: 19 | config: 20 | threshold: 12 21 | -------------------------------------------------------------------------------- /client/src/app/components/presentation/messages/WelcomeMessage.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { NavLink } from 'react-router-dom'; 3 | 4 | /** 5 | * @description Component for Welcome Mesage 6 | * 7 | * @class WelcomeMessage 8 | */ 9 | const WelcomeMessage = () => ( 10 |
11 | To Borrow any Books, Please  12 | Login 13 |
14 | ); 15 | export default WelcomeMessage; 16 | -------------------------------------------------------------------------------- /server/src/mailer/mailer.js: -------------------------------------------------------------------------------- 1 | import nodemailer from 'nodemailer'; 2 | 3 | require('dotenv').config(); 4 | 5 | export const transporter = nodemailer.createTransport({ 6 | service: 'gmail', 7 | auth: { 8 | user: process.env.EMAIL_ADDRESS, 9 | pass: process.env.EMAIL_PASSWORD 10 | } 11 | }); 12 | 13 | export const mailOptions = (to, bcc, subject, html) => ({ 14 | from: 'Mailer from library.com', to, bcc, subject, html 15 | }); 16 | -------------------------------------------------------------------------------- /server/src/cron/index.js: -------------------------------------------------------------------------------- 1 | import { CronJob } from 'cron'; 2 | import sendSurcharge from './sendSurcharge'; 3 | 4 | export const setCron = props => new CronJob(props); 5 | 6 | 7 | export const sendSurchargeJob = () => 8 | setCron({ 9 | cronTime: '05 17 * * 1-7', 10 | onTick: sendSurcharge, 11 | timeZone: 'Africa/Lagos', 12 | start: true 13 | }); 14 | 15 | if (require.main === module) { 16 | sendSurchargeJob(); 17 | } 18 | 19 | -------------------------------------------------------------------------------- /client/src/app/components/container/LandingPage.jsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import PropTypes from 'prop-types'; 3 | import LandingPage from '../presentation/LandingPage'; 4 | 5 | LandingPage.PropTypes = { 6 | isAuthenticated: PropTypes.bool.isRequired 7 | }; 8 | 9 | const mapStateToProps = state => ({ 10 | isAuthenticated: !!state.userReducer.isAuthenticated 11 | }); 12 | 13 | export default connect(mapStateToProps)(LandingPage); 14 | -------------------------------------------------------------------------------- /client/src/app/components/presentation/messages/dashboardMessages/MessageforNoOverdueBooks.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | 4 | /** 5 | * @description Component for Message for No Overdue Books 6 | * 7 | * @class MessageforNoOverdueBooks 8 | */ 9 | const MessageforNoOverdueBooks = () => ( 10 |

11 | You have no overdue books at present 12 |

13 | ); 14 | export default MessageforNoOverdueBooks; 15 | -------------------------------------------------------------------------------- /client/src/app/components/presentation/common/Preloader/ShowProgressBar.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Col, Row, ProgressBar } from 'react-materialize'; 3 | 4 | /** 5 | * @description Component for Progress Bar, 6 | * 7 | * @class ShowProgressBar 8 | */ 9 | const ShowProgressBar = () => ( 10 | 11 | 12 | Loading.... 13 | 14 | 15 | 16 | ); 17 | 18 | export default ShowProgressBar; 19 | -------------------------------------------------------------------------------- /client/src/app/components/container/booklist/Loader.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Preloader } from 'react-materialize'; 4 | 5 | const Loader = ({ records, callback }) => { 6 | if (!records) { 7 | return (); 8 | } 9 | callback(); 10 | }; 11 | 12 | Loader.propTypes = { 13 | records: PropTypes.object, 14 | callback: PropTypes.oneOfType([ 15 | PropTypes.object 16 | ]) 17 | }; 18 | 19 | export default (Loader); 20 | -------------------------------------------------------------------------------- /client/src/app/components/presentation/messages/dashboardMessages/MessageforNoBooks.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Row } from 'react-materialize'; 3 | 4 | 5 | /** 6 | * @description Component for Message for No Books 7 | * 8 | * @class MessageforNoBooks 9 | */ 10 | const MessageforNoBooks = () => ( 11 | 12 |

13 | You have not borrowed any books. 14 |

15 |
16 | ); 17 | export default MessageforNoBooks; 18 | -------------------------------------------------------------------------------- /client/src/app/components/presentation/messages/dashboardMessages/NoNotifications.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Row } from 'react-materialize'; 3 | 4 | 5 | /** 6 | * @description Component for No notifications 7 | * 8 | * @class NoNotificationsMessage 9 | */ 10 | const NoNotificationsMessage = () => ( 11 | 12 |

13 | No Notifications Found. 14 |

15 |
16 | ); 17 | export default NoNotificationsMessage; 18 | -------------------------------------------------------------------------------- /client/src/app/components/presentation/messages/dashboardMessages/MessageforNoCatBooks.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Row } from 'react-materialize'; 3 | 4 | /** 5 | * @description Component for Message for No Loan History 6 | * 7 | * @class MessageforNoBooksHistory 8 | */ 9 | const MessageforNoCatBooks = () => ( 10 | 11 |

12 | There are no books in this category 13 |

14 |
15 | ); 16 | export default MessageforNoCatBooks; 17 | -------------------------------------------------------------------------------- /client/src/app/components/Root.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Header from '../components/container/header/Header'; 3 | 4 | import UploadModal from 5 | '../components/presentation/common/modal/UploadModal'; 6 | import DisplayBookModal from './presentation/common/book/DisplayBookModal'; 7 | 8 | 9 | const Root = props => ( 10 |
11 | 12 |
13 | {props.children} 14 | 15 | 16 | 17 |
18 | ); 19 | 20 | 21 | export default Root; 22 | -------------------------------------------------------------------------------- /client/src/app/utils/setAuthorizationToken.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | /** 4 | * set or remove authorization token on request headers 5 | * 6 | * @param {String} token authorization 7 | * 8 | * @returns {Undefined} sets token on request header 9 | */ 10 | const setAuthorizationToken = (token) => { 11 | if (token) { 12 | axios.defaults.headers.common['x-access-token'] = token; 13 | } else { 14 | delete axios.defaults.headers.common['x-access-token']; 15 | } 16 | }; 17 | 18 | export default setAuthorizationToken; 19 | 20 | -------------------------------------------------------------------------------- /server/src/models/categories.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export default(sequelize, DataTypes) => { 4 | const Categories = sequelize.define('Categories', { 5 | categoryName: { 6 | type: DataTypes.STRING, 7 | allowNull: { 8 | args: false, 9 | msg: 'Please enter a category name' 10 | } 11 | } 12 | }, { paranoid: true }); 13 | Categories.associate = (models) => { 14 | Categories.hasMany(models.Books, { 15 | foreignKey: 'categoryId', 16 | as: 'books' 17 | }); 18 | }; 19 | return Categories; 20 | }; 21 | -------------------------------------------------------------------------------- /client/__tests__/components/presentation/common/Footer.spec.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | 4 | import SiteFooter from 5 | '../../../../src/app/components/presentation/common/Footer'; 6 | 7 | const props = {}; 8 | 9 | 10 | describe('Footer Component', () => { 11 | const setup = () => shallow(); 12 | 13 | it('should render without throwing an error', () => { 14 | const wrapper = setup(); 15 | expect(wrapper).toBeDefined(); 16 | expect(wrapper.find('Footer')).toHaveLength(1); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /client/src/app/components/presentation/messages/dashboardMessages/MessageforNoBooksHistory.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Row } from 'react-materialize'; 3 | 4 | /** 5 | * @description Component for Message for No Loan History 6 | * 7 | * @class MessageforNoBooksHistory 8 | */ 9 | const MessageforNoBooksHistory = () => ( 10 | 11 |

12 | You have no loan history. Go and search our collection and loan some books 13 |

14 |
15 | ); 16 | export default MessageforNoBooksHistory; 17 | 18 | -------------------------------------------------------------------------------- /client/src/app/reducers/rootReducers.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { reducer as notifReducer } from 'redux-notifications'; 3 | 4 | import userReducer from './userReducers'; 5 | import bookReducer from './bookReducers'; 6 | import imageReducer from './imageReducers'; 7 | import notifierReducer from './notifierReducers'; 8 | import categoryReducer from './categoryReducers'; 9 | 10 | 11 | export default combineReducers({ 12 | notifierReducer, 13 | userReducer, 14 | bookReducer, 15 | imageReducer, 16 | categoryReducer, 17 | notifs: notifReducer 18 | }); 19 | -------------------------------------------------------------------------------- /client/src/app/store/configStore.prod.js: -------------------------------------------------------------------------------- 1 | import throttle from 'lodash/throttle'; 2 | 3 | import { createStore, applyMiddleware } from 'redux'; 4 | import thunk from 'redux-thunk'; 5 | import rootReducer from '../reducers/rootReducers'; 6 | import { saveState, loadState } from '../utils/localSave'; 7 | 8 | const initialState = loadState(); 9 | const store = createStore( 10 | rootReducer, 11 | initialState, 12 | (applyMiddleware(thunk)) 13 | ); 14 | 15 | 16 | store.subscribe(throttle(() => { 17 | saveState(store.getState()); 18 | }), 1000); 19 | 20 | 21 | export default store; 22 | -------------------------------------------------------------------------------- /server/src/models/notifications.js: -------------------------------------------------------------------------------- 1 | export default (sequelize, DataTypes) => { 2 | const Notifications = sequelize.define('Notifications', { 3 | userId: DataTypes.INTEGER, 4 | bookId: DataTypes.INTEGER, 5 | action: DataTypes.STRING 6 | }); 7 | 8 | Notifications.associate = (models) => { 9 | Notifications.belongsTo(models.Books, { 10 | as: 'book', 11 | foreignKey: 'bookId', 12 | }); 13 | Notifications.belongsTo(models.User, { 14 | as: 'user', 15 | foreignKey: 'userId', 16 | onDelete: 'CASCADE', 17 | }); 18 | }; 19 | return Notifications; 20 | }; 21 | -------------------------------------------------------------------------------- /client/__tests__/components/presentation/messages/MessageforNoBooks.spec.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | /* eslint-disable */ 4 | import MessageforNoBooks from 5 | '../../../../src/app/components/presentation/messages/dashboardMessages/MessageforNoBooks'; 6 | 7 | 8 | describe('MessageforNoBooks/>', () => { 9 | it('renders a MessageforNoBooks without crashing', () => { 10 | const setup = () => shallow(); 11 | const wrapper = setup(); 12 | expect(wrapper.find('Row').length).toBe(1); 13 | expect(wrapper.length).toBe(1); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /client/__tests__/components/presentation/messages/MessageforNoBooksHistory.spec.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import MessageforNoBooksHistory from 4 | '../../../../src/app/components/presentation/messages/dashboardMessages/MessageforNoBooksHistory'; 5 | 6 | 7 | describe('MessageforNoBooks/>', () => { 8 | it('renders a MessageforNoBooksHistory without crashing', () => { 9 | const setup = () => shallow(); 10 | const wrapper = setup(); 11 | expect(wrapper.find('Row').length).toBe(1); 12 | expect(wrapper.length).toBe(1); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /client/__tests__/components/presentation/messages/MessageforNoCatBooks.spec.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | /* eslint-disable */ 4 | import MessageforNoCatBooks from 5 | '../../../../src/app/components/presentation/messages/dashboardMessages/MessageforNoCatBooks'; 6 | 7 | 8 | describe('MessageforNoBooks/>', () => { 9 | it('renders a MessageforNoBooks without crashing', () => { 10 | const setup = () => shallow(); 11 | const wrapper = setup(); 12 | expect(wrapper.find('Row').length).toBe(1); 13 | expect(wrapper.length).toBe(1); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /server/src/controllers/helpers/pagination.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @param {number} offset - offset number 4 | * 5 | * @param {number} limit - limit number 6 | * 7 | * @param {object} books 8 | * 9 | * @returns {object} getPagination - with the limit and offset fields 10 | * to query database 11 | */ 12 | const pagination = (offset, limit, books) => { 13 | const getPagination = { 14 | page: Math.floor(offset / limit) + 1, 15 | pageCount: Math.ceil(books.count / limit), 16 | pageSize: books.rows.length, 17 | totalCount: books.count 18 | }; 19 | return getPagination; 20 | }; 21 | 22 | export default pagination; 23 | -------------------------------------------------------------------------------- /client/src/app/components/presentation/common/Footer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Footer } from 'react-materialize'; 3 | 4 | /** 5 | * @description Component for Footer, 6 | * 7 | * named Bottom so as not to conflict with materialize footer 8 | * @class Bottom 9 | */ 10 | const SiteFooter = () => ( 11 |