├── .gitignore ├── client ├── src │ ├── components │ │ ├── Forms │ │ │ ├── LoginForm.js │ │ │ ├── RegisterForm.js │ │ │ ├── SearchBar.js │ │ │ ├── ListingForm.js │ │ │ └── AdvancedSearchForm.js │ │ ├── Layout │ │ │ ├── Spinner.gif │ │ │ ├── Footer.js │ │ │ ├── Spinner.js │ │ │ ├── ImageGallery.js │ │ │ ├── PageNotFound.js │ │ │ ├── Notification.js │ │ │ ├── Countdown.js │ │ │ ├── PaginationButtons.js │ │ │ └── Navbar.js │ │ ├── Listing │ │ │ ├── ListingsCardRow.js │ │ │ ├── ListingCard.js │ │ │ ├── EditListingPage.js │ │ │ ├── ListingPage.js │ │ │ └── CreateListingPage.js │ │ ├── Routing │ │ │ ├── PrivateRoute.js │ │ │ └── Routes.js │ │ ├── Listings │ │ │ ├── ListItem.js │ │ │ └── ListingsPage.js │ │ ├── Bids │ │ │ ├── BidItem.js │ │ │ └── ViewBidsModal.js │ │ ├── Dashboard │ │ │ ├── Dashboard.js │ │ │ ├── YourReviewsPage.js │ │ │ ├── YourListingsPage.js │ │ │ └── BiddingHistoryPage.js │ │ ├── Profile │ │ │ ├── ViewReviewsModal.js │ │ │ ├── ProfilePage.js │ │ │ └── EditProfilePage.js │ │ ├── Homepage │ │ │ └── HomePage.js │ │ ├── Auth │ │ │ ├── LoginPage.js │ │ │ └── RegisterPage.js │ │ ├── Reviews │ │ │ ├── ReviewItem.js │ │ │ ├── CreateReviewModal.js │ │ │ └── EditReviewModal.js │ │ └── Report │ │ │ └── CreateReportModal.js │ ├── styles │ │ ├── components │ │ │ ├── _create-listing-page.scss │ │ │ ├── _footer.scss │ │ │ ├── _modal.scss │ │ │ ├── _forms.scss │ │ │ ├── _profile-page.scss │ │ │ ├── _buttons.scss │ │ │ ├── _listings-page.scss │ │ │ ├── _navbar.scss │ │ │ └── _listing-page.scss │ │ ├── base │ │ │ ├── _reset.scss │ │ │ └── _typography.scss │ │ └── utilities │ │ │ └── _variables.scss │ ├── index.js │ ├── actions │ │ ├── imageActions.js │ │ ├── notification.js │ │ ├── report.js │ │ ├── user.js │ │ ├── types.js │ │ ├── auth.js │ │ ├── review.js │ │ └── listing.js │ ├── setupTests.js │ ├── utils │ │ └── setAuthToken.js │ ├── store.js │ ├── reducers │ │ ├── index.js │ │ ├── notification.js │ │ ├── user.js │ │ ├── review.js │ │ ├── listing.js │ │ ├── listings.js │ │ ├── auth.js │ │ └── reviews.js │ ├── App.scss │ └── App.js ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── manifest.json │ └── index.html └── package.json ├── utils ├── catchAsync.js └── appError.js ├── routes └── api │ ├── reportRouter.js │ ├── authRouter.js │ ├── userRouter.js │ ├── reviewRouter.js │ └── listingRouter.js ├── models ├── reportModel.js ├── userModel.js ├── listingModel.js └── reviewModel.js ├── config └── db.js ├── controllers ├── reportController.js ├── handlerFactory.js ├── authController.js ├── reviewController.js ├── errorController.js ├── userController.js └── listingController.js ├── README.md ├── tests └── backend │ ├── report.test.js │ ├── review.test.js │ ├── auth.test.js │ ├── user.test.js │ └── listing.test.js ├── server.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | env -------------------------------------------------------------------------------- /client/src/components/Forms/LoginForm.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/components/Forms/RegisterForm.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/styles/components/_create-listing-page.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /client/src/styles/base/_reset.scss: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | } 6 | -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JarrodMalkovic/auction-website-monolith/HEAD/client/public/favicon.ico -------------------------------------------------------------------------------- /utils/catchAsync.js: -------------------------------------------------------------------------------- 1 | module.exports = fn => { 2 | return (req, res, next) => { 3 | fn(req, res, next).catch(next); 4 | }; 5 | }; 6 | -------------------------------------------------------------------------------- /client/src/components/Layout/Spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JarrodMalkovic/auction-website-monolith/HEAD/client/src/components/Layout/Spinner.gif -------------------------------------------------------------------------------- /client/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render(, document.getElementById('root')); 6 | -------------------------------------------------------------------------------- /client/src/actions/imageActions.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | export const addImage = imageData => async dispatch => { 4 | const res = await axios.post('/api/listings/upload/image', imageData); 5 | console.log(res); 6 | }; 7 | -------------------------------------------------------------------------------- /client/src/actions/notification.js: -------------------------------------------------------------------------------- 1 | import { ADD_NOTIFICATION } from './types'; 2 | 3 | export function addNotification(message, level) { 4 | return { 5 | type: ADD_NOTIFICATION, 6 | message, 7 | level 8 | }; 9 | } 10 | -------------------------------------------------------------------------------- /client/src/styles/utilities/_variables.scss: -------------------------------------------------------------------------------- 1 | // Sizes 2 | $xl-size: 5rem; 3 | $l-size: 3rem; 4 | $m-size: 2rem; 5 | $s-size: 1.2rem; 6 | 7 | // Colours 8 | $gray: #46474a; 9 | $dark-gray: #38393b; 10 | $light-gray: #68696b; 11 | $black: #000; 12 | $white: #fff; 13 | -------------------------------------------------------------------------------- /client/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect'; 6 | -------------------------------------------------------------------------------- /client/src/utils/setAuthToken.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | export const setAuthToken = token => { 4 | if (token) { 5 | console.log('setting'); 6 | axios.defaults.headers.common['x-auth-token'] = token; 7 | } else { 8 | delete axios.defaults.headers.common['x-auth-token']; 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /utils/appError.js: -------------------------------------------------------------------------------- 1 | class AppError extends Error { 2 | constructor(message, statusCode) { 3 | super(message); 4 | this.statusCode = statusCode; 5 | this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error'; 6 | this.isOperational = true; 7 | Error.captureStackTrace(this, this.constructor); 8 | } 9 | } 10 | 11 | module.exports = AppError; 12 | -------------------------------------------------------------------------------- /client/src/styles/components/_footer.scss: -------------------------------------------------------------------------------- 1 | footer { 2 | background-color: $light-gray; 3 | color: $white; 4 | padding: 4rem; 5 | position: absolute; 6 | left: 0; 7 | bottom: 0; 8 | width: 100%; 9 | overflow: hidden; 10 | margin-top: $m-size; 11 | text-align: center; 12 | } 13 | 14 | html { 15 | position: relative; 16 | min-height: 100%; 17 | } 18 | 19 | body { 20 | margin: 0 0 11rem; 21 | } 22 | -------------------------------------------------------------------------------- /client/src/components/Layout/Footer.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | const Footer = props => { 5 | return ( 6 | 12 | ); 13 | }; 14 | 15 | Footer.propTypes = {}; 16 | 17 | export default Footer; 18 | -------------------------------------------------------------------------------- /client/src/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware } from 'redux'; 2 | import { composeWithDevTools } from 'redux-devtools-extension'; 3 | import thunk from 'redux-thunk'; 4 | import rootReducer from './reducers/index'; 5 | 6 | const initialState = {}; 7 | 8 | const middleware = [thunk]; 9 | 10 | const store = createStore( 11 | rootReducer, 12 | initialState, 13 | composeWithDevTools(applyMiddleware(...middleware)) 14 | ); 15 | 16 | export default store; 17 | -------------------------------------------------------------------------------- /client/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import auth from './auth'; 3 | import user from './user'; 4 | import reviews from './reviews'; 5 | import review from './review'; 6 | import listings from './listings'; 7 | import listing from './listing'; 8 | import notification from './notification'; 9 | 10 | export default combineReducers({ 11 | auth, 12 | listings, 13 | listing, 14 | user, 15 | reviews, 16 | review, 17 | notification 18 | }); 19 | -------------------------------------------------------------------------------- /client/src/reducers/notification.js: -------------------------------------------------------------------------------- 1 | import { ADD_NOTIFICATION } from '../actions/types'; 2 | 3 | export default function notification(state = {}, action) { 4 | switch (action.type) { 5 | case ADD_NOTIFICATION: 6 | return Object.assign({}, state, { 7 | message: action.message, 8 | level: action.level 9 | }); 10 | 11 | default: 12 | console.debug('notification reducer :: hit default', action.type); 13 | return state; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /client/src/styles/components/_modal.scss: -------------------------------------------------------------------------------- 1 | .modal { 2 | margin: 75px auto; 3 | padding: 30px 30px; 4 | max-height: 80vh; 5 | overflow-y: auto; 6 | border-radius: 15px; 7 | background: white; 8 | max-width: 102.4rem; 9 | word-wrap: break-word; 10 | 11 | box-shadow: 0 0 30px 0 rgba(0, 0, 0, 0.25); 12 | } 13 | 14 | .overlay { 15 | position: absolute; 16 | top: 0; 17 | left: 0; 18 | right: 0; 19 | bottom: 0; 20 | background-color: rgba(41, 41, 41, 0.8); 21 | } 22 | -------------------------------------------------------------------------------- /client/src/components/Layout/Spinner.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from 'react'; 2 | import SpinnerGif from './Spinner.gif'; 3 | import { Helmet } from 'react-helmet'; 4 | 5 | const Spinner = () => { 6 | return ( 7 | 8 | 9 | Loading.. | Auction 10 | 11 |
12 | Loading 13 |
14 |
15 | ); 16 | }; 17 | 18 | export default Spinner; 19 | -------------------------------------------------------------------------------- /routes/api/reportRouter.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | 4 | const reportController = require('../../controllers/reportController'); 5 | const authController = require('../../controllers/authController'); 6 | 7 | // @route POST api/report/:id 8 | // @desc Create a report for a user, review or listing 9 | // @access Private 10 | router.post('/:id', authController.authenticate, reportController.createReport); 11 | 12 | router.get('/', reportController.getReports); 13 | module.exports = router; 14 | -------------------------------------------------------------------------------- /client/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /client/src/components/Listing/ListingsCardRow.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import ListingCard from './ListingCard'; 4 | 5 | const ListingsCardRow = ({ listings }) => { 6 | console.log(listings); 7 | return listings === null ? ( 8 |
Loading..
9 | ) : ( 10 |
11 | {listings.map(listing => ( 12 | 17 | ))} 18 |
19 | ); 20 | }; 21 | 22 | ListingsCardRow.propTypes = {}; 23 | 24 | export default ListingsCardRow; 25 | -------------------------------------------------------------------------------- /client/src/components/Layout/ImageGallery.js: -------------------------------------------------------------------------------- 1 | import ImageGallery from 'react-image-gallery'; 2 | 3 | const images = [ 4 | { 5 | original: 'https://picsum.photos/id/1018/1000/600/', 6 | thumbnail: 'https://picsum.photos/id/1018/250/150/' 7 | }, 8 | { 9 | original: 'https://picsum.photos/id/1015/1000/600/', 10 | thumbnail: 'https://picsum.photos/id/1015/250/150/' 11 | }, 12 | { 13 | original: 'https://picsum.photos/id/1019/1000/600/', 14 | thumbnail: 'https://picsum.photos/id/1019/250/150/' 15 | } 16 | ]; 17 | 18 | class MyGallery extends React.Component { 19 | render() { 20 | return ; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /client/src/styles/components/_forms.scss: -------------------------------------------------------------------------------- 1 | .form-group { 2 | margin-top: $m-size; 3 | margin-bottom: $m-size; 4 | } 5 | 6 | .form .form-group > input[type='text'], 7 | input[type='email'], 8 | input[type='password'], 9 | input[type='textarea'], 10 | input[type='number'], 11 | textarea { 12 | width: 100%; 13 | padding: $s-size $s-size; 14 | margin: 8px 0; 15 | box-sizing: border-box; 16 | border-radius: 4px; 17 | border: 1px solid #ccc; 18 | } 19 | 20 | textarea { 21 | height: 15rem; 22 | color: #555; 23 | font-family: 'Lato'; 24 | } 25 | .form { 26 | padding-top: $m-size; 27 | } 28 | 29 | .recaptcha-container, 30 | select { 31 | margin-top: $s-size; 32 | } 33 | -------------------------------------------------------------------------------- /models/reportModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const ReportSchema = new mongoose.Schema({ 4 | reportedBy: { 5 | type: mongoose.Schema.ObjectId, 6 | ref: 'user', 7 | required: true 8 | }, 9 | reported: { 10 | type: mongoose.Schema.ObjectId, 11 | required: true, 12 | refPath: 'reportedRef' 13 | }, 14 | reason: { 15 | type: String, 16 | required: true 17 | }, 18 | reportedRef: { 19 | type: String, 20 | required: true, 21 | enum: ['user', 'Listing', 'review'] 22 | }, 23 | date: { 24 | type: Date, 25 | default: Date.now() 26 | } 27 | }); 28 | 29 | module.exports = Report = mongoose.model('report', ReportSchema); 30 | -------------------------------------------------------------------------------- /client/src/actions/report.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { addNotification } from './notification'; 3 | 4 | export const createReport = (id, reason, reportedRef) => async dispatch => { 5 | try { 6 | const config = { 7 | headers: { 8 | 'Content-Type': 'application/json' 9 | } 10 | }; 11 | 12 | const body = { 13 | reason, 14 | reportedRef 15 | }; 16 | 17 | await axios.post(`/api/report/${id}`, body, config); 18 | dispatch(addNotification('Report Created Successfully!', 'success')); 19 | } catch (err) { 20 | console.log(`Error: ${err.response.data.message}`); 21 | dispatch(addNotification(err.response.data.message, 'error')); 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /client/src/styles/components/_profile-page.scss: -------------------------------------------------------------------------------- 1 | .profile-top { 2 | background-color: #68696b; 3 | color: #ffffff; 4 | height: 55rem; 5 | text-align: center; 6 | margin-bottom: 15px; 7 | } 8 | 9 | .profile-top > * { 10 | margin: 15px; 11 | color: white; 12 | } 13 | 14 | .round-image { 15 | border-radius: 50%; 16 | } 17 | 18 | .review-item { 19 | padding-top: 20px; 20 | } 21 | 22 | .see-more { 23 | float: right; 24 | } 25 | 26 | .listing-card-title { 27 | h3 { 28 | float: left; 29 | } 30 | } 31 | 32 | .link h3, 33 | .link h4 { 34 | color: #293241; 35 | font-size: 100%; 36 | text-decoration: none; 37 | } 38 | 39 | .small { 40 | padding: 5px; 41 | } 42 | 43 | .right { 44 | float: right; 45 | } 46 | -------------------------------------------------------------------------------- /client/src/components/Layout/PageNotFound.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Helmet } from 'react-helmet'; 4 | 5 | const PageNotFound = props => { 6 | return ( 7 |
8 | 9 | Page not found! | Auction 10 | 11 |
12 |

404: Page Not Found

13 |

14 | Sorry, but the page you are looking for was not found. Please make 15 | sure you have typed the current URL correctly. 16 |

17 |
18 |
19 | ); 20 | }; 21 | 22 | PageNotFound.propTypes = {}; 23 | 24 | export default PageNotFound; 25 | -------------------------------------------------------------------------------- /config/db.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const dotenv = require('dotenv').config({ path: './config.env' }); 3 | 4 | const connectDB = async () => { 5 | try { 6 | if (process.env.NODE_ENV === 'test') { 7 | var dbName = 'test'; 8 | } else { 9 | var dbName = 'Production'; 10 | } 11 | 12 | await mongoose.connect(process.env.MONGO_URI, { 13 | useNewUrlParser: true, 14 | useUnifiedTopology: true, 15 | useCreateIndex: true, 16 | useFindAndModify: false, 17 | dbName 18 | }); 19 | 20 | console.log(`MongoDB Connected to ${dbName} DB...`); 21 | } catch (err) { 22 | console.log(err.message); 23 | process.exit(1); 24 | } 25 | }; 26 | 27 | module.exports = connectDB; 28 | -------------------------------------------------------------------------------- /client/src/components/Routing/PrivateRoute.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route, Redirect } from 'react-router-dom'; 3 | import PropTypes from 'prop-types'; 4 | import { connect } from 'react-redux'; 5 | 6 | const PrivateRoute = ({ 7 | component: Component, 8 | auth: { isAuthenticated, loading }, 9 | ...rest 10 | }) => ( 11 | 14 | !isAuthenticated && !loading ? ( 15 | 16 | ) : ( 17 | 18 | ) 19 | } 20 | /> 21 | ); 22 | 23 | PrivateRoute.propTypes = { 24 | auth: PropTypes.object.isRequired 25 | }; 26 | 27 | const mapStateToProps = state => ({ 28 | auth: state.auth 29 | }); 30 | 31 | export default connect(mapStateToProps)(PrivateRoute); 32 | -------------------------------------------------------------------------------- /client/src/reducers/user.js: -------------------------------------------------------------------------------- 1 | import { GET_USER, CLEAR_USER, USER_ERROR } from '../actions/types'; 2 | 3 | const initialState = { 4 | data: null, 5 | loading: true, 6 | errors: null 7 | }; 8 | 9 | export default function(state = initialState, action) { 10 | const { type, payload } = action; 11 | switch (type) { 12 | case GET_USER: { 13 | return { ...state, data: payload, loading: false }; 14 | } 15 | case CLEAR_USER: { 16 | return { 17 | ...state, 18 | data: null, 19 | errors: null, 20 | loading: true 21 | }; 22 | } 23 | case USER_ERROR: 24 | return { 25 | ...state, 26 | data: [], 27 | loading: false, 28 | errors: 'ERROR' 29 | }; 30 | default: 31 | return state; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /client/src/App.scss: -------------------------------------------------------------------------------- 1 | // Utilities 2 | @import './styles/utilities/_variables.scss'; 3 | 4 | // Base Rules 5 | @import './styles/base/_reset.scss'; 6 | @import './styles/base/_typography.scss'; 7 | 8 | // Components 9 | @import './styles/components/_listings-page.scss'; 10 | @import './styles/components/_navbar.scss'; 11 | @import './styles/components/_modal.scss'; 12 | @import './styles/components/_forms.scss'; 13 | @import './styles/components/_footer.scss'; 14 | @import './styles/components/_profile-page.scss'; 15 | @import './styles/components/_create-listing-page.scss'; 16 | @import './styles/components/_listing-page.scss'; 17 | @import './styles/components/_buttons.scss'; 18 | 19 | // Third Party Components 20 | @import '~react-image-gallery/styles/scss/image-gallery.scss'; 21 | @import '~react-notifications/lib/notifications.css'; 22 | -------------------------------------------------------------------------------- /client/src/reducers/review.js: -------------------------------------------------------------------------------- 1 | import { GET_REVIEW, REVIEW_ERROR, CLEAR_REVIEW } from '../actions/types'; 2 | 3 | const initialState = { 4 | data: null, 5 | loading: true, 6 | errors: null 7 | }; 8 | 9 | export default function(state = initialState, action) { 10 | const { type, payload } = action; 11 | switch (type) { 12 | case GET_REVIEW: 13 | return { 14 | ...state, 15 | data: payload, 16 | loading: false 17 | }; 18 | case REVIEW_ERROR: 19 | return { 20 | ...state, 21 | data: [], 22 | loading: false, 23 | errors: 'ERROR' 24 | }; 25 | case CLEAR_REVIEW: 26 | return { 27 | ...state, 28 | data: null, 29 | loading: true, 30 | errors: null 31 | }; 32 | default: 33 | return state; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /routes/api/authRouter.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | 4 | const authController = require('../../controllers/authController'); 5 | const userController = require('../../controllers/userController'); 6 | 7 | // @route GET api/auth 8 | // @desc Get user by token 9 | // @access Private 10 | router.get('/', authController.authenticate, userController.getMyProfile); 11 | 12 | // @route POST api/auth 13 | // @desc Authenticate user & get token 14 | // @access Public 15 | router.post('/', authController.loginUser); 16 | 17 | // @route POST api/auth/update-password 18 | // @desc Update user password 19 | // @access Private 20 | router.patch( 21 | '/update-password', 22 | authController.authenticate, 23 | authController.updatePassword 24 | ); 25 | 26 | module.exports = router; 27 | -------------------------------------------------------------------------------- /models/userModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const UserSchema = new mongoose.Schema({ 4 | name: { 5 | type: String, 6 | required: true 7 | }, 8 | email: { 9 | type: String, 10 | required: true, 11 | unique: true 12 | }, 13 | password: { 14 | type: String, 15 | required: true 16 | }, 17 | avatar: { 18 | type: String 19 | }, 20 | ratingsAverage: { 21 | type: Number, 22 | default: 4.5, 23 | min: [1, 'Rating must be above 1.0'], 24 | max: [5, 'Rating must be below 5.0'], 25 | set: val => Math.round(val * 10) / 10 26 | }, 27 | date: { 28 | type: Date, 29 | default: Date.now 30 | }, 31 | location: { 32 | type: String 33 | }, 34 | bio: { 35 | type: String 36 | } 37 | }); 38 | 39 | module.exports = User = mongoose.model('user', UserSchema); 40 | -------------------------------------------------------------------------------- /client/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from 'react'; 2 | import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; 3 | import Routes from './components/Routing/Routes'; 4 | import Navbar from './components/Layout/Navbar'; 5 | import Footer from './components/Layout/Footer'; 6 | import Notification from './components/Layout/Notification'; 7 | import './App.scss'; 8 | import { Provider } from 'react-redux'; 9 | import store from './store'; 10 | 11 | const App = () => { 12 | return ( 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |