├── Chapter02 └── mern-simplesetup │ ├── .babelrc │ ├── LICENSE.md │ ├── README.md │ ├── client │ ├── HelloWorld.js │ └── main.js │ ├── nodemon.json │ ├── package-lock.json │ ├── package.json │ ├── server │ ├── devBundle.js │ └── server.js │ ├── template.js │ ├── webpack.config.client.js │ ├── webpack.config.client.production.js │ └── webpack.config.server.js ├── Chapter03 and 04 └── mern-skeleton │ ├── .babelrc │ ├── LICENSE.md │ ├── README.md │ ├── client │ ├── App.js │ ├── MainRouter.js │ ├── assets │ │ └── images │ │ │ └── seashell.jpg │ ├── auth │ │ ├── PrivateRoute.js │ │ ├── Signin.js │ │ ├── api-auth.js │ │ └── auth-helper.js │ ├── core │ │ ├── Home.js │ │ └── Menu.js │ ├── main.js │ └── user │ │ ├── DeleteUser.js │ │ ├── EditProfile.js │ │ ├── Profile.js │ │ ├── Signup.js │ │ ├── Users.js │ │ └── api-user.js │ ├── config │ └── config.js │ ├── nodemon.json │ ├── package-lock.json │ ├── package.json │ ├── server │ ├── controllers │ │ ├── auth.controller.js │ │ └── user.controller.js │ ├── devBundle.js │ ├── express.js │ ├── helpers │ │ └── dbErrorHandler.js │ ├── models │ │ └── user.model.js │ ├── routes │ │ ├── auth.routes.js │ │ └── user.routes.js │ └── server.js │ ├── template.js │ ├── webpack.config.client.js │ ├── webpack.config.client.production.js │ └── webpack.config.server.js ├── Chapter05 └── mern-social │ ├── .babelrc │ ├── LICENSE.md │ ├── README.md │ ├── client │ ├── App.js │ ├── MainRouter.js │ ├── assets │ │ └── images │ │ │ ├── profile-pic.png │ │ │ └── seashell.jpg │ ├── auth │ │ ├── PrivateRoute.js │ │ ├── Signin.js │ │ ├── api-auth.js │ │ └── auth-helper.js │ ├── core │ │ ├── Home.js │ │ └── Menu.js │ ├── main.js │ ├── post │ │ ├── Comments.js │ │ ├── NewPost.js │ │ ├── Newsfeed.js │ │ ├── Post.js │ │ ├── PostList.js │ │ └── api-post.js │ └── user │ │ ├── DeleteUser.js │ │ ├── EditProfile.js │ │ ├── FindPeople.js │ │ ├── FollowGrid.js │ │ ├── FollowProfileButton.js │ │ ├── Profile.js │ │ ├── ProfileTabs.js │ │ ├── Signup.js │ │ ├── Users.js │ │ └── api-user.js │ ├── config │ └── config.js │ ├── nodemon.json │ ├── package-lock.json │ ├── package.json │ ├── server │ ├── controllers │ │ ├── auth.controller.js │ │ ├── post.controller.js │ │ └── user.controller.js │ ├── devBundle.js │ ├── express.js │ ├── helpers │ │ └── dbErrorHandler.js │ ├── models │ │ ├── post.model.js │ │ └── user.model.js │ ├── routes │ │ ├── auth.routes.js │ │ ├── post.routes.js │ │ └── user.routes.js │ └── server.js │ ├── template.js │ ├── webpack.config.client.js │ ├── webpack.config.client.production.js │ └── webpack.config.server.js ├── Chapter06 and 07 └── mern-marketplace │ ├── .babelrc │ ├── LICENSE.md │ ├── README.md │ ├── client │ ├── App.js │ ├── MainRouter.js │ ├── assets │ │ └── images │ │ │ ├── profile-pic.png │ │ │ ├── seashell.jpg │ │ │ └── stripeButton.png │ ├── auth │ │ ├── PrivateRoute.js │ │ ├── Signin.js │ │ ├── api-auth.js │ │ └── auth-helper.js │ ├── cart │ │ ├── AddToCart.js │ │ ├── Cart.js │ │ ├── CartItems.js │ │ ├── Checkout.js │ │ ├── PlaceOrder.js │ │ └── cart-helper.js │ ├── core │ │ ├── Home.js │ │ └── Menu.js │ ├── main.js │ ├── order │ │ ├── MyOrders.js │ │ ├── Order.js │ │ ├── ProductOrderEdit.js │ │ ├── ShopOrders.js │ │ └── api-order.js │ ├── product │ │ ├── Categories.js │ │ ├── DeleteProduct.js │ │ ├── EditProduct.js │ │ ├── MyProducts.js │ │ ├── NewProduct.js │ │ ├── Product.js │ │ ├── Products.js │ │ ├── Search.js │ │ ├── Suggestions.js │ │ └── api-product.js │ ├── shop │ │ ├── DeleteShop.js │ │ ├── EditShop.js │ │ ├── MyShops.js │ │ ├── NewShop.js │ │ ├── Shop.js │ │ ├── Shops.js │ │ └── api-shop.js │ └── user │ │ ├── DeleteUser.js │ │ ├── EditProfile.js │ │ ├── Profile.js │ │ ├── Signup.js │ │ ├── StripeConnect.js │ │ ├── Users.js │ │ └── api-user.js │ ├── config │ └── config.js │ ├── nodemon.json │ ├── package-lock.json │ ├── package.json │ ├── server │ ├── controllers │ │ ├── auth.controller.js │ │ ├── order.controller.js │ │ ├── product.controller.js │ │ ├── shop.controller.js │ │ └── user.controller.js │ ├── devBundle.js │ ├── express.js │ ├── helpers │ │ └── dbErrorHandler.js │ ├── models │ │ ├── order.model.js │ │ ├── product.model.js │ │ ├── shop.model.js │ │ └── user.model.js │ ├── routes │ │ ├── auth.routes.js │ │ ├── order.routes.js │ │ ├── product.routes.js │ │ ├── shop.routes.js │ │ └── user.routes.js │ └── server.js │ ├── template.js │ ├── webpack.config.client.js │ ├── webpack.config.client.production.js │ └── webpack.config.server.js ├── Chapter08 and 09 └── mern-mediastream │ ├── .babelrc │ ├── LICENSE.md │ ├── README.md │ ├── client │ ├── App.js │ ├── MainRouter.js │ ├── assets │ │ └── images │ │ │ └── seashell.jpg │ ├── auth │ │ ├── PrivateRoute.js │ │ ├── Signin.js │ │ ├── api-auth.js │ │ └── auth-helper.js │ ├── core │ │ ├── Home.js │ │ └── Menu.js │ ├── main.js │ ├── media │ │ ├── DeleteMedia.js │ │ ├── EditMedia.js │ │ ├── Media.js │ │ ├── MediaList.js │ │ ├── MediaPlayer.js │ │ ├── NewMedia.js │ │ ├── PlayMedia.js │ │ ├── RelatedMedia.js │ │ └── api-media.js │ ├── routeConfig.js │ └── user │ │ ├── DeleteUser.js │ │ ├── EditProfile.js │ │ ├── Profile.js │ │ ├── Signup.js │ │ ├── Users.js │ │ └── api-user.js │ ├── config │ └── config.js │ ├── nodemon.json │ ├── package-lock.json │ ├── package.json │ ├── server │ ├── controllers │ │ ├── auth.controller.js │ │ ├── media.controller.js │ │ └── user.controller.js │ ├── devBundle.js │ ├── express.js │ ├── helpers │ │ └── dbErrorHandler.js │ ├── models │ │ ├── media.model.js │ │ └── user.model.js │ ├── routes │ │ ├── auth.routes.js │ │ ├── media.routes.js │ │ └── user.routes.js │ └── server.js │ ├── template.js │ ├── webpack.config.client.js │ ├── webpack.config.client.production.js │ └── webpack.config.server.js ├── Chapter10 └── MERNVR │ ├── README.md │ ├── __tests__ │ └── index-test.js │ ├── client.js │ ├── index.html │ ├── index.js │ ├── package-lock.json │ ├── package.json │ ├── rn-cli.config.js │ └── static_assets │ ├── 360_world.jpg │ ├── 360_world_black.jpg │ ├── clog-up.mp3 │ ├── collect.mp3 │ └── happy-bot.mp3 ├── Chapter11 └── mern-vrgame │ ├── .babelrc │ ├── LICENSE.md │ ├── README.md │ ├── client │ ├── App.js │ ├── MainRouter.js │ ├── assets │ │ └── images │ │ │ └── seashell.jpg │ ├── auth │ │ ├── PrivateRoute.js │ │ ├── Signin.js │ │ ├── api-auth.js │ │ └── auth-helper.js │ ├── core │ │ ├── Home.js │ │ └── Menu.js │ ├── game │ │ ├── DeleteGame.js │ │ ├── EditGame.js │ │ ├── GameDetail.js │ │ ├── GameForm.js │ │ ├── NewGame.js │ │ ├── VRObjectForm.js │ │ └── api-game.js │ ├── main.js │ └── user │ │ ├── DeleteUser.js │ │ ├── EditProfile.js │ │ ├── Profile.js │ │ ├── Signup.js │ │ ├── Users.js │ │ └── api-user.js │ ├── config │ └── config.js │ ├── dist │ ├── client.bundle.js │ ├── index.bundle.js │ └── static_assets │ │ ├── 360_world.jpg │ │ ├── 360_world_black.jpg │ │ ├── clog-up.mp3 │ │ ├── collect.mp3 │ │ └── happy-bot.mp3 │ ├── nodemon.json │ ├── package-lock.json │ ├── package.json │ ├── server │ ├── controllers │ │ ├── auth.controller.js │ │ ├── game.controller.js │ │ └── user.controller.js │ ├── devBundle.js │ ├── express.js │ ├── helpers │ │ └── dbErrorHandler.js │ ├── models │ │ ├── game.model.js │ │ └── user.model.js │ ├── routes │ │ ├── auth.routes.js │ │ ├── game.routes.js │ │ └── user.routes.js │ ├── server.js │ └── vr │ │ └── index.html │ ├── template.js │ ├── webpack.config.client.js │ ├── webpack.config.client.production.js │ └── webpack.config.server.js ├── LICENSE └── README.md /Chapter02/mern-simplesetup/.babelrc: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "presets": [ 4 | "env", 5 | "stage-2", 6 | "react" 7 | ], 8 | "plugins": [ 9 | "react-hot-loader/babel" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /Chapter02/mern-simplesetup/LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Shama Hoque 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Chapter02/mern-simplesetup/README.md: -------------------------------------------------------------------------------- 1 | # MERN - Simple Setup Check 2 | 3 | ### [Live Demo](http://simplesetup.mernbook.com/ "MERN Simple Setup") 4 | 5 | #### What you need to run this code 6 | 1. Node (8.11.1) 7 | 2. NPM (5.8.0) 8 | 3. MongoDB (3.6.3) 9 | 10 | #### How to run this code 11 | 1. Clone this repository 12 | 2. Open command line in the cloned folder, 13 | - To install dependencies, run ``` npm install ``` 14 | - To run the application for development, run ``` npm run development ``` 15 | 4. Open [localhost:3000](http://localhost:3000/) in the browser 16 | 17 | -------------------------------------------------------------------------------- /Chapter02/mern-simplesetup/client/HelloWorld.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { hot } from 'react-hot-loader' 3 | 4 | class HelloWorld extends Component { 5 | render() { 6 | return ( 7 |
8 |

Hello World!

9 |
10 | ) 11 | } 12 | } 13 | 14 | export default hot(module)(HelloWorld) 15 | -------------------------------------------------------------------------------- /Chapter02/mern-simplesetup/client/main.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { render } from 'react-dom' 3 | import HelloWorld from './HelloWorld' 4 | 5 | 6 | render(, document.getElementById('root')) 7 | -------------------------------------------------------------------------------- /Chapter02/mern-simplesetup/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "verbose": false, 3 | "watch": [ 4 | "./server" 5 | ], 6 | "exec": "webpack --mode=development --config webpack.config.server.js && node ./dist/server.generated.js" 7 | } 8 | -------------------------------------------------------------------------------- /Chapter02/mern-simplesetup/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mern-simplesetup", 3 | "version": "1.0.0", 4 | "description": "A quick check for your MERN stack installation", 5 | "author": "Shama Hoque", 6 | "license": "MIT", 7 | "keywords": [ 8 | "react", 9 | "node", 10 | "express", 11 | "mongodb", 12 | "mern" 13 | ], 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/shamahoque/mern-simplesetup.git" 17 | }, 18 | "homepage": "https://github.com/shamahoque/mern-simplesetup", 19 | "main": "template.js", 20 | "scripts": { 21 | "development": "nodemon", 22 | "build": "webpack --config webpack.config.client.production.js && webpack --mode=production --config webpack.config.server.js", 23 | "start": "NODE_ENV=production node ./dist/server.generated.js" 24 | }, 25 | "engines": { 26 | "node": "8.11.1", 27 | "npm": "5.8.0" 28 | }, 29 | "dependencies": { 30 | "express": "^4.16.3", 31 | "mongodb": "^3.0.7", 32 | "react": "^16.3.2", 33 | "react-dom": "^16.3.2", 34 | "react-hot-loader": "^4.1.2" 35 | }, 36 | "devDependencies": { 37 | "babel-core": "^6.26.2", 38 | "babel-loader": "^7.1.4", 39 | "babel-preset-env": "^1.6.1", 40 | "babel-preset-react": "^6.24.1", 41 | "babel-preset-stage-2": "^6.24.1", 42 | "nodemon": "^1.17.3", 43 | "webpack": "^4.6.0", 44 | "webpack-cli": "^2.0.15", 45 | "webpack-dev-middleware": "^3.1.2", 46 | "webpack-hot-middleware": "^2.22.1", 47 | "webpack-node-externals": "^1.7.2" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Chapter02/mern-simplesetup/server/devBundle.js: -------------------------------------------------------------------------------- 1 | import webpack from 'webpack' 2 | import webpackMiddleware from 'webpack-dev-middleware' 3 | import webpackHotMiddleware from 'webpack-hot-middleware' 4 | import webpackConfig from './../webpack.config.client.js' 5 | 6 | const compile = (app) => { 7 | if(process.env.NODE_ENV == "development"){ 8 | const compiler = webpack(webpackConfig) 9 | const middleware = webpackMiddleware(compiler, { 10 | publicPath: webpackConfig.output.publicPath 11 | }) 12 | app.use(middleware) 13 | app.use(webpackHotMiddleware(compiler)) 14 | } 15 | } 16 | 17 | export default { 18 | compile 19 | } 20 | -------------------------------------------------------------------------------- /Chapter02/mern-simplesetup/server/server.js: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import express from 'express' 3 | import { MongoClient } from 'mongodb' 4 | import template from './../template' 5 | //comment out before building for production 6 | import devBundle from './devBundle' 7 | 8 | const app = express() 9 | //comment out before building for production 10 | devBundle.compile(app) 11 | 12 | const CURRENT_WORKING_DIR = process.cwd() 13 | app.use('/dist', express.static(path.join(CURRENT_WORKING_DIR, 'dist'))) 14 | 15 | app.get('/', (req, res) => { 16 | res.status(200).send(template()) 17 | }) 18 | 19 | let port = process.env.PORT || 3000 20 | app.listen(port, function onStart(err) { 21 | if (err) { 22 | console.log(err) 23 | } 24 | console.info('Server started on port %s.', port) 25 | }) 26 | 27 | // Database Connection URL 28 | const url = process.env.MONGODB_URI || 'mongodb://localhost:27017/mernSimpleSetup' 29 | // Use connect method to connect to the server 30 | MongoClient.connect(url, (err, db)=>{ 31 | console.log("Connected successfully to mongodb server") 32 | db.close() 33 | }) 34 | -------------------------------------------------------------------------------- /Chapter02/mern-simplesetup/template.js: -------------------------------------------------------------------------------- 1 | export default () => { 2 | return ` 3 | 4 | 5 | 6 | MERN Kickstart 7 | 8 | 9 |
10 | 11 | 12 | ` 13 | } 14 | -------------------------------------------------------------------------------- /Chapter02/mern-simplesetup/webpack.config.client.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | const CURRENT_WORKING_DIR = process.cwd() 4 | 5 | const config = { 6 | name: "browser", 7 | mode: "development", 8 | devtool: 'eval-source-map', 9 | entry: [ 10 | 'react-hot-loader/patch', 11 | 'webpack-hot-middleware/client?reload=true', 12 | path.join(CURRENT_WORKING_DIR, 'client/main.js') 13 | ], 14 | output: { 15 | path: path.join(CURRENT_WORKING_DIR , '/dist'), 16 | filename: 'bundle.js', 17 | publicPath: '/dist/' 18 | }, 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.jsx?$/, 23 | exclude: /node_modules/, 24 | use: [ 25 | 'babel-loader' 26 | ] 27 | } 28 | ] 29 | }, plugins: [ 30 | new webpack.HotModuleReplacementPlugin(), 31 | new webpack.NoEmitOnErrorsPlugin() 32 | ] 33 | } 34 | 35 | module.exports = config 36 | -------------------------------------------------------------------------------- /Chapter02/mern-simplesetup/webpack.config.client.production.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | const CURRENT_WORKING_DIR = process.cwd() 4 | 5 | const config = { 6 | mode: "production", 7 | entry: [ 8 | path.join(CURRENT_WORKING_DIR, 'client/main.js') 9 | ], 10 | output: { 11 | path: path.join(CURRENT_WORKING_DIR , '/dist'), 12 | filename: 'bundle.js', 13 | publicPath: "/dist/" 14 | }, 15 | module: { 16 | rules: [ 17 | { 18 | test: /\.jsx?$/, 19 | exclude: /node_modules/, 20 | use: [ 21 | 'babel-loader' 22 | ] 23 | } 24 | ] 25 | } 26 | } 27 | 28 | module.exports = config 29 | -------------------------------------------------------------------------------- /Chapter02/mern-simplesetup/webpack.config.server.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | const nodeExternals = require('webpack-node-externals') 4 | const CURRENT_WORKING_DIR = process.cwd() 5 | 6 | const config = { 7 | name: "server", 8 | entry: [ path.join(CURRENT_WORKING_DIR , './server/server.js') ], 9 | target: "node", 10 | output: { 11 | path: path.join(CURRENT_WORKING_DIR , '/dist/'), 12 | filename: "server.generated.js", 13 | publicPath: '/dist/', 14 | libraryTarget: "commonjs2" 15 | }, 16 | externals: [nodeExternals()], 17 | module: { 18 | rules: [ 19 | { 20 | test: /\.js$/, 21 | exclude: /node_modules/, 22 | use: [ 'babel-loader' ] 23 | } 24 | ] 25 | } 26 | 27 | } 28 | 29 | module.exports = config 30 | -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "env", 4 | "stage-2", 5 | "react" 6 | ], 7 | "plugins": [ 8 | "react-hot-loader/babel" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Shama Hoque 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/README.md: -------------------------------------------------------------------------------- 1 | # MERN Skeleton 2 | 3 | ### [Live Demo](http://skeleton.mernbook.com/ "MERN Skeleton") 4 | 5 | #### What you need to run this code 6 | 1. Node (8.11.1) 7 | 2. NPM (5.8.0) 8 | 3. MongoDB (3.6.3) 9 | 10 | #### How to run this code 11 | 1. Clone this repository 12 | 2. Open command line in the cloned folder, 13 | - To install dependencies, run ``` npm install ``` 14 | - To run the application for development, run ``` npm run development ``` 15 | 4. Open [localhost:3000](http://localhost:3000/) in the browser 16 | 17 | -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/client/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import MainRouter from './MainRouter' 3 | import {BrowserRouter} from 'react-router-dom' 4 | import {MuiThemeProvider, createMuiTheme} from 'material-ui/styles' 5 | import {indigo, pink} from 'material-ui/colors' 6 | import { hot } from 'react-hot-loader' 7 | 8 | // Create a theme instance. 9 | const theme = createMuiTheme({ 10 | palette: { 11 | primary: { 12 | light: '#757de8', 13 | main: '#3f51b5', 14 | dark: '#002984', 15 | contrastText: '#fff', 16 | }, 17 | secondary: { 18 | light: '#ff79b0', 19 | main: '#ff4081', 20 | dark: '#c60055', 21 | contrastText: '#000', 22 | }, 23 | openTitle: indigo['400'], 24 | protectedTitle: pink['400'], 25 | type: 'light' 26 | } 27 | }) 28 | 29 | const App = () => ( 30 | 31 | 32 | 33 | 34 | 35 | ) 36 | 37 | export default hot(module)(App) 38 | -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/client/MainRouter.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import {Route, Switch} from 'react-router-dom' 3 | import Home from './core/Home' 4 | import Users from './user/Users' 5 | import Signup from './user/Signup' 6 | import Signin from './auth/Signin' 7 | import EditProfile from './user/EditProfile' 8 | import Profile from './user/Profile' 9 | import PrivateRoute from './auth/PrivateRoute' 10 | import Menu from './core/Menu' 11 | 12 | class MainRouter extends Component { 13 | // Removes the server-side injected CSS when React component mounts 14 | componentDidMount() { 15 | const jssStyles = document.getElementById('jss-server-side') 16 | if (jssStyles && jssStyles.parentNode) { 17 | jssStyles.parentNode.removeChild(jssStyles) 18 | } 19 | } 20 | 21 | render() { 22 | return (
23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
) 33 | } 34 | } 35 | 36 | export default MainRouter 37 | -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/client/assets/images/seashell.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Full-Stack-React-Projects/93940878b3429a415af672d0f3751a2f7e2fe1ca/Chapter03 and 04/mern-skeleton/client/assets/images/seashell.jpg -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/client/auth/PrivateRoute.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Route, Redirect } from 'react-router-dom' 3 | import auth from './auth-helper' 4 | 5 | const PrivateRoute = ({ component: Component, ...rest }) => ( 6 | ( 7 | auth.isAuthenticated() ? ( 8 | 9 | ) : ( 10 | 14 | ) 15 | )}/> 16 | ) 17 | 18 | export default PrivateRoute 19 | -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/client/auth/api-auth.js: -------------------------------------------------------------------------------- 1 | const signin = (user) => { 2 | return fetch('/auth/signin/', { 3 | method: 'POST', 4 | headers: { 5 | 'Accept': 'application/json', 6 | 'Content-Type': 'application/json' 7 | }, 8 | credentials: 'include', 9 | body: JSON.stringify(user) 10 | }) 11 | .then((response) => { 12 | return response.json() 13 | }).catch((err) => console.log(err)) 14 | } 15 | 16 | const signout = () => { 17 | return fetch('/auth/signout/', { 18 | method: 'GET', 19 | }).then(response => { 20 | return response.json() 21 | }).catch((err) => console.log(err)) 22 | } 23 | 24 | export { 25 | signin, 26 | signout 27 | } 28 | -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/client/auth/auth-helper.js: -------------------------------------------------------------------------------- 1 | import { signout } from './api-auth.js' 2 | 3 | const auth = { 4 | isAuthenticated() { 5 | if (typeof window == "undefined") 6 | return false 7 | 8 | if (sessionStorage.getItem('jwt')) 9 | return JSON.parse(sessionStorage.getItem('jwt')) 10 | else 11 | return false 12 | }, 13 | authenticate(jwt, cb) { 14 | if (typeof window !== "undefined") 15 | sessionStorage.setItem('jwt', JSON.stringify(jwt)) 16 | cb() 17 | }, 18 | signout(cb) { 19 | if (typeof window !== "undefined") 20 | sessionStorage.removeItem('jwt') 21 | cb() 22 | //optional 23 | signout().then((data) => { 24 | document.cookie = "t=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;" 25 | }) 26 | } 27 | } 28 | 29 | export default auth 30 | -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/client/core/Home.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import PropTypes from 'prop-types' 3 | import {withStyles} from 'material-ui/styles' 4 | import Card, {CardContent, CardMedia} from 'material-ui/Card' 5 | import Typography from 'material-ui/Typography' 6 | import seashellImg from './../assets/images/seashell.jpg' 7 | 8 | const styles = theme => ({ 9 | card: { 10 | maxWidth: 600, 11 | margin: 'auto', 12 | marginTop: theme.spacing.unit * 5 13 | }, 14 | title: { 15 | padding:`${theme.spacing.unit * 3}px ${theme.spacing.unit * 2.5}px ${theme.spacing.unit * 2}px`, 16 | color: theme.palette.text.secondary 17 | }, 18 | media: { 19 | minHeight: 330 20 | } 21 | }) 22 | 23 | class Home extends Component { 24 | render() { 25 | const {classes} = this.props 26 | return ( 27 | 28 | 29 | Home Page 30 | 31 | 32 | 33 | 34 | Welcome to the MERN Skeleton home page. 35 | 36 | 37 | 38 | ) 39 | } 40 | } 41 | 42 | Home.propTypes = { 43 | classes: PropTypes.object.isRequired 44 | } 45 | 46 | export default withStyles(styles)(Home) 47 | -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/client/core/Menu.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import AppBar from 'material-ui/AppBar' 3 | import Toolbar from 'material-ui/Toolbar' 4 | import Typography from 'material-ui/Typography' 5 | import IconButton from 'material-ui/IconButton' 6 | import HomeIcon from 'material-ui-icons/Home' 7 | import Button from 'material-ui/Button' 8 | import auth from './../auth/auth-helper' 9 | import {Link, withRouter} from 'react-router-dom' 10 | 11 | const isActive = (history, path) => { 12 | if (history.location.pathname == path) 13 | return {color: '#ff4081'} 14 | else 15 | return {color: '#ffffff'} 16 | } 17 | const Menu = withRouter(({history}) => ( 18 | 19 | 20 | 21 | MERN Skeleton 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | { 32 | !auth.isAuthenticated() && ( 33 | 34 | 36 | 37 | 38 | 40 | 41 | ) 42 | } 43 | { 44 | auth.isAuthenticated() && ( 45 | 46 | 47 | 48 | 51 | ) 52 | } 53 | 54 | 55 | )) 56 | 57 | export default Menu 58 | -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/client/main.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { hydrate } from 'react-dom' 3 | import App from './App' 4 | 5 | hydrate(, document.getElementById('root')) 6 | -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/client/user/DeleteUser.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import PropTypes from 'prop-types' 3 | import IconButton from 'material-ui/IconButton' 4 | import Button from 'material-ui/Button' 5 | import DeleteIcon from 'material-ui-icons/Delete' 6 | import Dialog, {DialogActions, DialogContent, DialogContentText, DialogTitle} from 'material-ui/Dialog' 7 | import auth from './../auth/auth-helper' 8 | import {remove} from './api-user.js' 9 | import {Redirect, Link} from 'react-router-dom' 10 | 11 | class DeleteUser extends Component { 12 | state = { 13 | redirect: false, 14 | open: false 15 | } 16 | clickButton = () => { 17 | this.setState({open: true}) 18 | } 19 | deleteAccount = () => { 20 | const jwt = auth.isAuthenticated() 21 | remove({ 22 | userId: this.props.userId 23 | }, {t: jwt.token}).then((data) => { 24 | if (data.error) { 25 | console.log(data.error) 26 | } else { 27 | auth.signout(() => console.log('deleted')) 28 | this.setState({redirect: true}) 29 | } 30 | }) 31 | } 32 | handleRequestClose = () => { 33 | this.setState({open: false}) 34 | } 35 | render() { 36 | const redirect = this.state.redirect 37 | if (redirect) { 38 | return 39 | } 40 | return ( 41 | 42 | 43 | 44 | 45 | 46 | {"Delete Account"} 47 | 48 | 49 | Confirm to delete your account. 50 | 51 | 52 | 53 | 56 | 59 | 60 | 61 | ) 62 | } 63 | } 64 | DeleteUser.propTypes = { 65 | userId: PropTypes.string.isRequired 66 | } 67 | export default DeleteUser 68 | -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/client/user/api-user.js: -------------------------------------------------------------------------------- 1 | const create = (user) => { 2 | return fetch('/api/users/', { 3 | method: 'POST', 4 | headers: { 5 | 'Accept': 'application/json', 6 | 'Content-Type': 'application/json' 7 | }, 8 | body: JSON.stringify(user) 9 | }) 10 | .then((response) => { 11 | return response.json() 12 | }).catch((err) => console.log(err)) 13 | } 14 | 15 | const list = () => { 16 | return fetch('/api/users/', { 17 | method: 'GET', 18 | }).then(response => { 19 | return response.json() 20 | }).catch((err) => console.log(err)) 21 | } 22 | 23 | const read = (params, credentials) => { 24 | return fetch('/api/users/' + params.userId, { 25 | method: 'GET', 26 | headers: { 27 | 'Accept': 'application/json', 28 | 'Content-Type': 'application/json', 29 | 'Authorization': 'Bearer ' + credentials.t 30 | } 31 | }).then((response) => { 32 | return response.json() 33 | }).catch((err) => console.log(err)) 34 | } 35 | 36 | const update = (params, credentials, user) => { 37 | return fetch('/api/users/' + params.userId, { 38 | method: 'PUT', 39 | headers: { 40 | 'Accept': 'application/json', 41 | 'Content-Type': 'application/json', 42 | 'Authorization': 'Bearer ' + credentials.t 43 | }, 44 | body: JSON.stringify(user) 45 | }).then((response) => { 46 | return response.json() 47 | }).catch((err) => console.log(err)) 48 | } 49 | 50 | const remove = (params, credentials) => { 51 | return fetch('/api/users/' + params.userId, { 52 | method: 'DELETE', 53 | headers: { 54 | 'Accept': 'application/json', 55 | 'Content-Type': 'application/json', 56 | 'Authorization': 'Bearer ' + credentials.t 57 | } 58 | }).then((response) => { 59 | return response.json() 60 | }).catch((err) => console.log(err)) 61 | } 62 | 63 | export { 64 | create, 65 | list, 66 | read, 67 | update, 68 | remove 69 | } 70 | -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/config/config.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | env: process.env.NODE_ENV || 'development', 3 | port: process.env.PORT || 3000, 4 | jwtSecret: process.env.JWT_SECRET || "YOUR_secret_key", 5 | mongoUri: process.env.MONGODB_URI || 6 | process.env.MONGO_HOST || 7 | 'mongodb://' + (process.env.IP || 'localhost') + ':' + 8 | (process.env.MONGO_PORT || '27017') + 9 | '/mernproject' 10 | } 11 | 12 | export default config 13 | -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "verbose": false, 3 | "watch": [ 4 | "./server" 5 | ], 6 | "exec": "webpack --mode=development --config webpack.config.server.js && node ./dist/server.generated.js" 7 | } 8 | -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mern-skeleton", 3 | "version": "1.0.0", 4 | "description": "A MERN stack skeleton web application", 5 | "author": "Shama Hoque", 6 | "license": "MIT", 7 | "keywords": [ 8 | "react", 9 | "express", 10 | "mongodb", 11 | "node", 12 | "mern" 13 | ], 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/shamahoque/mern-skeleton.git" 17 | }, 18 | "homepage": "https://github.com/shamahoque/mern-skeleton", 19 | "main": "./dist/server.generated.js", 20 | "scripts": { 21 | "development": "nodemon", 22 | "build": "webpack --config webpack.config.client.production.js && webpack --mode=production --config webpack.config.server.js", 23 | "start": "NODE_ENV=production node ./dist/server.generated.js" 24 | }, 25 | "engines": { 26 | "node": "8.11.1", 27 | "npm": "5.8.0" 28 | }, 29 | "devDependencies": { 30 | "babel-core": "^6.26.2", 31 | "babel-loader": "^7.1.4", 32 | "babel-preset-env": "^1.6.1", 33 | "babel-preset-react": "^6.24.1", 34 | "babel-preset-stage-2": "^6.24.1", 35 | "file-loader": "^1.1.11", 36 | "nodemon": "^1.17.3", 37 | "webpack": "^4.6.0", 38 | "webpack-cli": "^2.0.15", 39 | "webpack-dev-middleware": "^3.1.3", 40 | "webpack-hot-middleware": "^2.22.1", 41 | "webpack-node-externals": "^1.7.2" 42 | }, 43 | "dependencies": { 44 | "body-parser": "^1.18.2", 45 | "compression": "^1.7.2", 46 | "cookie-parser": "^1.4.3", 47 | "cors": "^2.8.4", 48 | "express": "^4.16.3", 49 | "express-jwt": "^5.3.1", 50 | "helmet": "^3.12.0", 51 | "jsonwebtoken": "^8.2.1", 52 | "lodash": "^4.17.10", 53 | "material-ui": "^1.0.0-beta.43", 54 | "material-ui-icons": "^1.0.0-beta.36", 55 | "mongoose": "^5.0.16", 56 | "react": "^16.3.2", 57 | "react-dom": "^16.3.2", 58 | "react-hot-loader": "^4.1.2", 59 | "react-router": "^4.2.0", 60 | "react-router-dom": "^4.2.2" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/server/controllers/auth.controller.js: -------------------------------------------------------------------------------- 1 | import User from '../models/user.model' 2 | import jwt from 'jsonwebtoken' 3 | import expressJwt from 'express-jwt' 4 | import config from './../../config/config' 5 | 6 | const signin = (req, res) => { 7 | User.findOne({ 8 | "email": req.body.email 9 | }, (err, user) => { 10 | 11 | if (err || !user) 12 | return res.status('401').json({ 13 | error: "User not found" 14 | }) 15 | 16 | if (!user.authenticate(req.body.password)) { 17 | return res.status('401').send({ 18 | error: "Email and password don't match." 19 | }) 20 | } 21 | 22 | const token = jwt.sign({ 23 | _id: user._id 24 | }, config.jwtSecret) 25 | 26 | res.cookie("t", token, { 27 | expire: new Date() + 9999 28 | }) 29 | 30 | return res.json({ 31 | token, 32 | user: {_id: user._id, name: user.name, email: user.email} 33 | }) 34 | 35 | }) 36 | } 37 | 38 | const signout = (req, res) => { 39 | res.clearCookie("t") 40 | return res.status('200').json({ 41 | message: "signed out" 42 | }) 43 | } 44 | 45 | const requireSignin = expressJwt({ 46 | secret: config.jwtSecret, 47 | userProperty: 'auth' 48 | }) 49 | 50 | const hasAuthorization = (req, res, next) => { 51 | const authorized = req.profile && req.auth && req.profile._id == req.auth._id 52 | if (!(authorized)) { 53 | return res.status('403').json({ 54 | error: "User is not authorized" 55 | }) 56 | } 57 | next() 58 | } 59 | 60 | export default { 61 | signin, 62 | signout, 63 | requireSignin, 64 | hasAuthorization 65 | } 66 | -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/server/controllers/user.controller.js: -------------------------------------------------------------------------------- 1 | import User from '../models/user.model' 2 | import _ from 'lodash' 3 | import errorHandler from './../helpers/dbErrorHandler' 4 | 5 | const create = (req, res, next) => { 6 | const user = new User(req.body) 7 | user.save((err, result) => { 8 | if (err) { 9 | return res.status(400).json({ 10 | error: errorHandler.getErrorMessage(err) 11 | }) 12 | } 13 | res.status(200).json({ 14 | message: "Successfully signed up!" 15 | }) 16 | }) 17 | } 18 | 19 | /** 20 | * Load user and append to req. 21 | */ 22 | const userByID = (req, res, next, id) => { 23 | User.findById(id).exec((err, user) => { 24 | if (err || !user) 25 | return res.status('400').json({ 26 | error: "User not found" 27 | }) 28 | req.profile = user 29 | next() 30 | }) 31 | } 32 | 33 | const read = (req, res) => { 34 | req.profile.hashed_password = undefined 35 | req.profile.salt = undefined 36 | return res.json(req.profile) 37 | } 38 | 39 | const list = (req, res) => { 40 | User.find((err, users) => { 41 | if (err) { 42 | return res.status(400).json({ 43 | error: errorHandler.getErrorMessage(err) 44 | }) 45 | } 46 | res.json(users) 47 | }).select('name email updated created') 48 | } 49 | 50 | const update = (req, res, next) => { 51 | let user = req.profile 52 | user = _.extend(user, req.body) 53 | user.updated = Date.now() 54 | user.save((err) => { 55 | if (err) { 56 | return res.status(400).json({ 57 | error: errorHandler.getErrorMessage(err) 58 | }) 59 | } 60 | user.hashed_password = undefined 61 | user.salt = undefined 62 | res.json(user) 63 | }) 64 | } 65 | 66 | const remove = (req, res, next) => { 67 | let user = req.profile 68 | user.remove((err, deletedUser) => { 69 | if (err) { 70 | return res.status(400).json({ 71 | error: errorHandler.getErrorMessage(err) 72 | }) 73 | } 74 | deletedUser.hashed_password = undefined 75 | deletedUser.salt = undefined 76 | res.json(deletedUser) 77 | }) 78 | } 79 | 80 | export default { 81 | create, 82 | userByID, 83 | read, 84 | list, 85 | remove, 86 | update 87 | } 88 | -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/server/devBundle.js: -------------------------------------------------------------------------------- 1 | import config from './../config/config' 2 | import webpack from 'webpack' 3 | import webpackMiddleware from 'webpack-dev-middleware' 4 | import webpackHotMiddleware from 'webpack-hot-middleware' 5 | import webpackConfig from './../webpack.config.client.js' 6 | 7 | const compile = (app) => { 8 | if(config.env === "development"){ 9 | const compiler = webpack(webpackConfig) 10 | const middleware = webpackMiddleware(compiler, { 11 | publicPath: webpackConfig.output.publicPath 12 | }) 13 | app.use(middleware) 14 | app.use(webpackHotMiddleware(compiler)) 15 | } 16 | } 17 | 18 | export default { 19 | compile 20 | } 21 | -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/server/helpers/dbErrorHandler.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Get unique error field name 5 | */ 6 | const getUniqueErrorMessage = (err) => { 7 | let output 8 | try { 9 | let fieldName = err.message.substring(err.message.lastIndexOf('.$') + 2, err.message.lastIndexOf('_1')) 10 | output = fieldName.charAt(0).toUpperCase() + fieldName.slice(1) + ' already exists' 11 | } catch (ex) { 12 | output = 'Unique field already exists' 13 | } 14 | 15 | return output 16 | } 17 | 18 | /** 19 | * Get the error message from error object 20 | */ 21 | const getErrorMessage = (err) => { 22 | let message = '' 23 | 24 | if (err.code) { 25 | switch (err.code) { 26 | case 11000: 27 | case 11001: 28 | message = getUniqueErrorMessage(err) 29 | break 30 | default: 31 | message = 'Something went wrong' 32 | } 33 | } else { 34 | for (let errName in err.errors) { 35 | if (err.errors[errName].message) message = err.errors[errName].message 36 | } 37 | } 38 | 39 | return message 40 | } 41 | 42 | export default {getErrorMessage} 43 | -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/server/models/user.model.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose' 2 | import crypto from 'crypto' 3 | const UserSchema = new mongoose.Schema({ 4 | name: { 5 | type: String, 6 | trim: true, 7 | required: 'Name is required' 8 | }, 9 | email: { 10 | type: String, 11 | trim: true, 12 | unique: 'Email already exists', 13 | match: [/.+\@.+\..+/, 'Please fill a valid email address'], 14 | required: 'Email is required' 15 | }, 16 | hashed_password: { 17 | type: String, 18 | required: "Password is required" 19 | }, 20 | salt: String, 21 | updated: Date, 22 | created: { 23 | type: Date, 24 | default: Date.now 25 | } 26 | }) 27 | 28 | UserSchema 29 | .virtual('password') 30 | .set(function(password) { 31 | this._password = password 32 | this.salt = this.makeSalt() 33 | this.hashed_password = this.encryptPassword(password) 34 | }) 35 | .get(function() { 36 | return this._password 37 | }) 38 | 39 | UserSchema.path('hashed_password').validate(function(v) { 40 | if (this._password && this._password.length < 6) { 41 | this.invalidate('password', 'Password must be at least 6 characters.') 42 | } 43 | if (this.isNew && !this._password) { 44 | this.invalidate('password', 'Password is required') 45 | } 46 | }, null) 47 | 48 | UserSchema.methods = { 49 | authenticate: function(plainText) { 50 | return this.encryptPassword(plainText) === this.hashed_password 51 | }, 52 | encryptPassword: function(password) { 53 | if (!password) return '' 54 | try { 55 | return crypto 56 | .createHmac('sha1', this.salt) 57 | .update(password) 58 | .digest('hex') 59 | } catch (err) { 60 | return '' 61 | } 62 | }, 63 | makeSalt: function() { 64 | return Math.round((new Date().valueOf() * Math.random())) + '' 65 | } 66 | } 67 | 68 | export default mongoose.model('User', UserSchema) 69 | -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/server/routes/auth.routes.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import authCtrl from '../controllers/auth.controller' 3 | 4 | const router = express.Router() 5 | 6 | router.route('/auth/signin') 7 | .post(authCtrl.signin) 8 | router.route('/auth/signout') 9 | .get(authCtrl.signout) 10 | 11 | export default router 12 | -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/server/routes/user.routes.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import userCtrl from '../controllers/user.controller' 3 | import authCtrl from '../controllers/auth.controller' 4 | 5 | const router = express.Router() 6 | 7 | router.route('/api/users') 8 | .get(userCtrl.list) 9 | .post(userCtrl.create) 10 | 11 | router.route('/api/users/:userId') 12 | .get(authCtrl.requireSignin, userCtrl.read) 13 | .put(authCtrl.requireSignin, authCtrl.hasAuthorization, userCtrl.update) 14 | .delete(authCtrl.requireSignin, authCtrl.hasAuthorization, userCtrl.remove) 15 | 16 | router.param('userId', userCtrl.userByID) 17 | 18 | export default router 19 | -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/server/server.js: -------------------------------------------------------------------------------- 1 | import config from './../config/config' 2 | import app from './express' 3 | import mongoose from 'mongoose' 4 | 5 | // Connection URL 6 | mongoose.Promise = global.Promise 7 | mongoose.connect(config.mongoUri) 8 | mongoose.connection.on('error', () => { 9 | throw new Error(`unable to connect to database: ${mongoUri}`) 10 | }) 11 | 12 | app.listen(config.port, (err) => { 13 | if (err) { 14 | console.log(err) 15 | } 16 | console.info('Server started on port %s.', config.port) 17 | }) 18 | -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/template.js: -------------------------------------------------------------------------------- 1 | export default ({markup, css}) => { 2 | return ` 3 | 4 | 5 | 6 | MERN Skeleton 7 | 8 | 9 | 14 | 15 | 16 |
${markup}
17 | 18 | 19 | 20 | ` 21 | } 22 | -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/webpack.config.client.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | const CURRENT_WORKING_DIR = process.cwd() 4 | 5 | const config = { 6 | name: "browser", 7 | mode: "development", 8 | devtool: 'eval-source-map', 9 | entry: [ 10 | 'react-hot-loader/patch', 11 | 'webpack-hot-middleware/client?reload=true', 12 | path.join(CURRENT_WORKING_DIR, 'client/main.js') 13 | ], 14 | output: { 15 | path: path.join(CURRENT_WORKING_DIR , '/dist'), 16 | filename: 'bundle.js', 17 | publicPath: '/dist/' 18 | }, 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.jsx?$/, 23 | exclude: /node_modules/, 24 | use: [ 25 | 'babel-loader' 26 | ] 27 | }, 28 | { 29 | test: /\.(ttf|eot|svg|gif|jpg|png)(\?[\s\S]+)?$/, 30 | use: 'file-loader' 31 | } 32 | ] 33 | }, plugins: [ 34 | new webpack.HotModuleReplacementPlugin(), 35 | new webpack.NoEmitOnErrorsPlugin() 36 | ] 37 | } 38 | 39 | module.exports = config 40 | -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/webpack.config.client.production.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | const CURRENT_WORKING_DIR = process.cwd() 4 | 5 | const config = { 6 | mode: "production", 7 | entry: [ 8 | path.join(CURRENT_WORKING_DIR, 'client/main.js') 9 | ], 10 | output: { 11 | path: path.join(CURRENT_WORKING_DIR , '/dist'), 12 | filename: 'bundle.js', 13 | publicPath: "/dist/" 14 | }, 15 | module: { 16 | rules: [ 17 | { 18 | test: /\.jsx?$/, 19 | exclude: /node_modules/, 20 | use: [ 21 | 'babel-loader' 22 | ] 23 | }, 24 | { 25 | test: /\.(ttf|eot|svg|gif|jpg|png)(\?[\s\S]+)?$/, 26 | use: 'file-loader' 27 | } 28 | ] 29 | } 30 | } 31 | 32 | module.exports = config 33 | -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/webpack.config.server.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | const nodeExternals = require('webpack-node-externals') 4 | const CURRENT_WORKING_DIR = process.cwd() 5 | 6 | const config = { 7 | name: "server", 8 | entry: [ path.join(CURRENT_WORKING_DIR , './server/server.js') ], 9 | target: "node", 10 | output: { 11 | path: path.join(CURRENT_WORKING_DIR , '/dist/'), 12 | filename: "server.generated.js", 13 | publicPath: '/dist/', 14 | libraryTarget: "commonjs2" 15 | }, 16 | externals: [nodeExternals()], 17 | module: { 18 | rules: [ 19 | { 20 | test: /\.js$/, 21 | exclude: /node_modules/, 22 | use: [ 'babel-loader' ] 23 | }, 24 | { 25 | test: /\.(ttf|eot|svg|gif|jpg|png)(\?[\s\S]+)?$/, 26 | use: 'file-loader' 27 | } 28 | ] 29 | } 30 | } 31 | 32 | module.exports = config 33 | -------------------------------------------------------------------------------- /Chapter05/mern-social/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "env", 4 | "stage-2", 5 | "react" 6 | ], 7 | "plugins": [ 8 | "react-hot-loader/babel" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /Chapter05/mern-social/LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Shama Hoque 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Chapter05/mern-social/README.md: -------------------------------------------------------------------------------- 1 | # MERN Social 2 | 3 | ### [Live Demo](http://social.mernbook.com/ "MERN Social") 4 | 5 | #### What you need to run this code 6 | 1. Node (8.11.1) 7 | 2. NPM (5.8.0) 8 | 3. MongoDB (3.6.3) 9 | 10 | #### How to run this code 11 | 1. Clone this repository 12 | 2. Open command line in the cloned folder, 13 | - To install dependencies, run ``` npm install ``` 14 | - To run the application for development, run ``` npm run development ``` 15 | 4. Open [localhost:3000](http://localhost:3000/) in the browser 16 | -------------------------------------------------------------------------------- /Chapter05/mern-social/client/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import MainRouter from './MainRouter' 3 | import {BrowserRouter} from 'react-router-dom' 4 | import {MuiThemeProvider, createMuiTheme} from 'material-ui/styles' 5 | import {teal, orange} from 'material-ui/colors' 6 | import { hot } from 'react-hot-loader' 7 | 8 | // Create a theme instance. 9 | const theme = createMuiTheme({ 10 | palette: { 11 | primary: { 12 | light: '#52c7b8', 13 | main: '#009688', 14 | dark: '#00675b', 15 | contrastText: '#fff', 16 | }, 17 | secondary: { 18 | light: '#ffd95b', 19 | main: '#ffa726', 20 | dark: '#c77800', 21 | contrastText: '#000', 22 | }, 23 | openTitle: teal['700'], 24 | protectedTitle: orange['700'], 25 | type: 'light' 26 | } 27 | }) 28 | 29 | const App = () => ( 30 | 31 | 32 | 33 | 34 | 35 | ) 36 | 37 | export default hot(module)(App) 38 | -------------------------------------------------------------------------------- /Chapter05/mern-social/client/MainRouter.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import {Route, Switch} from 'react-router-dom' 3 | import Home from './core/Home' 4 | import Users from './user/Users' 5 | import Signup from './user/Signup' 6 | import Signin from './auth/Signin' 7 | import EditProfile from './user/EditProfile' 8 | import Profile from './user/Profile' 9 | import PrivateRoute from './auth/PrivateRoute' 10 | import Menu from './core/Menu' 11 | 12 | class MainRouter extends Component { 13 | // Removes the server-side injected CSS when React component mounts 14 | componentDidMount() { 15 | const jssStyles = document.getElementById('jss-server-side') 16 | if (jssStyles && jssStyles.parentNode) { 17 | jssStyles.parentNode.removeChild(jssStyles) 18 | } 19 | } 20 | 21 | render() { 22 | return (
23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
) 33 | } 34 | } 35 | 36 | export default MainRouter 37 | -------------------------------------------------------------------------------- /Chapter05/mern-social/client/assets/images/profile-pic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Full-Stack-React-Projects/93940878b3429a415af672d0f3751a2f7e2fe1ca/Chapter05/mern-social/client/assets/images/profile-pic.png -------------------------------------------------------------------------------- /Chapter05/mern-social/client/assets/images/seashell.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Full-Stack-React-Projects/93940878b3429a415af672d0f3751a2f7e2fe1ca/Chapter05/mern-social/client/assets/images/seashell.jpg -------------------------------------------------------------------------------- /Chapter05/mern-social/client/auth/PrivateRoute.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Route, Redirect } from 'react-router-dom' 3 | import auth from './auth-helper' 4 | 5 | const PrivateRoute = ({ component: Component, ...rest }) => ( 6 | ( 7 | auth.isAuthenticated() ? ( 8 | 9 | ) : ( 10 | 14 | ) 15 | )}/> 16 | ) 17 | 18 | export default PrivateRoute 19 | -------------------------------------------------------------------------------- /Chapter05/mern-social/client/auth/api-auth.js: -------------------------------------------------------------------------------- 1 | const signin = (user) => { 2 | return fetch('/auth/signin/', { 3 | method: 'POST', 4 | headers: { 5 | 'Accept': 'application/json', 6 | 'Content-Type': 'application/json' 7 | }, 8 | credentials: 'include', 9 | body: JSON.stringify(user) 10 | }) 11 | .then((response) => { 12 | return response.json() 13 | }).catch((err) => console.log(err)) 14 | } 15 | 16 | const signout = () => { 17 | return fetch('/auth/signout/', { 18 | method: 'GET', 19 | }).then(response => { 20 | return response.json() 21 | }).catch((err) => console.log(err)) 22 | } 23 | 24 | export { 25 | signin, 26 | signout 27 | } 28 | -------------------------------------------------------------------------------- /Chapter05/mern-social/client/auth/auth-helper.js: -------------------------------------------------------------------------------- 1 | import { signout } from './api-auth.js' 2 | 3 | const auth = { 4 | isAuthenticated() { 5 | if (typeof window == "undefined") 6 | return false 7 | 8 | if (sessionStorage.getItem('jwt')) 9 | return JSON.parse(sessionStorage.getItem('jwt')) 10 | else 11 | return false 12 | }, 13 | authenticate(jwt, cb) { 14 | if (typeof window !== "undefined") 15 | sessionStorage.setItem('jwt', JSON.stringify(jwt)) 16 | cb() 17 | }, 18 | signout(cb) { 19 | if (typeof window !== "undefined") 20 | sessionStorage.removeItem('jwt') 21 | cb() 22 | //optional 23 | signout().then((data) => { 24 | document.cookie = "t=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;" 25 | }) 26 | } 27 | } 28 | 29 | export default auth 30 | -------------------------------------------------------------------------------- /Chapter05/mern-social/client/core/Menu.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import AppBar from 'material-ui/AppBar' 3 | import Toolbar from 'material-ui/Toolbar' 4 | import Typography from 'material-ui/Typography' 5 | import IconButton from 'material-ui/IconButton' 6 | import HomeIcon from 'material-ui-icons/Home' 7 | import Button from 'material-ui/Button' 8 | import auth from './../auth/auth-helper' 9 | import {Link, withRouter} from 'react-router-dom' 10 | 11 | const isActive = (history, path) => { 12 | if (history.location.pathname == path) 13 | return {color: '#ffa726'} 14 | else 15 | return {color: '#ffffff'} 16 | } 17 | const Menu = withRouter(({history}) => ( 18 | 19 | 20 | 21 | MERN Social 22 | 23 | 24 | 25 | 26 | 27 | 28 | { 29 | !auth.isAuthenticated() && ( 30 | 31 | 33 | 34 | 35 | 37 | 38 | ) 39 | } 40 | { 41 | auth.isAuthenticated() && ( 42 | 43 | 44 | 45 | 48 | ) 49 | } 50 | 51 | 52 | )) 53 | 54 | export default Menu 55 | -------------------------------------------------------------------------------- /Chapter05/mern-social/client/main.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { hydrate } from 'react-dom' 3 | import App from './App' 4 | 5 | hydrate(, document.getElementById('root')) 6 | -------------------------------------------------------------------------------- /Chapter05/mern-social/client/post/Newsfeed.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import PropTypes from 'prop-types' 3 | import {withStyles} from 'material-ui/styles' 4 | import Card from 'material-ui/Card' 5 | import Typography from 'material-ui/Typography' 6 | import Divider from 'material-ui/Divider' 7 | import auth from './../auth/auth-helper' 8 | import PostList from './PostList' 9 | import {listNewsFeed} from './api-post.js' 10 | import NewPost from './NewPost' 11 | 12 | const styles = theme => ({ 13 | card: { 14 | margin: 'auto', 15 | paddingTop: 0, 16 | paddingBottom: theme.spacing.unit*3 17 | }, 18 | title: { 19 | padding:`${theme.spacing.unit * 3}px ${theme.spacing.unit * 2.5}px ${theme.spacing.unit * 2}px`, 20 | color: theme.palette.openTitle, 21 | fontSize: '1em' 22 | }, 23 | media: { 24 | minHeight: 330 25 | } 26 | }) 27 | class Newsfeed extends Component { 28 | state = { 29 | posts: [] 30 | } 31 | loadPosts = () => { 32 | const jwt = auth.isAuthenticated() 33 | listNewsFeed({ 34 | userId: jwt.user._id 35 | }, { 36 | t: jwt.token 37 | }).then((data) => { 38 | if (data.error) { 39 | console.log(data.error) 40 | } else { 41 | this.setState({posts: data}) 42 | } 43 | }) 44 | } 45 | componentDidMount = () => { 46 | this.loadPosts() 47 | } 48 | addPost = (post) => { 49 | const updatedPosts = this.state.posts 50 | updatedPosts.unshift(post) 51 | this.setState({posts: updatedPosts}) 52 | } 53 | removePost = (post) => { 54 | const updatedPosts = this.state.posts 55 | const index = updatedPosts.indexOf(post) 56 | updatedPosts.splice(index, 1) 57 | this.setState({posts: updatedPosts}) 58 | } 59 | render() { 60 | const {classes} = this.props 61 | return ( 62 | 63 | 64 | Newsfeed 65 | 66 | 67 | 68 | 69 | 70 | 71 | ) 72 | } 73 | } 74 | Newsfeed.propTypes = { 75 | classes: PropTypes.object.isRequired 76 | } 77 | 78 | export default withStyles(styles)(Newsfeed) 79 | -------------------------------------------------------------------------------- /Chapter05/mern-social/client/post/PostList.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import PropTypes from 'prop-types' 3 | import {withStyles} from 'material-ui/styles' 4 | import Post from './Post' 5 | 6 | class PostList extends Component { 7 | render() { 8 | return ( 9 |
10 | {this.props.posts.map((item, i) => { 11 | return 12 | }) 13 | } 14 |
15 | ) 16 | } 17 | } 18 | PostList.propTypes = { 19 | posts: PropTypes.array.isRequired, 20 | removeUpdate: PropTypes.func.isRequired 21 | } 22 | export default PostList 23 | -------------------------------------------------------------------------------- /Chapter05/mern-social/client/user/DeleteUser.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import PropTypes from 'prop-types' 3 | import IconButton from 'material-ui/IconButton' 4 | import Button from 'material-ui/Button' 5 | import DeleteIcon from 'material-ui-icons/Delete' 6 | import Dialog, {DialogActions, DialogContent, DialogContentText, DialogTitle} from 'material-ui/Dialog' 7 | import auth from './../auth/auth-helper' 8 | import {remove} from './api-user.js' 9 | import {Redirect, Link} from 'react-router-dom' 10 | 11 | class DeleteUser extends Component { 12 | state = { 13 | redirect: false, 14 | open: false 15 | } 16 | clickButton = () => { 17 | this.setState({open: true}) 18 | } 19 | deleteAccount = () => { 20 | const jwt = auth.isAuthenticated() 21 | remove({ 22 | userId: this.props.userId 23 | }, {t: jwt.token}).then((data) => { 24 | if (data.error) { 25 | console.log(data.error) 26 | } else { 27 | auth.signout(() => console.log('deleted')) 28 | this.setState({redirect: true}) 29 | } 30 | }) 31 | } 32 | handleRequestClose = () => { 33 | this.setState({open: false}) 34 | } 35 | render() { 36 | const redirect = this.state.redirect 37 | if (redirect) { 38 | return 39 | } 40 | return ( 41 | 42 | 43 | 44 | 45 | 46 | {"Delete Account"} 47 | 48 | 49 | Confirm to delete your account. 50 | 51 | 52 | 53 | 56 | 59 | 60 | 61 | ) 62 | } 63 | } 64 | DeleteUser.propTypes = { 65 | userId: PropTypes.string.isRequired 66 | } 67 | export default DeleteUser 68 | -------------------------------------------------------------------------------- /Chapter05/mern-social/client/user/FollowGrid.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import PropTypes from 'prop-types' 3 | import {withStyles} from 'material-ui/styles' 4 | import List, {ListItem, ListItemAvatar, ListItemIcon, ListItemSecondaryAction, ListItemText} from 'material-ui/List' 5 | import Avatar from 'material-ui/Avatar' 6 | import Button from 'material-ui/Button' 7 | import Typography from 'material-ui/Typography' 8 | import {Link} from 'react-router-dom' 9 | import GridList, { GridListTile } from 'material-ui/GridList' 10 | 11 | const styles = theme => ({ 12 | root: { 13 | paddingTop: theme.spacing.unit*2, 14 | display: 'flex', 15 | flexWrap: 'wrap', 16 | justifyContent: 'space-around', 17 | overflow: 'hidden', 18 | background: theme.palette.background.paper, 19 | }, 20 | bigAvatar: { 21 | width: 60, 22 | height: 60, 23 | margin: 'auto' 24 | }, 25 | gridList: { 26 | width: 500, 27 | height: 220, 28 | }, 29 | tileText: { 30 | textAlign: 'center', 31 | marginTop: 10 32 | } 33 | }) 34 | class FollowGrid extends Component { 35 | render() { 36 | const {classes} = this.props 37 | return (
38 | 39 | {this.props.people.map((person, i) => { 40 | return 41 | 42 | 43 | {person.name} 44 | 45 | 46 | })} 47 | 48 |
) 49 | } 50 | } 51 | 52 | FollowGrid.propTypes = { 53 | classes: PropTypes.object.isRequired, 54 | people: PropTypes.array.isRequired 55 | } 56 | 57 | export default withStyles(styles)(FollowGrid) 58 | -------------------------------------------------------------------------------- /Chapter05/mern-social/client/user/FollowProfileButton.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import PropTypes from 'prop-types' 3 | import {withStyles} from 'material-ui/styles' 4 | import Button from 'material-ui/Button' 5 | import {unfollow, follow} from './api-user.js' 6 | 7 | class FollowProfileButton extends Component { 8 | followClick = () => { 9 | this.props.onButtonClick(follow) 10 | } 11 | unfollowClick = () => { 12 | this.props.onButtonClick(unfollow) 13 | } 14 | render() { 15 | return (
16 | { this.props.following 17 | ? () 18 | : () 19 | } 20 |
) 21 | } 22 | } 23 | FollowProfileButton.propTypes = { 24 | following: PropTypes.bool.isRequired, 25 | onButtonClick: PropTypes.func.isRequired 26 | } 27 | export default FollowProfileButton 28 | -------------------------------------------------------------------------------- /Chapter05/mern-social/client/user/ProfileTabs.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import PropTypes from 'prop-types' 3 | import {withStyles} from 'material-ui/styles' 4 | import Button from 'material-ui/Button' 5 | import {Link} from 'react-router-dom' 6 | import AppBar from 'material-ui/AppBar' 7 | import Typography from 'material-ui/Typography' 8 | import Tabs, { Tab } from 'material-ui/Tabs' 9 | import FollowGrid from './../user/FollowGrid' 10 | import PostList from './../post/PostList' 11 | 12 | class ProfileTabs extends Component { 13 | state = { 14 | tab: 0, 15 | posts: [] 16 | } 17 | 18 | componentWillReceiveProps = (props) => { 19 | this.setState({tab:0}) 20 | } 21 | handleTabChange = (event, value) => { 22 | this.setState({ tab: value }) 23 | } 24 | 25 | render() { 26 | return ( 27 |
28 | 29 | 36 | 37 | 38 | 39 | 40 | 41 | {this.state.tab === 0 && } 42 | {this.state.tab === 1 && } 43 | {this.state.tab === 2 && } 44 |
) 45 | } 46 | } 47 | 48 | ProfileTabs.propTypes = { 49 | user: PropTypes.object.isRequired, 50 | removePostUpdate: PropTypes.func.isRequired, 51 | posts: PropTypes.array.isRequired 52 | } 53 | 54 | const TabContainer = (props) => { 55 | return ( 56 | 57 | {props.children} 58 | 59 | ) 60 | } 61 | 62 | TabContainer.propTypes = { 63 | children: PropTypes.node.isRequired 64 | } 65 | 66 | export default ProfileTabs 67 | -------------------------------------------------------------------------------- /Chapter05/mern-social/config/config.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | env: process.env.NODE_ENV || 'development', 3 | port: process.env.PORT || 3000, 4 | jwtSecret: process.env.JWT_SECRET || "YOUR_secret_key", 5 | mongoUri: process.env.MONGODB_URI || 6 | process.env.MONGO_HOST || 7 | 'mongodb://' + (process.env.IP || 'localhost') + ':' + 8 | (process.env.MONGO_PORT || '27017') + 9 | '/mernproject' 10 | } 11 | 12 | export default config 13 | -------------------------------------------------------------------------------- /Chapter05/mern-social/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "verbose": false, 3 | "watch": [ 4 | "./server" 5 | ], 6 | "exec": "webpack --mode=development --config webpack.config.server.js && node ./dist/server.generated.js" 7 | } 8 | -------------------------------------------------------------------------------- /Chapter05/mern-social/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mern-social", 3 | "version": "1.0.0", 4 | "description": "A MERN stack based social media application", 5 | "author": "Shama Hoque", 6 | "license": "MIT", 7 | "keywords": [ 8 | "react", 9 | "express", 10 | "mongodb", 11 | "node", 12 | "mern" 13 | ], 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/shamahoque/mern-social.git" 17 | }, 18 | "homepage": "https://github.com/shamahoque/mern-social", 19 | "main": "./dist/server.generated.js", 20 | "scripts": { 21 | "development": "nodemon", 22 | "build": "webpack --config webpack.config.client.production.js && webpack --mode=production --config webpack.config.server.js", 23 | "start": "NODE_ENV=production node ./dist/server.generated.js" 24 | }, 25 | "engines": { 26 | "node": "8.11.1", 27 | "npm": "5.8.0" 28 | }, 29 | "devDependencies": { 30 | "babel-core": "^6.26.2", 31 | "babel-loader": "^7.1.4", 32 | "babel-preset-env": "^1.6.1", 33 | "babel-preset-react": "^6.24.1", 34 | "babel-preset-stage-2": "^6.24.1", 35 | "file-loader": "^1.1.11", 36 | "nodemon": "^1.17.3", 37 | "webpack": "^4.6.0", 38 | "webpack-cli": "^2.0.15", 39 | "webpack-dev-middleware": "^3.1.3", 40 | "webpack-hot-middleware": "^2.22.1", 41 | "webpack-node-externals": "^1.7.2" 42 | }, 43 | "dependencies": { 44 | "body-parser": "^1.18.2", 45 | "compression": "^1.7.2", 46 | "cookie-parser": "^1.4.3", 47 | "cors": "^2.8.4", 48 | "express": "^4.16.3", 49 | "express-jwt": "^5.3.1", 50 | "formidable": "^1.2.1", 51 | "helmet": "^3.12.0", 52 | "jsonwebtoken": "^8.2.1", 53 | "lodash": "^4.17.10", 54 | "material-ui": "^1.0.0-beta.43", 55 | "material-ui-icons": "^1.0.0-beta.36", 56 | "mongoose": "^5.0.16", 57 | "react": "^16.3.2", 58 | "react-dom": "^16.3.2", 59 | "react-hot-loader": "^4.1.2", 60 | "react-router": "^4.2.0", 61 | "react-router-dom": "^4.2.2" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Chapter05/mern-social/server/controllers/auth.controller.js: -------------------------------------------------------------------------------- 1 | import User from '../models/user.model' 2 | import jwt from 'jsonwebtoken' 3 | import expressJwt from 'express-jwt' 4 | import config from './../../config/config' 5 | 6 | const signin = (req, res) => { 7 | User.findOne({ 8 | "email": req.body.email 9 | }, (err, user) => { 10 | 11 | if (err || !user) 12 | return res.status('401').json({ 13 | error: "User not found" 14 | }) 15 | 16 | if (!user.authenticate(req.body.password)) { 17 | return res.status('401').send({ 18 | error: "Email and password don't match." 19 | }) 20 | } 21 | 22 | const token = jwt.sign({ 23 | _id: user._id 24 | }, config.jwtSecret) 25 | 26 | res.cookie("t", token, { 27 | expire: new Date() + 9999 28 | }) 29 | 30 | return res.json({ 31 | token, 32 | user: {_id: user._id, name: user.name, email: user.email} 33 | }) 34 | 35 | }) 36 | } 37 | 38 | const signout = (req, res) => { 39 | res.clearCookie("t") 40 | return res.status('200').json({ 41 | message: "signed out" 42 | }) 43 | } 44 | 45 | const requireSignin = expressJwt({ 46 | secret: config.jwtSecret, 47 | userProperty: 'auth' 48 | }) 49 | 50 | const hasAuthorization = (req, res, next) => { 51 | const authorized = req.profile && req.auth && req.profile._id == req.auth._id 52 | if (!(authorized)) { 53 | return res.status('403').json({ 54 | error: "User is not authorized" 55 | }) 56 | } 57 | next() 58 | } 59 | 60 | export default { 61 | signin, 62 | signout, 63 | requireSignin, 64 | hasAuthorization 65 | } 66 | -------------------------------------------------------------------------------- /Chapter05/mern-social/server/devBundle.js: -------------------------------------------------------------------------------- 1 | import config from './../config/config' 2 | import webpack from 'webpack' 3 | import webpackMiddleware from 'webpack-dev-middleware' 4 | import webpackHotMiddleware from 'webpack-hot-middleware' 5 | import webpackConfig from './../webpack.config.client.js' 6 | 7 | const compile = (app) => { 8 | if(config.env === "development"){ 9 | const compiler = webpack(webpackConfig) 10 | const middleware = webpackMiddleware(compiler, { 11 | publicPath: webpackConfig.output.publicPath 12 | }) 13 | app.use(middleware) 14 | app.use(webpackHotMiddleware(compiler)) 15 | } 16 | } 17 | 18 | export default { 19 | compile 20 | } 21 | -------------------------------------------------------------------------------- /Chapter05/mern-social/server/helpers/dbErrorHandler.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Get unique error field name 5 | */ 6 | const getUniqueErrorMessage = (err) => { 7 | let output 8 | try { 9 | let fieldName = err.message.substring(err.message.lastIndexOf('.$') + 2, err.message.lastIndexOf('_1')) 10 | output = fieldName.charAt(0).toUpperCase() + fieldName.slice(1) + ' already exists' 11 | } catch (ex) { 12 | output = 'Unique field already exists' 13 | } 14 | 15 | return output 16 | } 17 | 18 | /** 19 | * Get the error message from error object 20 | */ 21 | const getErrorMessage = (err) => { 22 | let message = '' 23 | 24 | if (err.code) { 25 | switch (err.code) { 26 | case 11000: 27 | case 11001: 28 | message = getUniqueErrorMessage(err) 29 | break 30 | default: 31 | message = 'Something went wrong' 32 | } 33 | } else { 34 | for (let errName in err.errors) { 35 | if (err.errors[errName].message) message = err.errors[errName].message 36 | } 37 | } 38 | 39 | return message 40 | } 41 | 42 | export default {getErrorMessage} 43 | -------------------------------------------------------------------------------- /Chapter05/mern-social/server/models/post.model.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose' 2 | import crypto from 'crypto' 3 | const PostSchema = new mongoose.Schema({ 4 | text: { 5 | type: String, 6 | required: 'Name is required' 7 | }, 8 | photo: { 9 | data: Buffer, 10 | contentType: String 11 | }, 12 | likes: [{type: mongoose.Schema.ObjectId, ref: 'User'}], 13 | comments: [{ 14 | text: String, 15 | created: { type: Date, default: Date.now }, 16 | postedBy: { type: mongoose.Schema.ObjectId, ref: 'User'} 17 | }], 18 | postedBy: {type: mongoose.Schema.ObjectId, ref: 'User'}, 19 | created: { 20 | type: Date, 21 | default: Date.now 22 | } 23 | }) 24 | 25 | export default mongoose.model('Post', PostSchema) 26 | -------------------------------------------------------------------------------- /Chapter05/mern-social/server/models/user.model.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose' 2 | import crypto from 'crypto' 3 | const UserSchema = new mongoose.Schema({ 4 | name: { 5 | type: String, 6 | trim: true, 7 | required: 'Name is required' 8 | }, 9 | email: { 10 | type: String, 11 | trim: true, 12 | unique: 'Email already exists', 13 | match: [/.+\@.+\..+/, 'Please fill a valid email address'], 14 | required: 'Email is required' 15 | }, 16 | hashed_password: { 17 | type: String, 18 | required: "Password is required" 19 | }, 20 | salt: String, 21 | updated: Date, 22 | created: { 23 | type: Date, 24 | default: Date.now 25 | }, 26 | about: { 27 | type: String, 28 | trim: true 29 | }, 30 | photo: { 31 | data: Buffer, 32 | contentType: String 33 | }, 34 | following: [{type: mongoose.Schema.ObjectId, ref: 'User'}], 35 | followers: [{type: mongoose.Schema.ObjectId, ref: 'User'}] 36 | }) 37 | 38 | UserSchema 39 | .virtual('password') 40 | .set(function(password) { 41 | this._password = password 42 | this.salt = this.makeSalt() 43 | this.hashed_password = this.encryptPassword(password) 44 | }) 45 | .get(function() { 46 | return this._password 47 | }) 48 | 49 | UserSchema.path('hashed_password').validate(function(v) { 50 | if (this._password && this._password.length < 6) { 51 | this.invalidate('password', 'Password must be at least 6 characters.') 52 | } 53 | if (this.isNew && !this._password) { 54 | this.invalidate('password', 'Password is required') 55 | } 56 | }, null) 57 | 58 | UserSchema.methods = { 59 | authenticate: function(plainText) { 60 | return this.encryptPassword(plainText) === this.hashed_password 61 | }, 62 | encryptPassword: function(password) { 63 | if (!password) return '' 64 | try { 65 | return crypto 66 | .createHmac('sha1', this.salt) 67 | .update(password) 68 | .digest('hex') 69 | } catch (err) { 70 | return '' 71 | } 72 | }, 73 | makeSalt: function() { 74 | return Math.round((new Date().valueOf() * Math.random())) + '' 75 | } 76 | } 77 | 78 | export default mongoose.model('User', UserSchema) 79 | -------------------------------------------------------------------------------- /Chapter05/mern-social/server/routes/auth.routes.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import authCtrl from '../controllers/auth.controller' 3 | 4 | const router = express.Router() 5 | 6 | router.route('/auth/signin') 7 | .post(authCtrl.signin) 8 | router.route('/auth/signout') 9 | .get(authCtrl.signout) 10 | 11 | export default router 12 | -------------------------------------------------------------------------------- /Chapter05/mern-social/server/routes/post.routes.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import userCtrl from '../controllers/user.controller' 3 | import authCtrl from '../controllers/auth.controller' 4 | import postCtrl from '../controllers/post.controller' 5 | 6 | const router = express.Router() 7 | 8 | router.route('/api/posts/new/:userId') 9 | .post(authCtrl.requireSignin, postCtrl.create) 10 | 11 | router.route('/api/posts/photo/:postId') 12 | .get(postCtrl.photo) 13 | 14 | router.route('/api/posts/by/:userId') 15 | .get(authCtrl.requireSignin, postCtrl.listByUser) 16 | 17 | router.route('/api/posts/feed/:userId') 18 | .get(authCtrl.requireSignin, postCtrl.listNewsFeed) 19 | 20 | router.route('/api/posts/like') 21 | .put(authCtrl.requireSignin, postCtrl.like) 22 | router.route('/api/posts/unlike') 23 | .put(authCtrl.requireSignin, postCtrl.unlike) 24 | 25 | router.route('/api/posts/comment') 26 | .put(authCtrl.requireSignin, postCtrl.comment) 27 | router.route('/api/posts/uncomment') 28 | .put(authCtrl.requireSignin, postCtrl.uncomment) 29 | 30 | router.route('/api/posts/:postId') 31 | .delete(authCtrl.requireSignin, postCtrl.isPoster, postCtrl.remove) 32 | 33 | router.param('userId', userCtrl.userByID) 34 | router.param('postId', postCtrl.postByID) 35 | 36 | export default router 37 | -------------------------------------------------------------------------------- /Chapter05/mern-social/server/routes/user.routes.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import userCtrl from '../controllers/user.controller' 3 | import authCtrl from '../controllers/auth.controller' 4 | 5 | const router = express.Router() 6 | 7 | router.route('/api/users') 8 | .get(userCtrl.list) 9 | .post(userCtrl.create) 10 | 11 | router.route('/api/users/photo/:userId') 12 | .get(userCtrl.photo, userCtrl.defaultPhoto) 13 | router.route('/api/users/defaultphoto') 14 | .get(userCtrl.defaultPhoto) 15 | 16 | router.route('/api/users/follow') 17 | .put(authCtrl.requireSignin, userCtrl.addFollowing, userCtrl.addFollower) 18 | router.route('/api/users/unfollow') 19 | .put(authCtrl.requireSignin, userCtrl.removeFollowing, userCtrl.removeFollower) 20 | 21 | router.route('/api/users/findpeople/:userId') 22 | .get(authCtrl.requireSignin, userCtrl.findPeople) 23 | 24 | router.route('/api/users/:userId') 25 | .get(authCtrl.requireSignin, userCtrl.read) 26 | .put(authCtrl.requireSignin, authCtrl.hasAuthorization, userCtrl.update) 27 | .delete(authCtrl.requireSignin, authCtrl.hasAuthorization, userCtrl.remove) 28 | 29 | router.param('userId', userCtrl.userByID) 30 | 31 | export default router 32 | -------------------------------------------------------------------------------- /Chapter05/mern-social/server/server.js: -------------------------------------------------------------------------------- 1 | import config from './../config/config' 2 | import app from './express' 3 | import mongoose from 'mongoose' 4 | 5 | // Connection URL 6 | mongoose.Promise = global.Promise 7 | mongoose.connect(config.mongoUri) 8 | mongoose.connection.on('error', () => { 9 | throw new Error(`unable to connect to database: ${mongoUri}`) 10 | }) 11 | 12 | app.listen(config.port, (err) => { 13 | if (err) { 14 | console.log(err) 15 | } 16 | console.info('Server started on port %s.', config.port) 17 | }) 18 | -------------------------------------------------------------------------------- /Chapter05/mern-social/template.js: -------------------------------------------------------------------------------- 1 | export default ({markup, css}) => { 2 | return ` 3 | 4 | 5 | 6 | MERN Social 7 | 8 | 9 | 14 | 15 | 16 |
${markup}
17 | 18 | 19 | 20 | ` 21 | } 22 | -------------------------------------------------------------------------------- /Chapter05/mern-social/webpack.config.client.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | const CURRENT_WORKING_DIR = process.cwd() 4 | 5 | const config = { 6 | name: "browser", 7 | mode: "development", 8 | devtool: 'eval-source-map', 9 | entry: [ 10 | 'react-hot-loader/patch', 11 | 'webpack-hot-middleware/client?reload=true', 12 | path.join(CURRENT_WORKING_DIR, 'client/main.js') 13 | ], 14 | output: { 15 | path: path.join(CURRENT_WORKING_DIR , '/dist'), 16 | filename: 'bundle.js', 17 | publicPath: '/dist/' 18 | }, 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.jsx?$/, 23 | exclude: /node_modules/, 24 | use: [ 25 | 'babel-loader' 26 | ] 27 | }, 28 | { 29 | test: /\.(ttf|eot|svg|gif|jpg|png)(\?[\s\S]+)?$/, 30 | use: 'file-loader' 31 | } 32 | ] 33 | }, plugins: [ 34 | new webpack.HotModuleReplacementPlugin(), 35 | new webpack.NoEmitOnErrorsPlugin() 36 | ] 37 | } 38 | 39 | module.exports = config 40 | -------------------------------------------------------------------------------- /Chapter05/mern-social/webpack.config.client.production.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | const CURRENT_WORKING_DIR = process.cwd() 4 | 5 | const config = { 6 | mode: "production", 7 | entry: [ 8 | path.join(CURRENT_WORKING_DIR, 'client/main.js') 9 | ], 10 | output: { 11 | path: path.join(CURRENT_WORKING_DIR , '/dist'), 12 | filename: 'bundle.js', 13 | publicPath: "/dist/" 14 | }, 15 | module: { 16 | rules: [ 17 | { 18 | test: /\.jsx?$/, 19 | exclude: /node_modules/, 20 | use: [ 21 | 'babel-loader' 22 | ] 23 | }, 24 | { 25 | test: /\.(ttf|eot|svg|gif|jpg|png)(\?[\s\S]+)?$/, 26 | use: 'file-loader' 27 | } 28 | ] 29 | } 30 | } 31 | 32 | module.exports = config 33 | -------------------------------------------------------------------------------- /Chapter05/mern-social/webpack.config.server.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | const nodeExternals = require('webpack-node-externals') 4 | const CURRENT_WORKING_DIR = process.cwd() 5 | 6 | const config = { 7 | name: "server", 8 | entry: [ path.join(CURRENT_WORKING_DIR , './server/server.js') ], 9 | target: "node", 10 | output: { 11 | path: path.join(CURRENT_WORKING_DIR , '/dist/'), 12 | filename: "server.generated.js", 13 | publicPath: '/dist/', 14 | libraryTarget: "commonjs2" 15 | }, 16 | externals: [nodeExternals()], 17 | module: { 18 | rules: [ 19 | { 20 | test: /\.js$/, 21 | exclude: /node_modules/, 22 | use: [ 'babel-loader' ] 23 | }, 24 | { 25 | test: /\.(ttf|eot|svg|gif|jpg|png)(\?[\s\S]+)?$/, 26 | use: 'file-loader' 27 | } 28 | ] 29 | } 30 | } 31 | 32 | module.exports = config 33 | -------------------------------------------------------------------------------- /Chapter06 and 07/mern-marketplace/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "env", 4 | "stage-2", 5 | "react" 6 | ], 7 | "plugins": [ 8 | "react-hot-loader/babel" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /Chapter06 and 07/mern-marketplace/LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Shama Hoque 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Chapter06 and 07/mern-marketplace/README.md: -------------------------------------------------------------------------------- 1 | # MERN Marketplace 2 | 3 | ### [Live Demo](http://marketplace.mernbook.com/ "MERN Marketplace") 4 | 5 | #### What you need to run this code 6 | 1. Node (8.11.1) 7 | 2. NPM (5.8.0) 8 | 3. MongoDB (3.6.3) 9 | 10 | #### How to run this code 11 | 1. Clone this repository 12 | 2. Open command line in the cloned folder, 13 | - To install dependencies, run ``` npm install ``` 14 | - To run the application for development, run ``` npm run development ``` 15 | 4. Open [localhost:3000](http://localhost:3000/) in the browser 16 | -------------------------------------------------------------------------------- /Chapter06 and 07/mern-marketplace/client/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import MainRouter from './MainRouter' 3 | import {BrowserRouter} from 'react-router-dom' 4 | import {MuiThemeProvider, createMuiTheme} from 'material-ui/styles' 5 | import { blueGrey, lightGreen } from 'material-ui/colors' 6 | import { hot } from 'react-hot-loader' 7 | 8 | // Create a theme instance. 9 | const theme = createMuiTheme({ 10 | palette: { 11 | primary: { 12 | light: '#8eacbb', 13 | main: '#607d8b', 14 | dark: '#34515e', 15 | contrastText: '#fff', 16 | }, 17 | secondary: { 18 | light: '#e7ff8c', 19 | main: '#b2ff59', 20 | dark: '#7ecb20', 21 | contrastText: '#000', 22 | }, 23 | openTitle: blueGrey['400'], 24 | protectedTitle: lightGreen['400'], 25 | type: 'light' 26 | } 27 | }) 28 | 29 | const App = () => ( 30 | 31 | 32 | 33 | 34 | 35 | ) 36 | 37 | export default hot(module)(App) 38 | -------------------------------------------------------------------------------- /Chapter06 and 07/mern-marketplace/client/assets/images/profile-pic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Full-Stack-React-Projects/93940878b3429a415af672d0f3751a2f7e2fe1ca/Chapter06 and 07/mern-marketplace/client/assets/images/profile-pic.png -------------------------------------------------------------------------------- /Chapter06 and 07/mern-marketplace/client/assets/images/seashell.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Full-Stack-React-Projects/93940878b3429a415af672d0f3751a2f7e2fe1ca/Chapter06 and 07/mern-marketplace/client/assets/images/seashell.jpg -------------------------------------------------------------------------------- /Chapter06 and 07/mern-marketplace/client/assets/images/stripeButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Full-Stack-React-Projects/93940878b3429a415af672d0f3751a2f7e2fe1ca/Chapter06 and 07/mern-marketplace/client/assets/images/stripeButton.png -------------------------------------------------------------------------------- /Chapter06 and 07/mern-marketplace/client/auth/PrivateRoute.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Route, Redirect } from 'react-router-dom' 3 | import auth from './auth-helper' 4 | 5 | const PrivateRoute = ({ component: Component, ...rest }) => ( 6 | ( 7 | auth.isAuthenticated() ? ( 8 | 9 | ) : ( 10 | 14 | ) 15 | )}/> 16 | ) 17 | 18 | export default PrivateRoute 19 | -------------------------------------------------------------------------------- /Chapter06 and 07/mern-marketplace/client/auth/api-auth.js: -------------------------------------------------------------------------------- 1 | const signin = (user) => { 2 | return fetch('/auth/signin/', { 3 | method: 'POST', 4 | headers: { 5 | 'Accept': 'application/json', 6 | 'Content-Type': 'application/json' 7 | }, 8 | credentials: 'include', 9 | body: JSON.stringify(user) 10 | }) 11 | .then((response) => { 12 | return response.json() 13 | }).catch((err) => console.log(err)) 14 | } 15 | 16 | const signout = () => { 17 | return fetch('/auth/signout/', { 18 | method: 'GET', 19 | }).then(response => { 20 | return response.json() 21 | }).catch((err) => console.log(err)) 22 | } 23 | 24 | export { 25 | signin, 26 | signout 27 | } 28 | -------------------------------------------------------------------------------- /Chapter06 and 07/mern-marketplace/client/auth/auth-helper.js: -------------------------------------------------------------------------------- 1 | import { signout } from './api-auth.js' 2 | 3 | const auth = { 4 | isAuthenticated() { 5 | if (typeof window == "undefined") 6 | return false 7 | 8 | if (sessionStorage.getItem('jwt')) 9 | return JSON.parse(sessionStorage.getItem('jwt')) 10 | else 11 | return false 12 | }, 13 | authenticate(jwt, cb) { 14 | if (typeof window !== "undefined") 15 | sessionStorage.setItem('jwt', JSON.stringify(jwt)) 16 | cb() 17 | }, 18 | signout(cb) { 19 | if (typeof window !== "undefined") 20 | sessionStorage.removeItem('jwt') 21 | cb() 22 | //optional 23 | signout().then((data) => { 24 | document.cookie = "t=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;" 25 | }) 26 | }, 27 | updateUser(user, cb) { 28 | if(typeof window !== "undefined"){ 29 | if(sessionStorage.getItem('jwt')){ 30 | let auth = JSON.parse(sessionStorage.getItem('jwt')) 31 | auth.user = user 32 | sessionStorage.setItem('jwt', JSON.stringify(auth)) 33 | cb() 34 | } 35 | } 36 | } 37 | } 38 | 39 | export default auth 40 | -------------------------------------------------------------------------------- /Chapter06 and 07/mern-marketplace/client/cart/AddToCart.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import {withStyles} from 'material-ui/styles' 3 | import PropTypes from 'prop-types' 4 | import IconButton from 'material-ui/IconButton' 5 | import AddCartIcon from 'material-ui-icons/AddShoppingCart' 6 | import DisabledCartIcon from 'material-ui-icons/RemoveShoppingCart' 7 | import cart from './cart-helper.js' 8 | import { Redirect } from 'react-router-dom' 9 | 10 | const styles = theme => ({ 11 | iconButton: { 12 | width: '28px', 13 | height: '28px' 14 | }, 15 | disabledIconButton: { 16 | color: '#7f7563', 17 | width: '28px', 18 | height: '28px' 19 | } 20 | }) 21 | 22 | class AddToCart extends Component { 23 | state = { 24 | redirect: false 25 | } 26 | addToCart = () => { 27 | cart.addItem(this.props.item, () => { 28 | this.setState({redirect:true}) 29 | }) 30 | } 31 | render() { 32 | if (this.state.redirect) { 33 | return () 34 | } 35 | const {classes} = this.props 36 | return ( 37 | {this.props.item.quantity >= 0 ? 38 | 39 | 40 | : 41 | 42 | 43 | } 44 | ) 45 | } 46 | } 47 | 48 | AddToCart.propTypes = { 49 | classes: PropTypes.object.isRequired, 50 | item: PropTypes.object.isRequired, 51 | cartStyle: PropTypes.string 52 | } 53 | 54 | export default withStyles(styles)(AddToCart) 55 | -------------------------------------------------------------------------------- /Chapter06 and 07/mern-marketplace/client/cart/Cart.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import Grid from 'material-ui/Grid' 3 | import PropTypes from 'prop-types' 4 | import {withStyles} from 'material-ui/styles' 5 | import CartItems from './CartItems' 6 | import {StripeProvider} from 'react-stripe-elements' 7 | import config from './../../config/config' 8 | import Checkout from './Checkout' 9 | 10 | const styles = theme => ({ 11 | root: { 12 | flexGrow: 1, 13 | margin: 30, 14 | } 15 | }) 16 | 17 | class Cart extends Component { 18 | state = { 19 | checkout: false, 20 | stripe: null 21 | } 22 | 23 | componentDidMount = () => { 24 | if (window.Stripe) { 25 | this.setState({stripe: window.Stripe(config.stripe_test_api_key)}) 26 | } else { 27 | document.querySelector('#stripe-js').addEventListener('load', () => { 28 | // Create Stripe instance once Stripe.js loads 29 | this.setState({stripe: window.Stripe(config.stripe_test_api_key)}) 30 | }) 31 | } 32 | } 33 | 34 | setCheckout = val =>{ 35 | this.setState({checkout: val}) 36 | } 37 | 38 | render() { 39 | const {classes} = this.props 40 | return (
41 | 42 | 43 | 45 | 46 | {this.state.checkout && 47 | 48 | 49 | 50 | 51 | } 52 | 53 |
) 54 | } 55 | } 56 | 57 | Cart.propTypes = { 58 | classes: PropTypes.object.isRequired 59 | } 60 | 61 | export default withStyles(styles)(Cart) 62 | -------------------------------------------------------------------------------- /Chapter06 and 07/mern-marketplace/client/cart/cart-helper.js: -------------------------------------------------------------------------------- 1 | const cart = { 2 | itemTotal() { 3 | if (typeof window !== "undefined") { 4 | if (localStorage.getItem('cart')) { 5 | return JSON.parse(localStorage.getItem('cart')).length 6 | } 7 | } 8 | return 0 9 | }, 10 | addItem(item, cb) { 11 | let cart = [] 12 | if (typeof window !== "undefined") { 13 | if (localStorage.getItem('cart')) { 14 | cart = JSON.parse(localStorage.getItem('cart')) 15 | } 16 | cart.push({ 17 | product: item, 18 | quantity: 1, 19 | shop: item.shop._id 20 | }) 21 | localStorage.setItem('cart', JSON.stringify(cart)) 22 | cb() 23 | } 24 | }, 25 | updateCart(itemIndex, quantity) { 26 | let cart = [] 27 | if (typeof window !== "undefined") { 28 | if (localStorage.getItem('cart')) { 29 | cart = JSON.parse(localStorage.getItem('cart')) 30 | } 31 | cart[itemIndex].quantity = quantity 32 | localStorage.setItem('cart', JSON.stringify(cart)) 33 | } 34 | }, 35 | getCart() { 36 | if (typeof window !== "undefined") { 37 | if (localStorage.getItem('cart')) { 38 | return JSON.parse(localStorage.getItem('cart')) 39 | } 40 | } 41 | return [] 42 | }, 43 | removeItem(itemIndex) { 44 | let cart = [] 45 | if (typeof window !== "undefined") { 46 | if (localStorage.getItem('cart')) { 47 | cart = JSON.parse(localStorage.getItem('cart')) 48 | } 49 | cart.splice(itemIndex, 1) 50 | localStorage.setItem('cart', JSON.stringify(cart)) 51 | } 52 | return cart 53 | }, 54 | emptyCart(cb) { 55 | if (typeof window !== "undefined") { 56 | localStorage.removeItem('cart') 57 | cb() 58 | } 59 | } 60 | } 61 | 62 | export default cart 63 | -------------------------------------------------------------------------------- /Chapter06 and 07/mern-marketplace/client/core/Home.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import PropTypes from 'prop-types' 3 | import {withStyles} from 'material-ui/styles' 4 | import Grid from 'material-ui/Grid' 5 | import Suggestions from './../product/Suggestions' 6 | import {listLatest, listCategories} from './../product/api-product.js' 7 | import Search from './../product/Search' 8 | import Categories from './../product/Categories' 9 | 10 | const styles = theme => ({ 11 | root: { 12 | flexGrow: 1, 13 | margin: 30, 14 | } 15 | }) 16 | 17 | class Home extends Component { 18 | state={ 19 | suggestionTitle: "Latest Products", 20 | suggestions: [], 21 | categories: [] 22 | } 23 | componentDidMount = () => { 24 | listLatest().then((data) => { 25 | if (data.error) { 26 | console.log(data.error) 27 | } else { 28 | this.setState({suggestions: data}) 29 | } 30 | }) 31 | listCategories().then((data) => { 32 | if (data.error) { 33 | console.log(data.error) 34 | } else { 35 | this.setState({categories: data}) 36 | } 37 | }) 38 | } 39 | render() { 40 | const {classes} = this.props 41 | return ( 42 |
43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 |
53 | ) 54 | } 55 | } 56 | 57 | Home.propTypes = { 58 | classes: PropTypes.object.isRequired 59 | } 60 | 61 | export default withStyles(styles)(Home) 62 | -------------------------------------------------------------------------------- /Chapter06 and 07/mern-marketplace/client/main.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { hydrate } from 'react-dom' 3 | import App from './App' 4 | 5 | hydrate(, document.getElementById('root')) 6 | -------------------------------------------------------------------------------- /Chapter06 and 07/mern-marketplace/client/order/MyOrders.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import PropTypes from 'prop-types' 3 | import {withStyles} from 'material-ui/styles' 4 | import Paper from 'material-ui/Paper' 5 | import List, {ListItem, ListItemText} from 'material-ui/List' 6 | import Typography from 'material-ui/Typography' 7 | import Divider from 'material-ui/Divider' 8 | import auth from './../auth/auth-helper' 9 | import {listByUser} from './api-order.js' 10 | import {Link} from 'react-router-dom' 11 | 12 | const styles = theme => ({ 13 | root: theme.mixins.gutters({ 14 | maxWidth: 600, 15 | margin: '12px 24px', 16 | padding: theme.spacing.unit * 3, 17 | backgroundColor: '#3f3f3f0d' 18 | }), 19 | title: { 20 | margin: `${theme.spacing.unit * 2}px 0 12px ${theme.spacing.unit}px` , 21 | color: theme.palette.openTitle 22 | } 23 | }) 24 | class MyOrders extends Component { 25 | state = { 26 | orders:[] 27 | } 28 | 29 | loadOrders = () => { 30 | const jwt = auth.isAuthenticated() 31 | listByUser({ 32 | userId: jwt.user._id 33 | }, {t: jwt.token}).then((data) => { 34 | if (data.error) { 35 | console.log(data.error) 36 | } else { 37 | this.setState({orders: data}) 38 | } 39 | }) 40 | } 41 | 42 | componentDidMount = () => { 43 | this.loadOrders() 44 | } 45 | 46 | render() { 47 | const {classes} = this.props 48 | return ( 49 | 50 | 51 | Your Orders 52 | 53 | 54 | {this.state.orders.map((order, i) => { 55 | return 56 | 57 | 58 | {"Order # "+order._id}} secondary={(new Date(order.created)).toDateString()}/> 59 | 60 | 61 | 62 | })} 63 | 64 | 65 | ) 66 | } 67 | } 68 | 69 | MyOrders.propTypes = { 70 | classes: PropTypes.object.isRequired 71 | } 72 | 73 | export default withStyles(styles)(MyOrders) 74 | -------------------------------------------------------------------------------- /Chapter06 and 07/mern-marketplace/client/product/DeleteProduct.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import PropTypes from 'prop-types' 3 | import IconButton from 'material-ui/IconButton' 4 | import Button from 'material-ui/Button' 5 | import DeleteIcon from 'material-ui-icons/Delete' 6 | import Dialog, {DialogActions, DialogContent, DialogContentText, DialogTitle} from 'material-ui/Dialog' 7 | import auth from './../auth/auth-helper' 8 | import {remove} from './api-product.js' 9 | 10 | class DeleteProduct extends Component { 11 | state = { 12 | open: false 13 | } 14 | clickButton = () => { 15 | this.setState({open: true}) 16 | } 17 | deleteProduct = () => { 18 | const jwt = auth.isAuthenticated() 19 | remove({ 20 | shopId: this.props.shopId, 21 | productId: this.props.product._id 22 | }, {t: jwt.token}).then((data) => { 23 | if (data.error) { 24 | console.log(data.error) 25 | } else { 26 | this.setState({open: false}, () => { 27 | this.props.onRemove(this.props.product) 28 | }) 29 | } 30 | }) 31 | } 32 | handleRequestClose = () => { 33 | this.setState({open: false}) 34 | } 35 | render() { 36 | return ( 37 | 38 | 39 | 40 | 41 | {"Delete "+this.props.product.name} 42 | 43 | 44 | Confirm to delete your product {this.props.product.name}. 45 | 46 | 47 | 48 | 51 | 54 | 55 | 56 | ) 57 | } 58 | } 59 | DeleteProduct.propTypes = { 60 | shopId: PropTypes.string.isRequired, 61 | product: PropTypes.object.isRequired, 62 | onRemove: PropTypes.func.isRequired 63 | } 64 | export default DeleteProduct 65 | -------------------------------------------------------------------------------- /Chapter06 and 07/mern-marketplace/client/shop/DeleteShop.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import PropTypes from 'prop-types' 3 | import IconButton from 'material-ui/IconButton' 4 | import Button from 'material-ui/Button' 5 | import DeleteIcon from 'material-ui-icons/Delete' 6 | import Dialog, {DialogActions, DialogContent, DialogContentText, DialogTitle} from 'material-ui/Dialog' 7 | import auth from './../auth/auth-helper' 8 | import {remove} from './api-shop.js' 9 | 10 | class DeleteShop extends Component { 11 | state = { 12 | open: false 13 | } 14 | clickButton = () => { 15 | this.setState({open: true}) 16 | } 17 | deleteShop = () => { 18 | const jwt = auth.isAuthenticated() 19 | remove({ 20 | shopId: this.props.shop._id 21 | }, {t: jwt.token}).then((data) => { 22 | if (data.error) { 23 | console.log(data.error) 24 | } else { 25 | this.setState({open: false}, () => { 26 | this.props.onRemove(this.props.shop) 27 | }) 28 | } 29 | }) 30 | } 31 | handleRequestClose = () => { 32 | this.setState({open: false}) 33 | } 34 | render() { 35 | return ( 36 | 37 | 38 | 39 | 40 | 41 | {"Delete "+this.props.shop.name} 42 | 43 | 44 | Confirm to delete your shop {this.props.shop.name}. 45 | 46 | 47 | 48 | 51 | 54 | 55 | 56 | ) 57 | } 58 | } 59 | DeleteShop.propTypes = { 60 | shop: PropTypes.object.isRequired, 61 | onRemove: PropTypes.func.isRequired 62 | } 63 | export default DeleteShop 64 | -------------------------------------------------------------------------------- /Chapter06 and 07/mern-marketplace/client/shop/api-shop.js: -------------------------------------------------------------------------------- 1 | const create = (params, credentials, shop) => { 2 | return fetch('/api/shops/by/'+ params.userId, { 3 | method: 'POST', 4 | headers: { 5 | 'Accept': 'application/json', 6 | 'Authorization': 'Bearer ' + credentials.t 7 | }, 8 | body: shop 9 | }) 10 | .then((response) => { 11 | return response.json() 12 | }).catch((err) => console.log(err)) 13 | } 14 | 15 | const list = () => { 16 | return fetch('/api/shops', { 17 | method: 'GET', 18 | }).then(response => { 19 | return response.json() 20 | }).catch((err) => console.log(err)) 21 | } 22 | 23 | const listByOwner = (params, credentials) => { 24 | return fetch('/api/shops/by/'+params.userId, { 25 | method: 'GET', 26 | headers: { 27 | 'Accept': 'application/json', 28 | 'Authorization': 'Bearer ' + credentials.t 29 | } 30 | }).then((response) => { 31 | return response.json() 32 | }).catch((err) => { 33 | console.log(err) 34 | }) 35 | } 36 | 37 | const read = (params, credentials) => { 38 | return fetch('/api/shop/' + params.shopId, { 39 | method: 'GET' 40 | }).then((response) => { 41 | return response.json() 42 | }).catch((err) => console.log(err)) 43 | } 44 | 45 | const update = (params, credentials, shop) => { 46 | return fetch('/api/shops/' + params.shopId, { 47 | method: 'PUT', 48 | headers: { 49 | 'Accept': 'application/json', 50 | 'Authorization': 'Bearer ' + credentials.t 51 | }, 52 | body: shop 53 | }).then((response) => { 54 | return response.json() 55 | }).catch((err) => { 56 | console.log(err) 57 | }) 58 | } 59 | 60 | const remove = (params, credentials) => { 61 | return fetch('/api/shops/' + params.shopId, { 62 | method: 'DELETE', 63 | headers: { 64 | 'Accept': 'application/json', 65 | 'Content-Type': 'application/json', 66 | 'Authorization': 'Bearer ' + credentials.t 67 | } 68 | }).then((response) => { 69 | return response.json() 70 | }).catch((err) => { 71 | console.log(err) 72 | }) 73 | } 74 | 75 | export { 76 | create, 77 | list, 78 | listByOwner, 79 | read, 80 | update, 81 | remove 82 | } 83 | -------------------------------------------------------------------------------- /Chapter06 and 07/mern-marketplace/client/user/DeleteUser.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import PropTypes from 'prop-types' 3 | import IconButton from 'material-ui/IconButton' 4 | import Button from 'material-ui/Button' 5 | import DeleteIcon from 'material-ui-icons/Delete' 6 | import Dialog, {DialogActions, DialogContent, DialogContentText, DialogTitle} from 'material-ui/Dialog' 7 | import auth from './../auth/auth-helper' 8 | import {remove} from './api-user.js' 9 | import {Redirect, Link} from 'react-router-dom' 10 | 11 | class DeleteUser extends Component { 12 | state = { 13 | redirect: false, 14 | open: false 15 | } 16 | clickButton = () => { 17 | this.setState({open: true}) 18 | } 19 | deleteAccount = () => { 20 | const jwt = auth.isAuthenticated() 21 | remove({ 22 | userId: this.props.userId 23 | }, {t: jwt.token}).then((data) => { 24 | if (data.error) { 25 | console.log(data.error) 26 | } else { 27 | auth.signout(() => console.log('deleted')) 28 | this.setState({redirect: true}) 29 | } 30 | }) 31 | } 32 | handleRequestClose = () => { 33 | this.setState({open: false}) 34 | } 35 | render() { 36 | const redirect = this.state.redirect 37 | if (redirect) { 38 | return 39 | } 40 | return ( 41 | 42 | 43 | 44 | 45 | 46 | {"Delete Account"} 47 | 48 | 49 | Confirm to delete your account. 50 | 51 | 52 | 53 | 56 | 59 | 60 | 61 | ) 62 | } 63 | } 64 | DeleteUser.propTypes = { 65 | userId: PropTypes.string.isRequired 66 | } 67 | export default DeleteUser 68 | -------------------------------------------------------------------------------- /Chapter06 and 07/mern-marketplace/config/config.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | env: process.env.NODE_ENV || 'development', 3 | port: process.env.PORT || 3000, 4 | jwtSecret: process.env.JWT_SECRET || "YOUR_secret_key", 5 | mongoUri: process.env.MONGODB_URI || 6 | process.env.MONGO_HOST || 7 | 'mongodb://' + (process.env.IP || 'localhost') + ':' + 8 | (process.env.MONGO_PORT || '27017') + 9 | '/mernproject', 10 | stripe_connect_test_client_id: 'YOUR_stripe_connect_test_client', 11 | stripe_test_secret_key: 'YOUR_stripe_test_secret_key', 12 | stripe_test_api_key: 'YOUR_stripe_test_api_key' 13 | } 14 | 15 | export default config 16 | -------------------------------------------------------------------------------- /Chapter06 and 07/mern-marketplace/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "verbose": false, 3 | "watch": [ 4 | "./server" 5 | ], 6 | "exec": "webpack --mode=development --config webpack.config.server.js && node ./dist/server.generated.js" 7 | } 8 | -------------------------------------------------------------------------------- /Chapter06 and 07/mern-marketplace/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mern-marketplace", 3 | "version": "1.0.0", 4 | "description": "A MERN stack based online marketplace application", 5 | "author": "Shama Hoque", 6 | "license": "MIT", 7 | "keywords": [ 8 | "react", 9 | "express", 10 | "mongodb", 11 | "node", 12 | "mern" 13 | ], 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/shamahoque/mern-marketplace.git" 17 | }, 18 | "homepage": "https://github.com/shamahoque/mern-marketplace", 19 | "main": "./dist/server.generated.js", 20 | "scripts": { 21 | "development": "nodemon", 22 | "build": "webpack --config webpack.config.client.production.js && webpack --mode=production --config webpack.config.server.js", 23 | "start": "NODE_ENV=production node ./dist/server.generated.js" 24 | }, 25 | "engines": { 26 | "node": "8.11.1", 27 | "npm": "5.8.0" 28 | }, 29 | "devDependencies": { 30 | "babel-core": "^6.26.2", 31 | "babel-loader": "^7.1.4", 32 | "babel-preset-env": "^1.6.1", 33 | "babel-preset-react": "^6.24.1", 34 | "babel-preset-stage-2": "^6.24.1", 35 | "file-loader": "^1.1.11", 36 | "nodemon": "^1.17.3", 37 | "webpack": "^4.6.0", 38 | "webpack-cli": "^2.0.15", 39 | "webpack-dev-middleware": "^3.1.3", 40 | "webpack-hot-middleware": "^2.22.1", 41 | "webpack-node-externals": "^1.7.2" 42 | }, 43 | "dependencies": { 44 | "body-parser": "^1.18.2", 45 | "compression": "^1.7.2", 46 | "cookie-parser": "^1.4.3", 47 | "cors": "^2.8.4", 48 | "express": "^4.16.3", 49 | "express-jwt": "^5.3.1", 50 | "formidable": "^1.2.1", 51 | "helmet": "^3.12.0", 52 | "jsonwebtoken": "^8.2.1", 53 | "lodash": "^4.17.10", 54 | "material-ui": "^1.0.0-beta.43", 55 | "material-ui-icons": "^1.0.0-beta.36", 56 | "mongoose": "^5.0.16", 57 | "query-string": "^6.0.0", 58 | "react": "^16.3.2", 59 | "react-dom": "^16.3.2", 60 | "react-hot-loader": "^4.1.2", 61 | "react-router": "^4.2.0", 62 | "react-router-dom": "^4.2.2", 63 | "react-stripe-elements": "^1.6.0", 64 | "request": "^2.85.0", 65 | "stripe": "^5.8.0" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Chapter06 and 07/mern-marketplace/server/controllers/auth.controller.js: -------------------------------------------------------------------------------- 1 | import User from '../models/user.model' 2 | import jwt from 'jsonwebtoken' 3 | import expressJwt from 'express-jwt' 4 | import config from './../../config/config' 5 | 6 | const signin = (req, res) => { 7 | User.findOne({ 8 | "email": req.body.email 9 | }, (err, user) => { 10 | 11 | if (err || !user) 12 | return res.status('401').json({ 13 | error: "User not found" 14 | }) 15 | 16 | if (!user.authenticate(req.body.password)) { 17 | return res.status('401').send({ 18 | error: "Email and password don't match." 19 | }) 20 | } 21 | 22 | const token = jwt.sign({ 23 | _id: user._id 24 | }, config.jwtSecret) 25 | 26 | res.cookie("t", token, { 27 | expire: new Date() + 9999 28 | }) 29 | 30 | return res.json({ 31 | token, 32 | user: {_id: user._id, name: user.name, email: user.email, seller: user.seller} 33 | }) 34 | 35 | }) 36 | } 37 | 38 | const signout = (req, res) => { 39 | res.clearCookie("t") 40 | return res.status('200').json({ 41 | message: "signed out" 42 | }) 43 | } 44 | 45 | const requireSignin = expressJwt({ 46 | secret: config.jwtSecret, 47 | userProperty: 'auth' 48 | }) 49 | 50 | const hasAuthorization = (req, res, next) => { 51 | const authorized = req.profile && req.auth && req.profile._id == req.auth._id 52 | if (!(authorized)) { 53 | return res.status('403').json({ 54 | error: "User is not authorized" 55 | }) 56 | } 57 | next() 58 | } 59 | 60 | export default { 61 | signin, 62 | signout, 63 | requireSignin, 64 | hasAuthorization 65 | } 66 | -------------------------------------------------------------------------------- /Chapter06 and 07/mern-marketplace/server/devBundle.js: -------------------------------------------------------------------------------- 1 | import config from './../config/config' 2 | import webpack from 'webpack' 3 | import webpackMiddleware from 'webpack-dev-middleware' 4 | import webpackHotMiddleware from 'webpack-hot-middleware' 5 | import webpackConfig from './../webpack.config.client.js' 6 | 7 | const compile = (app) => { 8 | if(config.env === "development"){ 9 | const compiler = webpack(webpackConfig) 10 | const middleware = webpackMiddleware(compiler, { 11 | publicPath: webpackConfig.output.publicPath 12 | }) 13 | app.use(middleware) 14 | app.use(webpackHotMiddleware(compiler)) 15 | } 16 | } 17 | 18 | export default { 19 | compile 20 | } 21 | -------------------------------------------------------------------------------- /Chapter06 and 07/mern-marketplace/server/helpers/dbErrorHandler.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Get unique error field name 5 | */ 6 | const getUniqueErrorMessage = (err) => { 7 | let output 8 | try { 9 | let fieldName = err.message.substring(err.message.lastIndexOf('.$') + 2, err.message.lastIndexOf('_1')) 10 | output = fieldName.charAt(0).toUpperCase() + fieldName.slice(1) + ' already exists' 11 | } catch (ex) { 12 | output = 'Unique field already exists' 13 | } 14 | 15 | return output 16 | } 17 | 18 | /** 19 | * Get the error message from error object 20 | */ 21 | const getErrorMessage = (err) => { 22 | let message = '' 23 | 24 | if (err.code) { 25 | switch (err.code) { 26 | case 11000: 27 | case 11001: 28 | message = getUniqueErrorMessage(err) 29 | break 30 | default: 31 | message = 'Something went wrong' 32 | } 33 | } else { 34 | for (let errName in err.errors) { 35 | if (err.errors[errName].message) message = err.errors[errName].message 36 | } 37 | } 38 | 39 | return message 40 | } 41 | 42 | export default {getErrorMessage} 43 | -------------------------------------------------------------------------------- /Chapter06 and 07/mern-marketplace/server/models/order.model.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose' 2 | import crypto from 'crypto' 3 | const CartItemSchema = new mongoose.Schema({ 4 | product: {type: mongoose.Schema.ObjectId, ref: 'Product'}, 5 | quantity: Number, 6 | shop: {type: mongoose.Schema.ObjectId, ref: 'Shop'}, 7 | status: {type: String, 8 | default: 'Not processed', 9 | enum: ['Not processed' , 'Processing', 'Shipped', 'Delivered', 'Cancelled']} 10 | }) 11 | const CartItem = mongoose.model('CartItem', CartItemSchema) 12 | const OrderSchema = new mongoose.Schema({ 13 | products: [CartItemSchema], 14 | customer_name: { 15 | type: String, 16 | trim: true, 17 | required: 'Name is required' 18 | }, 19 | customer_email: { 20 | type: String, 21 | trim: true, 22 | match: [/.+\@.+\..+/, 'Please fill a valid email address'], 23 | required: 'Email is required' 24 | }, 25 | delivery_address: { 26 | street: {type: String, required: 'Street is required'}, 27 | city: {type: String, required: 'City is required'}, 28 | state: {type: String}, 29 | zipcode: {type: String, required: 'Zip Code is required'}, 30 | country: {type: String, required: 'Country is required'} 31 | }, 32 | payment_id: {}, 33 | updated: Date, 34 | created: { 35 | type: Date, 36 | default: Date.now 37 | }, 38 | user: {type: mongoose.Schema.ObjectId, ref: 'User'} 39 | }) 40 | 41 | const Order = mongoose.model('Order', OrderSchema) 42 | 43 | export {Order, CartItem} 44 | -------------------------------------------------------------------------------- /Chapter06 and 07/mern-marketplace/server/models/product.model.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose' 2 | import crypto from 'crypto' 3 | const ProductSchema = new mongoose.Schema({ 4 | name: { 5 | type: String, 6 | trim: true, 7 | required: 'Name is required' 8 | }, 9 | image: { 10 | data: Buffer, 11 | contentType: String 12 | }, 13 | description: { 14 | type: String, 15 | trim: true 16 | }, 17 | category: { 18 | type: String 19 | }, 20 | quantity: { 21 | type: Number, 22 | required: "Quantity is required" 23 | }, 24 | price: { 25 | type: Number, 26 | required: "Price is required" 27 | }, 28 | updated: Date, 29 | created: { 30 | type: Date, 31 | default: Date.now 32 | }, 33 | shop: {type: mongoose.Schema.ObjectId, ref: 'Shop'} 34 | }) 35 | 36 | export default mongoose.model('Product', ProductSchema) 37 | -------------------------------------------------------------------------------- /Chapter06 and 07/mern-marketplace/server/models/shop.model.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose' 2 | import crypto from 'crypto' 3 | const ShopSchema = new mongoose.Schema({ 4 | name: { 5 | type: String, 6 | trim: true, 7 | required: 'Name is required' 8 | }, 9 | image: { 10 | data: Buffer, 11 | contentType: String 12 | }, 13 | description: { 14 | type: String, 15 | trim: true 16 | }, 17 | updated: Date, 18 | created: { 19 | type: Date, 20 | default: Date.now 21 | }, 22 | owner: {type: mongoose.Schema.ObjectId, ref: 'User'} 23 | }) 24 | 25 | export default mongoose.model('Shop', ShopSchema) 26 | -------------------------------------------------------------------------------- /Chapter06 and 07/mern-marketplace/server/models/user.model.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose' 2 | import crypto from 'crypto' 3 | const UserSchema = new mongoose.Schema({ 4 | name: { 5 | type: String, 6 | trim: true, 7 | required: 'Name is required' 8 | }, 9 | email: { 10 | type: String, 11 | trim: true, 12 | unique: 'Email already exists', 13 | match: [/.+\@.+\..+/, 'Please fill a valid email address'], 14 | required: 'Email is required' 15 | }, 16 | hashed_password: { 17 | type: String, 18 | required: "Password is required" 19 | }, 20 | salt: String, 21 | updated: Date, 22 | created: { 23 | type: Date, 24 | default: Date.now 25 | }, 26 | seller: { 27 | type: Boolean, 28 | default: false 29 | }, 30 | stripe_seller: {}, 31 | stripe_customer: {} 32 | }) 33 | 34 | UserSchema 35 | .virtual('password') 36 | .set(function(password) { 37 | this._password = password 38 | this.salt = this.makeSalt() 39 | this.hashed_password = this.encryptPassword(password) 40 | }) 41 | .get(function() { 42 | return this._password 43 | }) 44 | 45 | UserSchema.path('hashed_password').validate(function(v) { 46 | if (this._password && this._password.length < 6) { 47 | this.invalidate('password', 'Password must be at least 6 characters.') 48 | } 49 | if (this.isNew && !this._password) { 50 | this.invalidate('password', 'Password is required') 51 | } 52 | }, null) 53 | 54 | UserSchema.methods = { 55 | authenticate: function(plainText) { 56 | return this.encryptPassword(plainText) === this.hashed_password 57 | }, 58 | encryptPassword: function(password) { 59 | if (!password) return '' 60 | try { 61 | return crypto 62 | .createHmac('sha1', this.salt) 63 | .update(password) 64 | .digest('hex') 65 | } catch (err) { 66 | return '' 67 | } 68 | }, 69 | makeSalt: function() { 70 | return Math.round((new Date().valueOf() * Math.random())) + '' 71 | } 72 | } 73 | 74 | export default mongoose.model('User', UserSchema) 75 | -------------------------------------------------------------------------------- /Chapter06 and 07/mern-marketplace/server/routes/auth.routes.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import authCtrl from '../controllers/auth.controller' 3 | 4 | const router = express.Router() 5 | 6 | router.route('/auth/signin') 7 | .post(authCtrl.signin) 8 | router.route('/auth/signout') 9 | .get(authCtrl.signout) 10 | 11 | export default router 12 | -------------------------------------------------------------------------------- /Chapter06 and 07/mern-marketplace/server/routes/order.routes.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import orderCtrl from '../controllers/order.controller' 3 | import productCtrl from '../controllers/product.controller' 4 | import authCtrl from '../controllers/auth.controller' 5 | import shopCtrl from '../controllers/shop.controller' 6 | import userCtrl from '../controllers/user.controller' 7 | 8 | const router = express.Router() 9 | 10 | router.route('/api/orders/:userId') 11 | .post(authCtrl.requireSignin, userCtrl.stripeCustomer, productCtrl.decreaseQuantity, orderCtrl.create) 12 | 13 | router.route('/api/orders/shop/:shopId') 14 | .get(authCtrl.requireSignin, shopCtrl.isOwner, orderCtrl.listByShop) 15 | 16 | router.route('/api/orders/user/:userId') 17 | .get(authCtrl.requireSignin, orderCtrl.listByUser) 18 | 19 | router.route('/api/order/status_values') 20 | .get(orderCtrl.getStatusValues) 21 | 22 | router.route('/api/order/:shopId/cancel/:productId') 23 | .put(authCtrl.requireSignin, shopCtrl.isOwner, productCtrl.increaseQuantity, orderCtrl.update) 24 | 25 | router.route('/api/order/:orderId/charge/:userId/:shopId') 26 | .put(authCtrl.requireSignin, shopCtrl.isOwner, userCtrl.createCharge, orderCtrl.update) 27 | 28 | router.route('/api/order/status/:shopId') 29 | .put(authCtrl.requireSignin, shopCtrl.isOwner, orderCtrl.update) 30 | 31 | router.route('/api/order/:orderId') 32 | .get(orderCtrl.read) 33 | 34 | router.param('userId', userCtrl.userByID) 35 | router.param('shopId', shopCtrl.shopByID) 36 | router.param('productId', productCtrl.productByID) 37 | router.param('orderId', orderCtrl.orderByID) 38 | 39 | export default router 40 | -------------------------------------------------------------------------------- /Chapter06 and 07/mern-marketplace/server/routes/product.routes.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import productCtrl from '../controllers/product.controller' 3 | import authCtrl from '../controllers/auth.controller' 4 | import shopCtrl from '../controllers/shop.controller' 5 | 6 | const router = express.Router() 7 | 8 | router.route('/api/products/by/:shopId') 9 | .post(authCtrl.requireSignin, shopCtrl.isOwner, productCtrl.create) 10 | .get(productCtrl.listByShop) 11 | 12 | router.route('/api/products/latest') 13 | .get(productCtrl.listLatest) 14 | 15 | router.route('/api/products/related/:productId') 16 | .get(productCtrl.listRelated) 17 | 18 | router.route('/api/products/categories') 19 | .get(productCtrl.listCategories) 20 | 21 | router.route('/api/products') 22 | .get(productCtrl.list) 23 | 24 | router.route('/api/products/:productId') 25 | .get(productCtrl.read) 26 | 27 | router.route('/api/product/image/:productId') 28 | .get(productCtrl.photo, productCtrl.defaultPhoto) 29 | router.route('/api/product/defaultphoto') 30 | .get(productCtrl.defaultPhoto) 31 | 32 | router.route('/api/product/:shopId/:productId') 33 | .put(authCtrl.requireSignin, shopCtrl.isOwner, productCtrl.update) 34 | .delete(authCtrl.requireSignin, shopCtrl.isOwner, productCtrl.remove) 35 | 36 | router.param('shopId', shopCtrl.shopByID) 37 | router.param('productId', productCtrl.productByID) 38 | 39 | export default router 40 | -------------------------------------------------------------------------------- /Chapter06 and 07/mern-marketplace/server/routes/shop.routes.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import userCtrl from '../controllers/user.controller' 3 | import authCtrl from '../controllers/auth.controller' 4 | import shopCtrl from '../controllers/shop.controller' 5 | 6 | const router = express.Router() 7 | 8 | router.route('/api/shops') 9 | .get(shopCtrl.list) 10 | 11 | router.route('/api/shop/:shopId') 12 | .get(shopCtrl.read) 13 | 14 | router.route('/api/shops/by/:userId') 15 | .post(authCtrl.requireSignin, authCtrl.hasAuthorization, userCtrl.isSeller, shopCtrl.create) 16 | .get(authCtrl.requireSignin, authCtrl.hasAuthorization, shopCtrl.listByOwner) 17 | 18 | router.route('/api/shops/:shopId') 19 | .put(authCtrl.requireSignin, shopCtrl.isOwner, shopCtrl.update) 20 | .delete(authCtrl.requireSignin, shopCtrl.isOwner, shopCtrl.remove) 21 | 22 | router.route('/api/shops/logo/:shopId') 23 | .get(shopCtrl.photo, shopCtrl.defaultPhoto) 24 | 25 | router.route('/api/shops/defaultphoto') 26 | .get(shopCtrl.defaultPhoto) 27 | 28 | router.param('shopId', shopCtrl.shopByID) 29 | router.param('userId', userCtrl.userByID) 30 | 31 | export default router 32 | -------------------------------------------------------------------------------- /Chapter06 and 07/mern-marketplace/server/routes/user.routes.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import userCtrl from '../controllers/user.controller' 3 | import authCtrl from '../controllers/auth.controller' 4 | 5 | const router = express.Router() 6 | 7 | router.route('/api/users') 8 | .get(userCtrl.list) 9 | .post(userCtrl.create) 10 | 11 | router.route('/api/users/:userId') 12 | .get(authCtrl.requireSignin, userCtrl.read) 13 | .put(authCtrl.requireSignin, authCtrl.hasAuthorization, userCtrl.update) 14 | .delete(authCtrl.requireSignin, authCtrl.hasAuthorization, userCtrl.remove) 15 | router.route('/api/stripe_auth/:userId') 16 | .put(authCtrl.requireSignin, authCtrl.hasAuthorization, userCtrl.stripe_auth, userCtrl.update) 17 | 18 | router.param('userId', userCtrl.userByID) 19 | 20 | export default router 21 | -------------------------------------------------------------------------------- /Chapter06 and 07/mern-marketplace/server/server.js: -------------------------------------------------------------------------------- 1 | import config from './../config/config' 2 | import app from './express' 3 | import mongoose from 'mongoose' 4 | 5 | // Connection URL 6 | mongoose.Promise = global.Promise 7 | mongoose.connect(config.mongoUri) 8 | mongoose.connection.on('error', () => { 9 | throw new Error(`unable to connect to database: ${mongoUri}`) 10 | }) 11 | 12 | app.listen(config.port, (err) => { 13 | if (err) { 14 | console.log(err) 15 | } 16 | console.info('Server started on port %s.', config.port) 17 | }) 18 | -------------------------------------------------------------------------------- /Chapter06 and 07/mern-marketplace/template.js: -------------------------------------------------------------------------------- 1 | export default ({markup, css}) => { 2 | return ` 3 | 4 | 5 | 6 | MERN Marketplace 7 | 8 | 9 | 14 | 15 | 16 |
${markup}
17 | 18 | 19 | 20 | 21 | ` 22 | } 23 | -------------------------------------------------------------------------------- /Chapter06 and 07/mern-marketplace/webpack.config.client.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | const CURRENT_WORKING_DIR = process.cwd() 4 | 5 | const config = { 6 | name: "browser", 7 | mode: "development", 8 | devtool: 'eval-source-map', 9 | entry: [ 10 | 'react-hot-loader/patch', 11 | 'webpack-hot-middleware/client?reload=true', 12 | path.join(CURRENT_WORKING_DIR, 'client/main.js') 13 | ], 14 | output: { 15 | path: path.join(CURRENT_WORKING_DIR , '/dist'), 16 | filename: 'bundle.js', 17 | publicPath: '/dist/' 18 | }, 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.jsx?$/, 23 | exclude: /node_modules/, 24 | use: [ 25 | 'babel-loader' 26 | ] 27 | }, 28 | { 29 | test: /\.(ttf|eot|svg|gif|jpg|png)(\?[\s\S]+)?$/, 30 | use: 'file-loader' 31 | } 32 | ] 33 | }, plugins: [ 34 | new webpack.HotModuleReplacementPlugin(), 35 | new webpack.NoEmitOnErrorsPlugin() 36 | ] 37 | } 38 | 39 | module.exports = config 40 | -------------------------------------------------------------------------------- /Chapter06 and 07/mern-marketplace/webpack.config.client.production.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | const CURRENT_WORKING_DIR = process.cwd() 4 | 5 | const config = { 6 | mode: "production", 7 | entry: [ 8 | path.join(CURRENT_WORKING_DIR, 'client/main.js') 9 | ], 10 | output: { 11 | path: path.join(CURRENT_WORKING_DIR , '/dist'), 12 | filename: 'bundle.js', 13 | publicPath: "/dist/" 14 | }, 15 | module: { 16 | rules: [ 17 | { 18 | test: /\.jsx?$/, 19 | exclude: /node_modules/, 20 | use: [ 21 | 'babel-loader' 22 | ] 23 | }, 24 | { 25 | test: /\.(ttf|eot|svg|gif|jpg|png)(\?[\s\S]+)?$/, 26 | use: 'file-loader' 27 | } 28 | ] 29 | } 30 | } 31 | 32 | module.exports = config 33 | -------------------------------------------------------------------------------- /Chapter06 and 07/mern-marketplace/webpack.config.server.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | const nodeExternals = require('webpack-node-externals') 4 | const CURRENT_WORKING_DIR = process.cwd() 5 | 6 | const config = { 7 | name: "server", 8 | entry: [ path.join(CURRENT_WORKING_DIR , './server/server.js') ], 9 | target: "node", 10 | output: { 11 | path: path.join(CURRENT_WORKING_DIR , '/dist/'), 12 | filename: "server.generated.js", 13 | publicPath: '/dist/', 14 | libraryTarget: "commonjs2" 15 | }, 16 | externals: [nodeExternals()], 17 | module: { 18 | rules: [ 19 | { 20 | test: /\.js$/, 21 | exclude: /node_modules/, 22 | use: [ 'babel-loader' ] 23 | }, 24 | { 25 | test: /\.(ttf|eot|svg|gif|jpg|png)(\?[\s\S]+)?$/, 26 | use: 'file-loader' 27 | } 28 | ] 29 | } 30 | } 31 | 32 | module.exports = config 33 | -------------------------------------------------------------------------------- /Chapter08 and 09/mern-mediastream/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "env", 4 | "stage-2", 5 | "react" 6 | ], 7 | "plugins": [ 8 | "react-hot-loader/babel" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /Chapter08 and 09/mern-mediastream/LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Shama Hoque 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Chapter08 and 09/mern-mediastream/README.md: -------------------------------------------------------------------------------- 1 | # MERN Mediastream 2 | 3 | ### [Live Demo](http://mediastream.mernbook.com/ "MERN Mediastream") 4 | 5 | #### What you need to run this code 6 | 1. Node (8.11.1) 7 | 2. NPM (5.8.0) 8 | 3. MongoDB (3.6.3) 9 | 10 | #### How to run this code 11 | 1. Clone this repository 12 | 2. Open command line in the cloned folder, 13 | - To install dependencies, run ``` npm install ``` 14 | - To run the application for development, run ``` npm run development ``` 15 | 4. Open [localhost:3000](http://localhost:3000/) in the browser 16 | -------------------------------------------------------------------------------- /Chapter08 and 09/mern-mediastream/client/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import MainRouter from './MainRouter' 3 | import {BrowserRouter} from 'react-router-dom' 4 | import {MuiThemeProvider, createMuiTheme} from 'material-ui/styles' 5 | import { red, brown } from 'material-ui/colors' 6 | import { hot } from 'react-hot-loader' 7 | 8 | // Create a theme instance. 9 | const theme = createMuiTheme({ 10 | palette: { 11 | primary: { 12 | light: '#f05545', 13 | main: '#b71c1c', 14 | dark: '#7f0000', 15 | contrastText: '#fff', 16 | }, 17 | secondary: { 18 | light: '#efdcd5', 19 | main: '#d7ccc8', 20 | dark: '#8c7b75', 21 | contrastText: '#424242', 22 | }, 23 | openTitle: red['500'], 24 | protectedTitle: brown['300'], 25 | type: 'light' 26 | } 27 | }) 28 | 29 | const App = () => ( 30 | 31 | 32 | 33 | 34 | 35 | ) 36 | 37 | export default hot(module)(App) 38 | -------------------------------------------------------------------------------- /Chapter08 and 09/mern-mediastream/client/MainRouter.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import {Route, Switch} from 'react-router-dom' 3 | import Home from './core/Home' 4 | import Users from './user/Users' 5 | import Signup from './user/Signup' 6 | import Signin from './auth/Signin' 7 | import EditProfile from './user/EditProfile' 8 | import Profile from './user/Profile' 9 | import PrivateRoute from './auth/PrivateRoute' 10 | import Menu from './core/Menu' 11 | import NewMedia from './media/NewMedia' 12 | import PlayMedia from './media/PlayMedia' 13 | import EditMedia from './media/EditMedia' 14 | 15 | class MainRouter extends Component { 16 | constructor({data}) { 17 | super() 18 | this.data = data 19 | } 20 | // Removes the server-side injected CSS when React component mounts 21 | componentDidMount() { 22 | const jssStyles = document.getElementById('jss-server-side') 23 | if (jssStyles && jssStyles.parentNode) { 24 | jssStyles.parentNode.removeChild(jssStyles) 25 | } 26 | } 27 | 28 | render() { 29 | return (
30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | ( 42 | 43 | )} /> 44 | 45 |
) 46 | } 47 | } 48 | 49 | export default MainRouter 50 | -------------------------------------------------------------------------------- /Chapter08 and 09/mern-mediastream/client/assets/images/seashell.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Full-Stack-React-Projects/93940878b3429a415af672d0f3751a2f7e2fe1ca/Chapter08 and 09/mern-mediastream/client/assets/images/seashell.jpg -------------------------------------------------------------------------------- /Chapter08 and 09/mern-mediastream/client/auth/PrivateRoute.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Route, Redirect } from 'react-router-dom' 3 | import auth from './auth-helper' 4 | 5 | const PrivateRoute = ({ component: Component, ...rest }) => ( 6 | ( 7 | auth.isAuthenticated() ? ( 8 | 9 | ) : ( 10 | 14 | ) 15 | )}/> 16 | ) 17 | 18 | export default PrivateRoute 19 | -------------------------------------------------------------------------------- /Chapter08 and 09/mern-mediastream/client/auth/api-auth.js: -------------------------------------------------------------------------------- 1 | const signin = (user) => { 2 | return fetch('/auth/signin/', { 3 | method: 'POST', 4 | headers: { 5 | 'Accept': 'application/json', 6 | 'Content-Type': 'application/json' 7 | }, 8 | credentials: 'include', 9 | body: JSON.stringify(user) 10 | }) 11 | .then((response) => { 12 | return response.json() 13 | }).catch((err) => console.log(err)) 14 | } 15 | 16 | const signout = () => { 17 | return fetch('/auth/signout/', { 18 | method: 'GET', 19 | }).then(response => { 20 | return response.json() 21 | }).catch((err) => console.log(err)) 22 | } 23 | 24 | export { 25 | signin, 26 | signout 27 | } 28 | -------------------------------------------------------------------------------- /Chapter08 and 09/mern-mediastream/client/auth/auth-helper.js: -------------------------------------------------------------------------------- 1 | import { signout } from './api-auth.js' 2 | 3 | const auth = { 4 | isAuthenticated() { 5 | if (typeof window == "undefined") 6 | return false 7 | 8 | if (sessionStorage.getItem('jwt')) 9 | return JSON.parse(sessionStorage.getItem('jwt')) 10 | else 11 | return false 12 | }, 13 | authenticate(jwt, cb) { 14 | if (typeof window !== "undefined") 15 | sessionStorage.setItem('jwt', JSON.stringify(jwt)) 16 | cb() 17 | }, 18 | signout(cb) { 19 | if (typeof window !== "undefined") 20 | sessionStorage.removeItem('jwt') 21 | cb() 22 | //optional 23 | signout().then((data) => { 24 | document.cookie = "t=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;" 25 | }) 26 | } 27 | } 28 | 29 | export default auth 30 | -------------------------------------------------------------------------------- /Chapter08 and 09/mern-mediastream/client/core/Home.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import PropTypes from 'prop-types' 3 | import {withStyles} from 'material-ui/styles' 4 | import Card from 'material-ui/Card' 5 | import Typography from 'material-ui/Typography' 6 | import MediaList from '../media/MediaList' 7 | import {listPopular} from '../media/api-media.js' 8 | 9 | const styles = theme => ({ 10 | card: { 11 | margin: `${theme.spacing.unit * 5}px 30px` 12 | }, 13 | title: { 14 | padding:`${theme.spacing.unit * 3}px ${theme.spacing.unit * 2.5}px 0px`, 15 | color: theme.palette.text.secondary, 16 | fontSize: '1em' 17 | }, 18 | media: { 19 | minHeight: 330 20 | } 21 | }) 22 | 23 | class Home extends Component { 24 | state={ 25 | media: [] 26 | } 27 | 28 | componentDidMount = () => { 29 | listPopular().then((data) => { 30 | if (data.error) { 31 | console.log(data.error) 32 | } else { 33 | this.setState({media: data}) 34 | } 35 | }) 36 | } 37 | 38 | render() { 39 | const {classes} = this.props 40 | return ( 41 | 42 | 43 | Popular Videos 44 | 45 | 46 | 47 | ) 48 | } 49 | } 50 | 51 | Home.propTypes = { 52 | classes: PropTypes.object.isRequired 53 | } 54 | 55 | export default withStyles(styles)(Home) 56 | -------------------------------------------------------------------------------- /Chapter08 and 09/mern-mediastream/client/core/Menu.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import AppBar from 'material-ui/AppBar' 3 | import Toolbar from 'material-ui/Toolbar' 4 | import Typography from 'material-ui/Typography' 5 | import IconButton from 'material-ui/IconButton' 6 | import HomeIcon from 'material-ui-icons/Home' 7 | import AddBoxIcon from 'material-ui-icons/AddBox' 8 | import Button from 'material-ui/Button' 9 | import auth from './../auth/auth-helper' 10 | import {Link, withRouter} from 'react-router-dom' 11 | 12 | const isActive = (history, path) => { 13 | if (history.location.pathname == path) 14 | return {color: '#f99085'} 15 | else 16 | return {color: '#efdcd5'} 17 | } 18 | const Menu = withRouter(({history}) => ( 19 | 20 | 21 | 22 | MERN Mediastream 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 |
31 |
32 | { 33 | !auth.isAuthenticated() && ( 34 | 35 | 37 | 38 | 39 | 41 | 42 | ) 43 | } 44 | { 45 | auth.isAuthenticated() && ( 46 | 47 | 50 | 51 | 52 | 53 | 54 | 57 | ) 58 | } 59 |
60 |
61 |
62 | )) 63 | 64 | export default Menu 65 | -------------------------------------------------------------------------------- /Chapter08 and 09/mern-mediastream/client/main.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { hydrate } from 'react-dom' 3 | import App from './App' 4 | 5 | hydrate(, document.getElementById('root')) 6 | -------------------------------------------------------------------------------- /Chapter08 and 09/mern-mediastream/client/media/DeleteMedia.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import PropTypes from 'prop-types' 3 | import IconButton from 'material-ui/IconButton' 4 | import Button from 'material-ui/Button' 5 | import DeleteIcon from 'material-ui-icons/Delete' 6 | import Dialog, {DialogActions, DialogContent, DialogContentText, DialogTitle} from 'material-ui/Dialog' 7 | import auth from './../auth/auth-helper' 8 | import {remove} from './api-media.js' 9 | import {Redirect} from 'react-router-dom' 10 | 11 | class DeleteMedia extends Component { 12 | state = { 13 | redirect: false, 14 | open: false 15 | } 16 | clickButton = () => { 17 | this.setState({open: true}) 18 | } 19 | deleteMedia = () => { 20 | const jwt = auth.isAuthenticated() 21 | remove({ 22 | mediaId: this.props.mediaId 23 | }, {t: jwt.token}).then((data) => { 24 | if (data.error) { 25 | console.log(data.error) 26 | } else { 27 | this.setState({redirect: true}) 28 | } 29 | }) 30 | } 31 | handleRequestClose = () => { 32 | this.setState({open: false}) 33 | } 34 | render() { 35 | const redirect = this.state.redirect 36 | if (redirect) { 37 | return 38 | } 39 | return ( 40 | 41 | 42 | 43 | 44 | 45 | {"Delete "+this.props.mediaTitle} 46 | 47 | 48 | Confirm to delete {this.props.mediaTitle} from your account. 49 | 50 | 51 | 52 | 55 | 58 | 59 | 60 | ) 61 | } 62 | } 63 | 64 | DeleteMedia.propTypes = { 65 | mediaId: PropTypes.string.isRequired, 66 | mediaTitle: PropTypes.string.isRequired 67 | } 68 | 69 | export default DeleteMedia 70 | -------------------------------------------------------------------------------- /Chapter08 and 09/mern-mediastream/client/routeConfig.js: -------------------------------------------------------------------------------- 1 | import PlayMedia from './media/PlayMedia' 2 | import { read } from './media/api-media.js' 3 | 4 | const routes = [ 5 | { 6 | path: '/media/:mediaId', 7 | component: PlayMedia, 8 | loadData: (params) => read(params) 9 | } 10 | 11 | ] 12 | export default routes 13 | -------------------------------------------------------------------------------- /Chapter08 and 09/mern-mediastream/client/user/DeleteUser.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import PropTypes from 'prop-types' 3 | import IconButton from 'material-ui/IconButton' 4 | import Button from 'material-ui/Button' 5 | import DeleteIcon from 'material-ui-icons/Delete' 6 | import Dialog, {DialogActions, DialogContent, DialogContentText, DialogTitle} from 'material-ui/Dialog' 7 | import auth from './../auth/auth-helper' 8 | import {remove} from './api-user.js' 9 | import {Redirect, Link} from 'react-router-dom' 10 | 11 | class DeleteUser extends Component { 12 | state = { 13 | redirect: false, 14 | open: false 15 | } 16 | clickButton = () => { 17 | this.setState({open: true}) 18 | } 19 | deleteAccount = () => { 20 | const jwt = auth.isAuthenticated() 21 | remove({ 22 | userId: this.props.userId 23 | }, {t: jwt.token}).then((data) => { 24 | if (data.error) { 25 | console.log(data.error) 26 | } else { 27 | auth.signout(() => console.log('deleted')) 28 | this.setState({redirect: true}) 29 | } 30 | }) 31 | } 32 | handleRequestClose = () => { 33 | this.setState({open: false}) 34 | } 35 | render() { 36 | const redirect = this.state.redirect 37 | if (redirect) { 38 | return 39 | } 40 | return ( 41 | 42 | 43 | 44 | 45 | 46 | {"Delete Account"} 47 | 48 | 49 | Confirm to delete your account. 50 | 51 | 52 | 53 | 56 | 59 | 60 | 61 | ) 62 | } 63 | } 64 | DeleteUser.propTypes = { 65 | userId: PropTypes.string.isRequired 66 | } 67 | export default DeleteUser 68 | -------------------------------------------------------------------------------- /Chapter08 and 09/mern-mediastream/client/user/api-user.js: -------------------------------------------------------------------------------- 1 | const create = (user) => { 2 | return fetch('/api/users/', { 3 | method: 'POST', 4 | headers: { 5 | 'Accept': 'application/json', 6 | 'Content-Type': 'application/json' 7 | }, 8 | body: JSON.stringify(user) 9 | }) 10 | .then((response) => { 11 | return response.json() 12 | }).catch((err) => console.log(err)) 13 | } 14 | 15 | const list = () => { 16 | return fetch('/api/users/', { 17 | method: 'GET', 18 | }).then(response => { 19 | return response.json() 20 | }).catch((err) => console.log(err)) 21 | } 22 | 23 | const read = (params, credentials) => { 24 | return fetch('/api/users/' + params.userId, { 25 | method: 'GET', 26 | headers: { 27 | 'Accept': 'application/json', 28 | 'Content-Type': 'application/json', 29 | 'Authorization': 'Bearer ' + credentials.t 30 | } 31 | }).then((response) => { 32 | return response.json() 33 | }).catch((err) => console.log(err)) 34 | } 35 | 36 | const update = (params, credentials, user) => { 37 | return fetch('/api/users/' + params.userId, { 38 | method: 'PUT', 39 | headers: { 40 | 'Accept': 'application/json', 41 | 'Content-Type': 'application/json', 42 | 'Authorization': 'Bearer ' + credentials.t 43 | }, 44 | body: JSON.stringify(user) 45 | }).then((response) => { 46 | return response.json() 47 | }).catch((err) => console.log(err)) 48 | } 49 | 50 | const remove = (params, credentials) => { 51 | return fetch('/api/users/' + params.userId, { 52 | method: 'DELETE', 53 | headers: { 54 | 'Accept': 'application/json', 55 | 'Content-Type': 'application/json', 56 | 'Authorization': 'Bearer ' + credentials.t 57 | } 58 | }).then((response) => { 59 | return response.json() 60 | }).catch((err) => console.log(err)) 61 | } 62 | 63 | export { 64 | create, 65 | list, 66 | read, 67 | update, 68 | remove 69 | } 70 | -------------------------------------------------------------------------------- /Chapter08 and 09/mern-mediastream/config/config.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | env: process.env.NODE_ENV || 'development', 3 | port: process.env.PORT || 3000, 4 | jwtSecret: process.env.JWT_SECRET || "YOUR_secret_key", 5 | mongoUri: process.env.MONGODB_URI || 6 | process.env.MONGO_HOST || 7 | 'mongodb://' + (process.env.IP || 'localhost') + ':' + 8 | (process.env.MONGO_PORT || '27017') + 9 | '/mernproject', 10 | serverUrl: process.env.serverUrl || 'http://localhost:3000' 11 | } 12 | 13 | export default config 14 | -------------------------------------------------------------------------------- /Chapter08 and 09/mern-mediastream/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "verbose": false, 3 | "watch": [ 4 | "./server" 5 | ], 6 | "exec": "webpack --mode=development --config webpack.config.server.js && node ./dist/server.generated.js" 7 | } 8 | -------------------------------------------------------------------------------- /Chapter08 and 09/mern-mediastream/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mern-mediastream", 3 | "version": "1.0.0", 4 | "description": "A MERN stack based media streaming application", 5 | "author": "Shama Hoque", 6 | "license": "MIT", 7 | "keywords": [ 8 | "react", 9 | "express", 10 | "mongodb", 11 | "node", 12 | "mern" 13 | ], 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/shamahoque/mern-mediastream.git" 17 | }, 18 | "homepage": "https://github.com/shamahoque/mern-mediastream", 19 | "main": "./dist/server.generated.js", 20 | "scripts": { 21 | "development": "nodemon", 22 | "build": "webpack --config webpack.config.client.production.js && webpack --mode=production --config webpack.config.server.js", 23 | "start": "NODE_ENV=production node ./dist/server.generated.js" 24 | }, 25 | "engines": { 26 | "node": "8.11.1", 27 | "npm": "5.8.0" 28 | }, 29 | "devDependencies": { 30 | "babel-core": "^6.26.2", 31 | "babel-loader": "^7.1.4", 32 | "babel-preset-env": "^1.6.1", 33 | "babel-preset-react": "^6.24.1", 34 | "babel-preset-stage-2": "^6.24.1", 35 | "file-loader": "^1.1.11", 36 | "nodemon": "^1.17.3", 37 | "webpack": "^4.6.0", 38 | "webpack-cli": "^2.0.15", 39 | "webpack-dev-middleware": "^3.1.3", 40 | "webpack-hot-middleware": "^2.22.1", 41 | "webpack-node-externals": "^1.7.2" 42 | }, 43 | "dependencies": { 44 | "body-parser": "^1.18.2", 45 | "compression": "^1.7.2", 46 | "cookie-parser": "^1.4.3", 47 | "cors": "^2.8.4", 48 | "express": "^4.16.3", 49 | "express-jwt": "^5.3.1", 50 | "formidable": "^1.2.1", 51 | "gridfs-stream": "^1.1.1", 52 | "helmet": "^3.12.0", 53 | "jsonwebtoken": "^8.2.1", 54 | "lodash": "^4.17.10", 55 | "material-ui": "^1.0.0-beta.43", 56 | "material-ui-icons": "^1.0.0-beta.36", 57 | "mongoose": "^5.0.16", 58 | "react": "^16.3.2", 59 | "react-dom": "^16.3.2", 60 | "react-hot-loader": "^4.1.2", 61 | "react-player": "^1.5.0", 62 | "react-router": "^4.2.0", 63 | "react-router-config": "^1.0.0-beta.4", 64 | "react-router-dom": "^4.2.2", 65 | "screenfull": "^3.3.2" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Chapter08 and 09/mern-mediastream/server/controllers/auth.controller.js: -------------------------------------------------------------------------------- 1 | import User from '../models/user.model' 2 | import jwt from 'jsonwebtoken' 3 | import expressJwt from 'express-jwt' 4 | import config from './../../config/config' 5 | 6 | const signin = (req, res) => { 7 | User.findOne({ 8 | "email": req.body.email 9 | }, (err, user) => { 10 | 11 | if (err || !user) 12 | return res.status('401').json({ 13 | error: "User not found" 14 | }) 15 | 16 | if (!user.authenticate(req.body.password)) { 17 | return res.status('401').send({ 18 | error: "Email and password don't match." 19 | }) 20 | } 21 | 22 | const token = jwt.sign({ 23 | _id: user._id 24 | }, config.jwtSecret) 25 | 26 | res.cookie("t", token, { 27 | expire: new Date() + 9999 28 | }) 29 | 30 | return res.json({ 31 | token, 32 | user: {_id: user._id, name: user.name, email: user.email} 33 | }) 34 | 35 | }) 36 | } 37 | 38 | const signout = (req, res) => { 39 | res.clearCookie("t") 40 | return res.status('200').json({ 41 | message: "signed out" 42 | }) 43 | } 44 | 45 | const requireSignin = expressJwt({ 46 | secret: config.jwtSecret, 47 | userProperty: 'auth' 48 | }) 49 | 50 | const hasAuthorization = (req, res, next) => { 51 | const authorized = req.profile && req.auth && req.profile._id == req.auth._id 52 | if (!(authorized)) { 53 | return res.status('403').json({ 54 | error: "User is not authorized" 55 | }) 56 | } 57 | next() 58 | } 59 | 60 | export default { 61 | signin, 62 | signout, 63 | requireSignin, 64 | hasAuthorization 65 | } 66 | -------------------------------------------------------------------------------- /Chapter08 and 09/mern-mediastream/server/controllers/user.controller.js: -------------------------------------------------------------------------------- 1 | import User from '../models/user.model' 2 | import _ from 'lodash' 3 | import errorHandler from './../helpers/dbErrorHandler' 4 | 5 | const create = (req, res, next) => { 6 | const user = new User(req.body) 7 | user.save((err, result) => { 8 | if (err) { 9 | return res.status(400).json({ 10 | error: errorHandler.getErrorMessage(err) 11 | }) 12 | } 13 | res.status(200).json({ 14 | message: "Successfully signed up!" 15 | }) 16 | }) 17 | } 18 | 19 | /** 20 | * Load user and append to req. 21 | */ 22 | const userByID = (req, res, next, id) => { 23 | User.findById(id).exec((err, user) => { 24 | if (err || !user) 25 | return res.status('400').json({ 26 | error: "User not found" 27 | }) 28 | req.profile = user 29 | next() 30 | }) 31 | } 32 | 33 | const read = (req, res) => { 34 | req.profile.hashed_password = undefined 35 | req.profile.salt = undefined 36 | return res.json(req.profile) 37 | } 38 | 39 | const list = (req, res) => { 40 | User.find((err, users) => { 41 | if (err) { 42 | return res.status(400).json({ 43 | error: errorHandler.getErrorMessage(err) 44 | }) 45 | } 46 | res.json(users) 47 | }).select('name email updated created') 48 | } 49 | 50 | const update = (req, res, next) => { 51 | let user = req.profile 52 | user = _.extend(user, req.body) 53 | user.updated = Date.now() 54 | user.save((err) => { 55 | if (err) { 56 | return res.status(400).json({ 57 | error: errorHandler.getErrorMessage(err) 58 | }) 59 | } 60 | user.hashed_password = undefined 61 | user.salt = undefined 62 | res.json(user) 63 | }) 64 | } 65 | 66 | const remove = (req, res, next) => { 67 | let user = req.profile 68 | user.remove((err, deletedUser) => { 69 | if (err) { 70 | return res.status(400).json({ 71 | error: errorHandler.getErrorMessage(err) 72 | }) 73 | } 74 | deletedUser.hashed_password = undefined 75 | deletedUser.salt = undefined 76 | res.json(deletedUser) 77 | }) 78 | } 79 | 80 | export default { 81 | create, 82 | userByID, 83 | read, 84 | list, 85 | remove, 86 | update 87 | } 88 | -------------------------------------------------------------------------------- /Chapter08 and 09/mern-mediastream/server/devBundle.js: -------------------------------------------------------------------------------- 1 | import config from './../config/config' 2 | import webpack from 'webpack' 3 | import webpackMiddleware from 'webpack-dev-middleware' 4 | import webpackHotMiddleware from 'webpack-hot-middleware' 5 | import webpackConfig from './../webpack.config.client.js' 6 | 7 | const compile = (app) => { 8 | if(config.env === "development"){ 9 | const compiler = webpack(webpackConfig) 10 | const middleware = webpackMiddleware(compiler, { 11 | publicPath: webpackConfig.output.publicPath 12 | }) 13 | app.use(middleware) 14 | app.use(webpackHotMiddleware(compiler)) 15 | } 16 | } 17 | 18 | export default { 19 | compile 20 | } 21 | -------------------------------------------------------------------------------- /Chapter08 and 09/mern-mediastream/server/helpers/dbErrorHandler.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Get unique error field name 5 | */ 6 | const getUniqueErrorMessage = (err) => { 7 | let output 8 | try { 9 | let fieldName = err.message.substring(err.message.lastIndexOf('.$') + 2, err.message.lastIndexOf('_1')) 10 | output = fieldName.charAt(0).toUpperCase() + fieldName.slice(1) + ' already exists' 11 | } catch (ex) { 12 | output = 'Unique field already exists' 13 | } 14 | 15 | return output 16 | } 17 | 18 | /** 19 | * Get the error message from error object 20 | */ 21 | const getErrorMessage = (err) => { 22 | let message = '' 23 | 24 | if (err.code) { 25 | switch (err.code) { 26 | case 11000: 27 | case 11001: 28 | message = getUniqueErrorMessage(err) 29 | break 30 | default: 31 | message = 'Something went wrong' 32 | } 33 | } else { 34 | for (let errName in err.errors) { 35 | if (err.errors[errName].message) message = err.errors[errName].message 36 | } 37 | } 38 | 39 | return message 40 | } 41 | 42 | export default {getErrorMessage} 43 | -------------------------------------------------------------------------------- /Chapter08 and 09/mern-mediastream/server/models/media.model.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose' 2 | import crypto from 'crypto' 3 | const MediaSchema = new mongoose.Schema({ 4 | title: { 5 | type: String, 6 | required: 'title is required' 7 | }, 8 | description: String, 9 | genre: String, 10 | views: {type: Number, default: 0}, 11 | postedBy: {type: mongoose.Schema.ObjectId, ref: 'User'}, 12 | created: { 13 | type: Date, 14 | default: Date.now 15 | }, 16 | updated: { 17 | type: Date 18 | } 19 | }) 20 | 21 | export default mongoose.model('Media', MediaSchema) 22 | -------------------------------------------------------------------------------- /Chapter08 and 09/mern-mediastream/server/models/user.model.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose' 2 | import crypto from 'crypto' 3 | const UserSchema = new mongoose.Schema({ 4 | name: { 5 | type: String, 6 | trim: true, 7 | required: 'Name is required' 8 | }, 9 | email: { 10 | type: String, 11 | trim: true, 12 | unique: 'Email already exists', 13 | match: [/.+\@.+\..+/, 'Please fill a valid email address'], 14 | required: 'Email is required' 15 | }, 16 | hashed_password: { 17 | type: String, 18 | required: "Password is required" 19 | }, 20 | salt: String, 21 | updated: Date, 22 | created: { 23 | type: Date, 24 | default: Date.now 25 | } 26 | }) 27 | 28 | UserSchema 29 | .virtual('password') 30 | .set(function(password) { 31 | this._password = password 32 | this.salt = this.makeSalt() 33 | this.hashed_password = this.encryptPassword(password) 34 | }) 35 | .get(function() { 36 | return this._password 37 | }) 38 | 39 | UserSchema.path('hashed_password').validate(function(v) { 40 | if (this._password && this._password.length < 6) { 41 | this.invalidate('password', 'Password must be at least 6 characters.') 42 | } 43 | if (this.isNew && !this._password) { 44 | this.invalidate('password', 'Password is required') 45 | } 46 | }, null) 47 | 48 | UserSchema.methods = { 49 | authenticate: function(plainText) { 50 | return this.encryptPassword(plainText) === this.hashed_password 51 | }, 52 | encryptPassword: function(password) { 53 | if (!password) return '' 54 | try { 55 | return crypto 56 | .createHmac('sha1', this.salt) 57 | .update(password) 58 | .digest('hex') 59 | } catch (err) { 60 | return '' 61 | } 62 | }, 63 | makeSalt: function() { 64 | return Math.round((new Date().valueOf() * Math.random())) + '' 65 | } 66 | } 67 | 68 | export default mongoose.model('User', UserSchema) 69 | -------------------------------------------------------------------------------- /Chapter08 and 09/mern-mediastream/server/routes/auth.routes.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import authCtrl from '../controllers/auth.controller' 3 | 4 | const router = express.Router() 5 | 6 | router.route('/auth/signin') 7 | .post(authCtrl.signin) 8 | router.route('/auth/signout') 9 | .get(authCtrl.signout) 10 | 11 | export default router 12 | -------------------------------------------------------------------------------- /Chapter08 and 09/mern-mediastream/server/routes/media.routes.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import userCtrl from '../controllers/user.controller' 3 | import authCtrl from '../controllers/auth.controller' 4 | import mediaCtrl from '../controllers/media.controller' 5 | 6 | const router = express.Router() 7 | 8 | router.route('/api/media/new/:userId') 9 | .post(authCtrl.requireSignin, mediaCtrl.create) 10 | 11 | router.route('/api/media/video/:mediaId') 12 | .get(mediaCtrl.video) 13 | 14 | router.route('/api/media/popular') 15 | .get(mediaCtrl.listPopular) 16 | 17 | router.route('/api/media/related/:mediaId') 18 | .get(mediaCtrl.listRelated) 19 | 20 | router.route('/api/media/by/:userId') 21 | .get(mediaCtrl.listByUser) 22 | 23 | router.route('/api/media/:mediaId') 24 | .get( mediaCtrl.incrementViews, mediaCtrl.read) 25 | .put(authCtrl.requireSignin, mediaCtrl.isPoster, mediaCtrl.update) 26 | .delete(authCtrl.requireSignin, mediaCtrl.isPoster, mediaCtrl.remove) 27 | 28 | router.param('userId', userCtrl.userByID) 29 | router.param('mediaId', mediaCtrl.mediaByID) 30 | 31 | export default router 32 | -------------------------------------------------------------------------------- /Chapter08 and 09/mern-mediastream/server/routes/user.routes.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import userCtrl from '../controllers/user.controller' 3 | import authCtrl from '../controllers/auth.controller' 4 | 5 | const router = express.Router() 6 | 7 | router.route('/api/users') 8 | .get(userCtrl.list) 9 | .post(userCtrl.create) 10 | 11 | router.route('/api/users/:userId') 12 | .get(authCtrl.requireSignin, userCtrl.read) 13 | .put(authCtrl.requireSignin, authCtrl.hasAuthorization, userCtrl.update) 14 | .delete(authCtrl.requireSignin, authCtrl.hasAuthorization, userCtrl.remove) 15 | 16 | router.param('userId', userCtrl.userByID) 17 | 18 | export default router 19 | -------------------------------------------------------------------------------- /Chapter08 and 09/mern-mediastream/server/server.js: -------------------------------------------------------------------------------- 1 | import config from './../config/config' 2 | import app from './express' 3 | import mongoose from 'mongoose' 4 | 5 | // Connection URL 6 | mongoose.Promise = global.Promise 7 | mongoose.connect(config.mongoUri) 8 | mongoose.connection.on('error', () => { 9 | throw new Error(`unable to connect to database: ${config.mongoUri}`) 10 | }) 11 | 12 | app.listen(config.port, (err) => { 13 | if (err) { 14 | console.log(err) 15 | } 16 | console.info('Server started on port %s.', config.port) 17 | }) 18 | -------------------------------------------------------------------------------- /Chapter08 and 09/mern-mediastream/template.js: -------------------------------------------------------------------------------- 1 | export default ({markup, css}) => { 2 | return ` 3 | 4 | 5 | 6 | MERN Mediastream 7 | 8 | 9 | 14 | 15 | 16 |
${markup}
17 | 18 | 19 | 20 | ` 21 | } 22 | -------------------------------------------------------------------------------- /Chapter08 and 09/mern-mediastream/webpack.config.client.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | const CURRENT_WORKING_DIR = process.cwd() 4 | 5 | const config = { 6 | name: "browser", 7 | mode: "development", 8 | devtool: 'eval-source-map', 9 | entry: [ 10 | 'react-hot-loader/patch', 11 | 'webpack-hot-middleware/client?reload=true', 12 | path.join(CURRENT_WORKING_DIR, 'client/main.js') 13 | ], 14 | output: { 15 | path: path.join(CURRENT_WORKING_DIR , '/dist'), 16 | filename: 'bundle.js', 17 | publicPath: '/dist/' 18 | }, 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.jsx?$/, 23 | exclude: /node_modules/, 24 | use: [ 25 | 'babel-loader' 26 | ] 27 | }, 28 | { 29 | test: /\.(ttf|eot|svg|gif|jpg|png)(\?[\s\S]+)?$/, 30 | use: 'file-loader' 31 | } 32 | ] 33 | }, plugins: [ 34 | new webpack.HotModuleReplacementPlugin(), 35 | new webpack.NoEmitOnErrorsPlugin() 36 | ] 37 | } 38 | 39 | module.exports = config 40 | -------------------------------------------------------------------------------- /Chapter08 and 09/mern-mediastream/webpack.config.client.production.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | const CURRENT_WORKING_DIR = process.cwd() 4 | 5 | const config = { 6 | mode: "production", 7 | entry: [ 8 | path.join(CURRENT_WORKING_DIR, 'client/main.js') 9 | ], 10 | output: { 11 | path: path.join(CURRENT_WORKING_DIR , '/dist'), 12 | filename: 'bundle.js', 13 | publicPath: "/dist/" 14 | }, 15 | module: { 16 | rules: [ 17 | { 18 | test: /\.jsx?$/, 19 | exclude: /node_modules/, 20 | use: [ 21 | 'babel-loader' 22 | ] 23 | }, 24 | { 25 | test: /\.(ttf|eot|svg|gif|jpg|png)(\?[\s\S]+)?$/, 26 | use: 'file-loader' 27 | } 28 | ] 29 | } 30 | } 31 | 32 | module.exports = config 33 | -------------------------------------------------------------------------------- /Chapter08 and 09/mern-mediastream/webpack.config.server.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | const nodeExternals = require('webpack-node-externals') 4 | const CURRENT_WORKING_DIR = process.cwd() 5 | 6 | const config = { 7 | name: "server", 8 | entry: [ path.join(CURRENT_WORKING_DIR , './server/server.js') ], 9 | target: "node", 10 | output: { 11 | path: path.join(CURRENT_WORKING_DIR , '/dist/'), 12 | filename: "server.generated.js", 13 | publicPath: '/dist/', 14 | libraryTarget: "commonjs2" 15 | }, 16 | externals: [nodeExternals()], 17 | module: { 18 | rules: [ 19 | { 20 | test: /\.js$/, 21 | exclude: /node_modules/, 22 | use: [ 'babel-loader' ] 23 | }, 24 | { 25 | test: /\.(ttf|eot|svg|gif|jpg|png)(\?[\s\S]+)?$/, 26 | use: 'file-loader' 27 | } 28 | ] 29 | } 30 | } 31 | 32 | module.exports = config 33 | -------------------------------------------------------------------------------- /Chapter10/MERNVR/README.md: -------------------------------------------------------------------------------- 1 | # MERNVR (React 360) 2 | 3 | #### What you need to run this code 4 | 1. Node (8.11.1) 5 | 2. NPM (5.8.0) 6 | 7 | #### How to run this code 8 | 1. Clone this repository 9 | 2. Open command line in the cloned folder, 10 | - To install dependencies, run ``` npm install ``` 11 | - To run the application for development, run ``` npm start ``` 12 | 4. Open [localhost:8081/index.html](http://localhost:8081/index.html) in the browser 13 | -------------------------------------------------------------------------------- /Chapter10/MERNVR/__tests__/index-test.js: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import 'react-360'; 3 | import React from 'react'; 4 | import Index from '../index.js'; 5 | 6 | // Note: test renderer must be required after react-native. 7 | import renderer from 'react-test-renderer'; 8 | 9 | it('renders correctly', () => { 10 | const tree = renderer.create( 11 | 12 | ); 13 | }); 14 | -------------------------------------------------------------------------------- /Chapter10/MERNVR/client.js: -------------------------------------------------------------------------------- 1 | // This file contains the boilerplate to execute your React app. 2 | // If you want to modify your application's content, start in "index.js" 3 | 4 | import {ReactInstance} from 'react-360-web' 5 | 6 | function init(bundle, parent, options = {}) { 7 | const r360 = new ReactInstance(bundle, parent, { 8 | // Add custom options here 9 | fullScreen: true, 10 | ...options, 11 | }) 12 | 13 | // Render your app content to the default cylinder surface 14 | r360.renderToLocation( 15 | r360.createRoot('MERNVR', { /* initial props */ }), 16 | r360.getDefaultLocation() 17 | ) 18 | 19 | // Load the initial environment 20 | r360.compositor.setBackground(r360.getAssetURL('360_world_black.jpg')) 21 | } 22 | 23 | window.React360 = {init} 24 | -------------------------------------------------------------------------------- /Chapter10/MERNVR/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | MERNVR 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Chapter10/MERNVR/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "MERNVR", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "start": "node -e \"console.log('open browser at http://localhost:8081/index.html\\n\\n');\" && node node_modules/react-native/local-cli/cli.js start", 7 | "bundle": "node node_modules/react-360/scripts/bundle.js", 8 | "open": "node -e \"require('xopen')('http://localhost:8081/index.html')\"", 9 | "devtools": "react-devtools", 10 | "test": "jest" 11 | }, 12 | "dependencies": { 13 | "react": "16.0.0", 14 | "react-native": "~0.49.5", 15 | "three": "^0.87.0", 16 | "react-360": "~1.0.0", 17 | "react-360-web": "~1.0.0" 18 | }, 19 | "devDependencies": { 20 | "babel-jest": "^19.0.0", 21 | "babel-preset-react-native": "^1.9.1", 22 | "jest": "^19.0.2", 23 | "react-devtools": "^2.5.2", 24 | "react-test-renderer": "16.0.0", 25 | "xopen": "1.0.0" 26 | }, 27 | "jest": { 28 | "preset": "react-360" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Chapter10/MERNVR/rn-cli.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var blacklist = require('metro-bundler/src/blacklist'); 5 | 6 | var config = { 7 | getProjectRoots() { 8 | return getRoots(); 9 | }, 10 | 11 | getBlacklistRE() { 12 | return blacklist([ 13 | ]); 14 | }, 15 | 16 | getAssetExts() { 17 | return ['obj', 'mtl']; 18 | }, 19 | 20 | getPlatforms() { 21 | return ['vr']; 22 | }, 23 | 24 | getProvidesModuleNodeModules() { 25 | return ['react-native', 'react-360']; 26 | }, 27 | }; 28 | 29 | function getRoots() { 30 | var root = process.env.REACT_NATIVE_APP_ROOT; 31 | if (root) { 32 | return [path.resolve(root)]; 33 | } 34 | return [path.resolve(__dirname)]; 35 | } 36 | 37 | module.exports = config; -------------------------------------------------------------------------------- /Chapter10/MERNVR/static_assets/360_world.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Full-Stack-React-Projects/93940878b3429a415af672d0f3751a2f7e2fe1ca/Chapter10/MERNVR/static_assets/360_world.jpg -------------------------------------------------------------------------------- /Chapter10/MERNVR/static_assets/360_world_black.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Full-Stack-React-Projects/93940878b3429a415af672d0f3751a2f7e2fe1ca/Chapter10/MERNVR/static_assets/360_world_black.jpg -------------------------------------------------------------------------------- /Chapter10/MERNVR/static_assets/clog-up.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Full-Stack-React-Projects/93940878b3429a415af672d0f3751a2f7e2fe1ca/Chapter10/MERNVR/static_assets/clog-up.mp3 -------------------------------------------------------------------------------- /Chapter10/MERNVR/static_assets/collect.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Full-Stack-React-Projects/93940878b3429a415af672d0f3751a2f7e2fe1ca/Chapter10/MERNVR/static_assets/collect.mp3 -------------------------------------------------------------------------------- /Chapter10/MERNVR/static_assets/happy-bot.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Full-Stack-React-Projects/93940878b3429a415af672d0f3751a2f7e2fe1ca/Chapter10/MERNVR/static_assets/happy-bot.mp3 -------------------------------------------------------------------------------- /Chapter11/mern-vrgame/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "env", 4 | "stage-2", 5 | "react" 6 | ], 7 | "plugins": [ 8 | "react-hot-loader/babel" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /Chapter11/mern-vrgame/LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Shama Hoque 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Chapter11/mern-vrgame/README.md: -------------------------------------------------------------------------------- 1 | # MERN VR Game 2 | 3 | ### [Live Demo](http://vrgame.mernbook.com/ "MERN VR Game") 4 | 5 | #### What you need to run this code 6 | 1. Node (8.11.1) 7 | 2. NPM (5.8.0) 8 | 3. MongoDB (3.6.3) 9 | 10 | #### How to run this code 11 | 1. Clone this repository 12 | 2. Open command line in the cloned folder, 13 | - To install dependencies, run ``` npm install ``` 14 | - To run the application for development, run ``` npm run development ``` 15 | 4. Open [localhost:3000](http://localhost:3000/) in the browser 16 | -------------------------------------------------------------------------------- /Chapter11/mern-vrgame/client/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import MainRouter from './MainRouter' 3 | import {BrowserRouter} from 'react-router-dom' 4 | import {MuiThemeProvider, createMuiTheme} from 'material-ui/styles' 5 | import { hot } from 'react-hot-loader' 6 | 7 | // Create a theme instance. 8 | const theme = createMuiTheme({ 9 | palette: { 10 | primary: { 11 | light: '#484848', 12 | main: '#212121', 13 | dark: '#000000', 14 | contrastText: '#fff', 15 | }, 16 | secondary: { 17 | light: '#ffff6e', 18 | main: '#cddc39', 19 | dark: '#99aa00', 20 | contrastText: '#000', 21 | }, 22 | openTitle: '#484848', 23 | protectedTitle: '#7da453', 24 | type: 'light' 25 | } 26 | }) 27 | 28 | const App = () => ( 29 | 30 | 31 | 32 | 33 | 34 | ) 35 | 36 | export default hot(module)(App) 37 | -------------------------------------------------------------------------------- /Chapter11/mern-vrgame/client/MainRouter.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import {Route, Switch} from 'react-router-dom' 3 | import Home from './core/Home' 4 | import Users from './user/Users' 5 | import Signup from './user/Signup' 6 | import Signin from './auth/Signin' 7 | import EditProfile from './user/EditProfile' 8 | import Profile from './user/Profile' 9 | import PrivateRoute from './auth/PrivateRoute' 10 | import Menu from './core/Menu' 11 | import NewGame from './game/NewGame' 12 | import EditGame from './game/EditGame' 13 | 14 | class MainRouter extends Component { 15 | // Removes the server-side injected CSS when React component mounts 16 | componentDidMount() { 17 | const jssStyles = document.getElementById('jss-server-side') 18 | if (jssStyles && jssStyles.parentNode) { 19 | jssStyles.parentNode.removeChild(jssStyles) 20 | } 21 | } 22 | 23 | render() { 24 | return (
25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
) 38 | } 39 | } 40 | 41 | export default MainRouter 42 | -------------------------------------------------------------------------------- /Chapter11/mern-vrgame/client/assets/images/seashell.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Full-Stack-React-Projects/93940878b3429a415af672d0f3751a2f7e2fe1ca/Chapter11/mern-vrgame/client/assets/images/seashell.jpg -------------------------------------------------------------------------------- /Chapter11/mern-vrgame/client/auth/PrivateRoute.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Route, Redirect } from 'react-router-dom' 3 | import auth from './auth-helper' 4 | 5 | const PrivateRoute = ({ component: Component, ...rest }) => ( 6 | ( 7 | auth.isAuthenticated() ? ( 8 | 9 | ) : ( 10 | 14 | ) 15 | )}/> 16 | ) 17 | 18 | export default PrivateRoute 19 | -------------------------------------------------------------------------------- /Chapter11/mern-vrgame/client/auth/api-auth.js: -------------------------------------------------------------------------------- 1 | const signin = (user) => { 2 | return fetch('/auth/signin/', { 3 | method: 'POST', 4 | headers: { 5 | 'Accept': 'application/json', 6 | 'Content-Type': 'application/json' 7 | }, 8 | credentials: 'include', 9 | body: JSON.stringify(user) 10 | }) 11 | .then((response) => { 12 | return response.json() 13 | }).catch((err) => console.log(err)) 14 | } 15 | 16 | const signout = () => { 17 | return fetch('/auth/signout/', { 18 | method: 'GET', 19 | }).then(response => { 20 | return response.json() 21 | }).catch((err) => console.log(err)) 22 | } 23 | 24 | export { 25 | signin, 26 | signout 27 | } 28 | -------------------------------------------------------------------------------- /Chapter11/mern-vrgame/client/auth/auth-helper.js: -------------------------------------------------------------------------------- 1 | import { signout } from './api-auth.js' 2 | 3 | const auth = { 4 | isAuthenticated() { 5 | if (typeof window == "undefined") 6 | return false 7 | 8 | if (sessionStorage.getItem('jwt')) 9 | return JSON.parse(sessionStorage.getItem('jwt')) 10 | else 11 | return false 12 | }, 13 | authenticate(jwt, cb) { 14 | if (typeof window !== "undefined") 15 | sessionStorage.setItem('jwt', JSON.stringify(jwt)) 16 | cb() 17 | }, 18 | signout(cb) { 19 | if (typeof window !== "undefined") 20 | sessionStorage.removeItem('jwt') 21 | cb() 22 | //optional 23 | signout().then((data) => { 24 | document.cookie = "t=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;" 25 | }) 26 | } 27 | } 28 | 29 | export default auth 30 | -------------------------------------------------------------------------------- /Chapter11/mern-vrgame/client/core/Home.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import PropTypes from 'prop-types' 3 | import {withStyles} from 'material-ui/styles' 4 | import Card, {CardContent, CardMedia} from 'material-ui/Card' 5 | import {list} from '../game/api-game.js' 6 | import GameDetail from '../game/GameDetail' 7 | 8 | const styles = theme => ({ 9 | root: { 10 | flexGrow: 1, 11 | margin: '10px 24px', 12 | } 13 | }) 14 | 15 | class Home extends Component { 16 | state={ 17 | games: [] 18 | } 19 | componentDidMount = () => { 20 | list().then((data) => { 21 | if (data.error) { 22 | console.log(data.error) 23 | } else { 24 | this.setState({games: data}) 25 | } 26 | }) 27 | } 28 | updateGames = (game) => { 29 | const updatedGames = this.state.games 30 | const index = updatedGames.indexOf(game) 31 | updatedGames.splice(index, 1) 32 | this.setState({games: updatedGames}) 33 | } 34 | render() { 35 | const {classes} = this.props 36 | return ( 37 |
38 | {this.state.games.map((game, i) => { 39 | return 40 | })} 41 |
42 | ) 43 | } 44 | } 45 | 46 | Home.propTypes = { 47 | classes: PropTypes.object.isRequired 48 | } 49 | 50 | export default withStyles(styles)(Home) 51 | -------------------------------------------------------------------------------- /Chapter11/mern-vrgame/client/core/Menu.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import AppBar from 'material-ui/AppBar' 3 | import Toolbar from 'material-ui/Toolbar' 4 | import Typography from 'material-ui/Typography' 5 | import IconButton from 'material-ui/IconButton' 6 | import HomeIcon from 'material-ui-icons/Home' 7 | import AddBoxIcon from 'material-ui-icons/AddBox' 8 | import Button from 'material-ui/Button' 9 | import auth from './../auth/auth-helper' 10 | import {Link, withRouter} from 'react-router-dom' 11 | 12 | const isActive = (history, path) => { 13 | if (history.location.pathname == path) 14 | return {color: '#cddc39'} 15 | else 16 | return {color: '#ffffff'} 17 | } 18 | const Menu = withRouter(({history}) => ( 19 | 20 | 21 | 22 | MERN VR Game 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 |
31 |
32 | { 33 | !auth.isAuthenticated() && ( 34 | 35 | 37 | 38 | 39 | 41 | 42 | ) 43 | } 44 | { 45 | auth.isAuthenticated() && ( 46 | 47 | 50 | 51 | 52 | 53 | 54 | 57 | ) 58 | } 59 |
60 |
61 |
62 | )) 63 | 64 | export default Menu 65 | -------------------------------------------------------------------------------- /Chapter11/mern-vrgame/client/game/DeleteGame.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import PropTypes from 'prop-types' 3 | import Button from 'material-ui/Button' 4 | import Dialog, {DialogActions, DialogContent, DialogContentText, DialogTitle} from 'material-ui/Dialog' 5 | import auth from './../auth/auth-helper' 6 | import {remove} from './api-game.js' 7 | 8 | class DeleteGame extends Component { 9 | state = { 10 | open: false 11 | } 12 | clickButton = () => { 13 | this.setState({open: true}) 14 | } 15 | deleteGame = () => { 16 | const jwt = auth.isAuthenticated() 17 | remove({ 18 | gameId: this.props.game._id 19 | }, {t: jwt.token}).then((data) => { 20 | if (data.error) { 21 | console.log(data.error) 22 | } else { 23 | this.props.removeGame(this.props.game) 24 | this.setState({open: false}) 25 | } 26 | }) 27 | } 28 | handleRequestClose = () => { 29 | this.setState({open: false}) 30 | } 31 | render() { 32 | return ( 33 | 36 | 37 | {"Delete "+this.props.game.name} 38 | 39 | 40 | Confirm to delete your game {this.props.game.name}. 41 | 42 | 43 | 44 | 47 | 50 | 51 | 52 | ) 53 | } 54 | } 55 | DeleteGame.propTypes = { 56 | game: PropTypes.object.isRequired, 57 | removeGame: PropTypes.func.isRequired 58 | } 59 | 60 | export default DeleteGame 61 | -------------------------------------------------------------------------------- /Chapter11/mern-vrgame/client/game/EditGame.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import auth from './../auth/auth-helper' 3 | import PropTypes from 'prop-types' 4 | import {update} from './api-game.js' 5 | import {Redirect} from 'react-router-dom' 6 | import GameForm from './GameForm' 7 | 8 | class EditGame extends Component { 9 | constructor({match}) { 10 | super() 11 | this.state = { 12 | redirect: false, 13 | error: '', 14 | } 15 | this.match = match 16 | } 17 | 18 | clickSubmit = game => event => { 19 | const jwt = auth.isAuthenticated() 20 | update({ 21 | gameId: this.match.params.gameId 22 | }, { 23 | t: jwt.token 24 | }, game).then((data) => { 25 | if (data.error) { 26 | this.setState({error: data.error}) 27 | } else { 28 | this.setState({error: '', redirect: true}) 29 | } 30 | }) 31 | } 32 | 33 | render() { 34 | if (this.state.redirect) { 35 | return () 36 | } 37 | const {classes} = this.props 38 | return ( 39 | 40 | ) 41 | } 42 | } 43 | 44 | export default EditGame 45 | -------------------------------------------------------------------------------- /Chapter11/mern-vrgame/client/game/NewGame.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import auth from './../auth/auth-helper' 3 | import PropTypes from 'prop-types' 4 | import {create} from './api-game.js' 5 | import {Redirect} from 'react-router-dom' 6 | import GameForm from './GameForm' 7 | 8 | class NewGame extends Component { 9 | state = { 10 | redirect: false, 11 | error: '' 12 | } 13 | clickSubmit = game => event => { 14 | const jwt = auth.isAuthenticated() 15 | create({ 16 | userId: jwt.user._id 17 | }, { 18 | t: jwt.token 19 | }, game).then((data) => { 20 | if (data.error) { 21 | this.setState({error: data.error}) 22 | } else { 23 | this.setState({error: '', redirect: true}) 24 | } 25 | }) 26 | } 27 | 28 | render() { 29 | if (this.state.redirect) { 30 | return () 31 | } 32 | return ( 33 | 34 | ) 35 | } 36 | } 37 | 38 | export default NewGame 39 | -------------------------------------------------------------------------------- /Chapter11/mern-vrgame/client/game/api-game.js: -------------------------------------------------------------------------------- 1 | const create = (params, credentials, game) => { 2 | return fetch('/api/games/by/'+ params.userId, { 3 | method: 'POST', 4 | headers: { 5 | 'Accept': 'application/json', 6 | 'Content-Type': 'application/json', 7 | 'Authorization': 'Bearer ' + credentials.t 8 | }, 9 | body: JSON.stringify(game) 10 | }) 11 | .then((response) => { 12 | return response.json() 13 | }).catch((err) => console.log(err)) 14 | } 15 | 16 | const list = () => { 17 | return fetch('/api/games', { 18 | method: 'GET', 19 | }).then(response => { 20 | return response.json() 21 | }).catch((err) => console.log(err)) 22 | } 23 | 24 | const listByMaker = (params) => { 25 | return fetch('/api/games/by/'+params.userId, { 26 | method: 'GET', 27 | headers: { 28 | 'Accept': 'application/json' 29 | } 30 | }).then((response) => { 31 | return response.json() 32 | }).catch((err) => { 33 | console.log(err) 34 | }) 35 | } 36 | 37 | const read = (params, credentials) => { 38 | return fetch('/api/game/' + params.gameId, { 39 | method: 'GET' 40 | }).then((response) => { 41 | return response.json() 42 | }).catch((err) => console.log(err)) 43 | } 44 | 45 | const update = (params, credentials, game) => { 46 | return fetch('/api/games/' + params.gameId, { 47 | method: 'PUT', 48 | headers: { 49 | 'Accept': 'application/json', 50 | 'Content-Type': 'application/json', 51 | 'Authorization': 'Bearer ' + credentials.t 52 | }, 53 | body: JSON.stringify(game) 54 | }).then((response) => { 55 | return response.json() 56 | }).catch((err) => { 57 | console.log(err) 58 | }) 59 | } 60 | 61 | const remove = (params, credentials) => { 62 | return fetch('/api/games/' + params.gameId, { 63 | method: 'DELETE', 64 | headers: { 65 | 'Accept': 'application/json', 66 | 'Content-Type': 'application/json', 67 | 'Authorization': 'Bearer ' + credentials.t 68 | } 69 | }).then((response) => { 70 | return response.json() 71 | }).catch((err) => { 72 | console.log(err) 73 | }) 74 | } 75 | 76 | export { 77 | create, 78 | list, 79 | listByMaker, 80 | read, 81 | update, 82 | remove 83 | } 84 | -------------------------------------------------------------------------------- /Chapter11/mern-vrgame/client/main.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { hydrate } from 'react-dom' 3 | import App from './App' 4 | 5 | hydrate(, document.getElementById('root')) 6 | -------------------------------------------------------------------------------- /Chapter11/mern-vrgame/client/user/DeleteUser.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import PropTypes from 'prop-types' 3 | import IconButton from 'material-ui/IconButton' 4 | import Button from 'material-ui/Button' 5 | import DeleteIcon from 'material-ui-icons/Delete' 6 | import Dialog, {DialogActions, DialogContent, DialogContentText, DialogTitle} from 'material-ui/Dialog' 7 | import auth from './../auth/auth-helper' 8 | import {remove} from './api-user.js' 9 | import {Redirect, Link} from 'react-router-dom' 10 | 11 | class DeleteUser extends Component { 12 | state = { 13 | redirect: false, 14 | open: false 15 | } 16 | clickButton = () => { 17 | this.setState({open: true}) 18 | } 19 | deleteAccount = () => { 20 | const jwt = auth.isAuthenticated() 21 | remove({ 22 | userId: this.props.userId 23 | }, {t: jwt.token}).then((data) => { 24 | if (data.error) { 25 | console.log(data.error) 26 | } else { 27 | auth.signout(() => console.log('deleted')) 28 | this.setState({redirect: true}) 29 | } 30 | }) 31 | } 32 | handleRequestClose = () => { 33 | this.setState({open: false}) 34 | } 35 | render() { 36 | const redirect = this.state.redirect 37 | if (redirect) { 38 | return 39 | } 40 | return ( 41 | 42 | 43 | 44 | 45 | 46 | {"Delete Account"} 47 | 48 | 49 | Confirm to delete your account. 50 | 51 | 52 | 53 | 56 | 59 | 60 | 61 | ) 62 | } 63 | } 64 | DeleteUser.propTypes = { 65 | userId: PropTypes.string.isRequired 66 | } 67 | export default DeleteUser 68 | -------------------------------------------------------------------------------- /Chapter11/mern-vrgame/client/user/api-user.js: -------------------------------------------------------------------------------- 1 | const create = (user) => { 2 | return fetch('/api/users/', { 3 | method: 'POST', 4 | headers: { 5 | 'Accept': 'application/json', 6 | 'Content-Type': 'application/json' 7 | }, 8 | body: JSON.stringify(user) 9 | }) 10 | .then((response) => { 11 | return response.json() 12 | }).catch((err) => console.log(err)) 13 | } 14 | 15 | const list = () => { 16 | return fetch('/api/users/', { 17 | method: 'GET', 18 | }).then(response => { 19 | return response.json() 20 | }).catch((err) => console.log(err)) 21 | } 22 | 23 | const read = (params, credentials) => { 24 | return fetch('/api/users/' + params.userId, { 25 | method: 'GET', 26 | headers: { 27 | 'Accept': 'application/json', 28 | 'Content-Type': 'application/json', 29 | 'Authorization': 'Bearer ' + credentials.t 30 | } 31 | }).then((response) => { 32 | return response.json() 33 | }).catch((err) => console.log(err)) 34 | } 35 | 36 | const update = (params, credentials, user) => { 37 | return fetch('/api/users/' + params.userId, { 38 | method: 'PUT', 39 | headers: { 40 | 'Accept': 'application/json', 41 | 'Content-Type': 'application/json', 42 | 'Authorization': 'Bearer ' + credentials.t 43 | }, 44 | body: JSON.stringify(user) 45 | }).then((response) => { 46 | return response.json() 47 | }).catch((err) => console.log(err)) 48 | } 49 | 50 | const remove = (params, credentials) => { 51 | return fetch('/api/users/' + params.userId, { 52 | method: 'DELETE', 53 | headers: { 54 | 'Accept': 'application/json', 55 | 'Content-Type': 'application/json', 56 | 'Authorization': 'Bearer ' + credentials.t 57 | } 58 | }).then((response) => { 59 | return response.json() 60 | }).catch((err) => console.log(err)) 61 | } 62 | 63 | export { 64 | create, 65 | list, 66 | read, 67 | update, 68 | remove 69 | } 70 | -------------------------------------------------------------------------------- /Chapter11/mern-vrgame/config/config.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | env: process.env.NODE_ENV || 'development', 3 | port: process.env.PORT || 3000, 4 | jwtSecret: process.env.JWT_SECRET || "YOUR_secret_key", 5 | mongoUri: process.env.MONGODB_URI || 6 | process.env.MONGO_HOST || 7 | 'mongodb://' + (process.env.IP || 'localhost') + ':' + 8 | (process.env.MONGO_PORT || '27017') + 9 | '/mernproject' 10 | } 11 | 12 | export default config 13 | -------------------------------------------------------------------------------- /Chapter11/mern-vrgame/dist/static_assets/360_world.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Full-Stack-React-Projects/93940878b3429a415af672d0f3751a2f7e2fe1ca/Chapter11/mern-vrgame/dist/static_assets/360_world.jpg -------------------------------------------------------------------------------- /Chapter11/mern-vrgame/dist/static_assets/360_world_black.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Full-Stack-React-Projects/93940878b3429a415af672d0f3751a2f7e2fe1ca/Chapter11/mern-vrgame/dist/static_assets/360_world_black.jpg -------------------------------------------------------------------------------- /Chapter11/mern-vrgame/dist/static_assets/clog-up.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Full-Stack-React-Projects/93940878b3429a415af672d0f3751a2f7e2fe1ca/Chapter11/mern-vrgame/dist/static_assets/clog-up.mp3 -------------------------------------------------------------------------------- /Chapter11/mern-vrgame/dist/static_assets/collect.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Full-Stack-React-Projects/93940878b3429a415af672d0f3751a2f7e2fe1ca/Chapter11/mern-vrgame/dist/static_assets/collect.mp3 -------------------------------------------------------------------------------- /Chapter11/mern-vrgame/dist/static_assets/happy-bot.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Full-Stack-React-Projects/93940878b3429a415af672d0f3751a2f7e2fe1ca/Chapter11/mern-vrgame/dist/static_assets/happy-bot.mp3 -------------------------------------------------------------------------------- /Chapter11/mern-vrgame/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "verbose": false, 3 | "watch": [ 4 | "./server" 5 | ], 6 | "exec": "webpack --mode=development --config webpack.config.server.js && node ./dist/server.generated.js" 7 | } 8 | -------------------------------------------------------------------------------- /Chapter11/mern-vrgame/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mern-vrgame", 3 | "version": "1.0.0", 4 | "description": "A MERN stack based VR game for the web", 5 | "author": "Shama Hoque", 6 | "license": "MIT", 7 | "keywords": [ 8 | "react", 9 | "express", 10 | "mongodb", 11 | "node", 12 | "mern" 13 | ], 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/shamahoque/mern-vrgame.git" 17 | }, 18 | "homepage": "https://github.com/shamahoque/mern-vrgame", 19 | "main": "./dist/server.generated.js", 20 | "scripts": { 21 | "development": "nodemon", 22 | "build": "webpack --config webpack.config.client.production.js && webpack --mode=production --config webpack.config.server.js", 23 | "start": "NODE_ENV=production node ./dist/server.generated.js" 24 | }, 25 | "engines": { 26 | "node": "8.11.1", 27 | "npm": "5.8.0" 28 | }, 29 | "devDependencies": { 30 | "babel-core": "^6.26.2", 31 | "babel-loader": "^7.1.4", 32 | "babel-preset-env": "^1.6.1", 33 | "babel-preset-react": "^6.24.1", 34 | "babel-preset-stage-2": "^6.24.1", 35 | "file-loader": "^1.1.11", 36 | "nodemon": "^1.17.3", 37 | "webpack": "^4.6.0", 38 | "webpack-cli": "^2.0.15", 39 | "webpack-dev-middleware": "^3.1.3", 40 | "webpack-hot-middleware": "^2.22.1", 41 | "webpack-node-externals": "^1.7.2" 42 | }, 43 | "dependencies": { 44 | "body-parser": "^1.18.2", 45 | "compression": "^1.7.2", 46 | "cookie-parser": "^1.4.3", 47 | "cors": "^2.8.4", 48 | "express": "^4.16.3", 49 | "express-jwt": "^5.3.1", 50 | "helmet": "^3.12.0", 51 | "jsonwebtoken": "^8.2.1", 52 | "lodash": "^4.17.10", 53 | "material-ui": "^1.0.0-beta.43", 54 | "material-ui-icons": "^1.0.0-beta.36", 55 | "mongoose": "^5.0.16", 56 | "react": "^16.3.2", 57 | "react-dom": "^16.3.2", 58 | "react-hot-loader": "^4.1.2", 59 | "react-router": "^4.2.0", 60 | "react-router-dom": "^4.2.2" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Chapter11/mern-vrgame/server/controllers/auth.controller.js: -------------------------------------------------------------------------------- 1 | import User from '../models/user.model' 2 | import jwt from 'jsonwebtoken' 3 | import expressJwt from 'express-jwt' 4 | import config from './../../config/config' 5 | 6 | const signin = (req, res) => { 7 | User.findOne({ 8 | "email": req.body.email 9 | }, (err, user) => { 10 | 11 | if (err || !user) 12 | return res.status('401').json({ 13 | error: "User not found" 14 | }) 15 | 16 | if (!user.authenticate(req.body.password)) { 17 | return res.status('401').send({ 18 | error: "Email and password don't match." 19 | }) 20 | } 21 | 22 | const token = jwt.sign({ 23 | _id: user._id 24 | }, config.jwtSecret) 25 | 26 | res.cookie("t", token, { 27 | expire: new Date() + 9999 28 | }) 29 | 30 | return res.json({ 31 | token, 32 | user: {_id: user._id, name: user.name, email: user.email} 33 | }) 34 | 35 | }) 36 | } 37 | 38 | const signout = (req, res) => { 39 | res.clearCookie("t") 40 | return res.status('200').json({ 41 | message: "signed out" 42 | }) 43 | } 44 | 45 | const requireSignin = expressJwt({ 46 | secret: config.jwtSecret, 47 | userProperty: 'auth' 48 | }) 49 | 50 | const hasAuthorization = (req, res, next) => { 51 | const authorized = req.profile && req.auth && req.profile._id == req.auth._id 52 | if (!(authorized)) { 53 | return res.status('403').json({ 54 | error: "User is not authorized" 55 | }) 56 | } 57 | next() 58 | } 59 | 60 | export default { 61 | signin, 62 | signout, 63 | requireSignin, 64 | hasAuthorization 65 | } 66 | -------------------------------------------------------------------------------- /Chapter11/mern-vrgame/server/controllers/user.controller.js: -------------------------------------------------------------------------------- 1 | import User from '../models/user.model' 2 | import _ from 'lodash' 3 | import errorHandler from './../helpers/dbErrorHandler' 4 | 5 | const create = (req, res, next) => { 6 | const user = new User(req.body) 7 | user.save((err, result) => { 8 | if (err) { 9 | return res.status(400).json({ 10 | error: errorHandler.getErrorMessage(err) 11 | }) 12 | } 13 | res.status(200).json({ 14 | message: "Successfully signed up!" 15 | }) 16 | }) 17 | } 18 | 19 | /** 20 | * Load user and append to req. 21 | */ 22 | const userByID = (req, res, next, id) => { 23 | User.findById(id).exec((err, user) => { 24 | if (err || !user) 25 | return res.status('400').json({ 26 | error: "User not found" 27 | }) 28 | req.profile = user 29 | next() 30 | }) 31 | } 32 | 33 | const read = (req, res) => { 34 | req.profile.hashed_password = undefined 35 | req.profile.salt = undefined 36 | return res.json(req.profile) 37 | } 38 | 39 | const list = (req, res) => { 40 | User.find((err, users) => { 41 | if (err) { 42 | return res.status(400).json({ 43 | error: errorHandler.getErrorMessage(err) 44 | }) 45 | } 46 | res.json(users) 47 | }).select('name email updated created') 48 | } 49 | 50 | const update = (req, res, next) => { 51 | let user = req.profile 52 | user = _.extend(user, req.body) 53 | user.updated = Date.now() 54 | user.save((err) => { 55 | if (err) { 56 | return res.status(400).json({ 57 | error: errorHandler.getErrorMessage(err) 58 | }) 59 | } 60 | user.hashed_password = undefined 61 | user.salt = undefined 62 | res.json(user) 63 | }) 64 | } 65 | 66 | const remove = (req, res, next) => { 67 | let user = req.profile 68 | user.remove((err, deletedUser) => { 69 | if (err) { 70 | return res.status(400).json({ 71 | error: errorHandler.getErrorMessage(err) 72 | }) 73 | } 74 | deletedUser.hashed_password = undefined 75 | deletedUser.salt = undefined 76 | res.json(deletedUser) 77 | }) 78 | } 79 | 80 | export default { 81 | create, 82 | userByID, 83 | read, 84 | list, 85 | remove, 86 | update 87 | } 88 | -------------------------------------------------------------------------------- /Chapter11/mern-vrgame/server/devBundle.js: -------------------------------------------------------------------------------- 1 | import config from './../config/config' 2 | import webpack from 'webpack' 3 | import webpackMiddleware from 'webpack-dev-middleware' 4 | import webpackHotMiddleware from 'webpack-hot-middleware' 5 | import webpackConfig from './../webpack.config.client.js' 6 | 7 | const compile = (app) => { 8 | if(config.env === "development"){ 9 | const compiler = webpack(webpackConfig) 10 | const middleware = webpackMiddleware(compiler, { 11 | publicPath: webpackConfig.output.publicPath 12 | }) 13 | app.use(middleware) 14 | app.use(webpackHotMiddleware(compiler)) 15 | } 16 | } 17 | 18 | export default { 19 | compile 20 | } 21 | -------------------------------------------------------------------------------- /Chapter11/mern-vrgame/server/helpers/dbErrorHandler.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Get unique error field name 5 | */ 6 | const getUniqueErrorMessage = (err) => { 7 | let output 8 | try { 9 | let fieldName = err.message.substring(err.message.lastIndexOf('.$') + 2, err.message.lastIndexOf('_1')) 10 | output = fieldName.charAt(0).toUpperCase() + fieldName.slice(1) + ' already exists' 11 | } catch (ex) { 12 | output = 'Unique field already exists' 13 | } 14 | 15 | return output 16 | } 17 | 18 | /** 19 | * Get the error message from error object 20 | */ 21 | const getErrorMessage = (err) => { 22 | let message = '' 23 | 24 | if (err.code) { 25 | switch (err.code) { 26 | case 11000: 27 | case 11001: 28 | message = getUniqueErrorMessage(err) 29 | break 30 | default: 31 | message = 'Something went wrong' 32 | } 33 | } else { 34 | for (let errName in err.errors) { 35 | if (err.errors[errName].message) message = err.errors[errName].message 36 | } 37 | } 38 | 39 | return message 40 | } 41 | 42 | export default {getErrorMessage} 43 | -------------------------------------------------------------------------------- /Chapter11/mern-vrgame/server/models/game.model.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose' 2 | import crypto from 'crypto' 3 | 4 | const VRObjectSchema = new mongoose.Schema({ 5 | objUrl: { 6 | type: String, trim: true, 7 | required: 'ObJ file is required' 8 | }, 9 | mtlUrl: { 10 | type: String, trim: true, 11 | required: 'MTL file is required' 12 | }, 13 | translateX: {type: Number, default: 0}, 14 | translateY: {type: Number, default: 0}, 15 | translateZ: {type: Number, default: 0}, 16 | rotateX: {type: Number, default: 0}, 17 | rotateY: {type: Number, default: 0}, 18 | rotateZ: {type: Number, default: 0}, 19 | scale: {type: Number, default: 1}, 20 | color: {type: String, default: 'white'} 21 | }) 22 | const VRObject = mongoose.model('VRObject',VRObjectSchema) 23 | 24 | const GameSchema = new mongoose.Schema({ 25 | name: { 26 | type: String, 27 | trim: true, 28 | required: 'Name is required' 29 | }, 30 | world: { 31 | type: String, trim: true, 32 | required: 'World image is required' 33 | }, 34 | clue: { 35 | type: String, 36 | trim: true 37 | }, 38 | answerObjects: [VRObjectSchema], 39 | wrongObjects: [VRObjectSchema], 40 | updated: Date, 41 | created: { 42 | type: Date, 43 | default: Date.now 44 | }, 45 | maker: {type: mongoose.Schema.ObjectId, ref: 'User'} 46 | }) 47 | 48 | GameSchema.path('answerObjects').validate(function(v) { 49 | if (v.length == 0) { 50 | this.invalidate('answerObjects', 'Must add alteast one VR object to collect') 51 | } 52 | }, null) 53 | GameSchema.path('wrongObjects').validate(function(v) { 54 | if (v.length == 0) { 55 | this.invalidate('wrongObjects', 'Must add alteast one other VR object') 56 | } 57 | }, null) 58 | export default mongoose.model('Game', GameSchema) 59 | -------------------------------------------------------------------------------- /Chapter11/mern-vrgame/server/models/user.model.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose' 2 | import crypto from 'crypto' 3 | const UserSchema = new mongoose.Schema({ 4 | name: { 5 | type: String, 6 | trim: true, 7 | required: 'Name is required' 8 | }, 9 | email: { 10 | type: String, 11 | trim: true, 12 | unique: 'Email already exists', 13 | match: [/.+\@.+\..+/, 'Please fill a valid email address'], 14 | required: 'Email is required' 15 | }, 16 | hashed_password: { 17 | type: String, 18 | required: "Password is required" 19 | }, 20 | salt: String, 21 | updated: Date, 22 | created: { 23 | type: Date, 24 | default: Date.now 25 | } 26 | }) 27 | 28 | UserSchema 29 | .virtual('password') 30 | .set(function(password) { 31 | this._password = password 32 | this.salt = this.makeSalt() 33 | this.hashed_password = this.encryptPassword(password) 34 | }) 35 | .get(function() { 36 | return this._password 37 | }) 38 | 39 | UserSchema.path('hashed_password').validate(function(v) { 40 | if (this._password && this._password.length < 6) { 41 | this.invalidate('password', 'Password must be at least 6 characters.') 42 | } 43 | if (this.isNew && !this._password) { 44 | this.invalidate('password', 'Password is required') 45 | } 46 | }, null) 47 | 48 | UserSchema.methods = { 49 | authenticate: function(plainText) { 50 | return this.encryptPassword(plainText) === this.hashed_password 51 | }, 52 | encryptPassword: function(password) { 53 | if (!password) return '' 54 | try { 55 | return crypto 56 | .createHmac('sha1', this.salt) 57 | .update(password) 58 | .digest('hex') 59 | } catch (err) { 60 | return '' 61 | } 62 | }, 63 | makeSalt: function() { 64 | return Math.round((new Date().valueOf() * Math.random())) + '' 65 | } 66 | } 67 | 68 | export default mongoose.model('User', UserSchema) 69 | -------------------------------------------------------------------------------- /Chapter11/mern-vrgame/server/routes/auth.routes.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import authCtrl from '../controllers/auth.controller' 3 | 4 | const router = express.Router() 5 | 6 | router.route('/auth/signin') 7 | .post(authCtrl.signin) 8 | router.route('/auth/signout') 9 | .get(authCtrl.signout) 10 | 11 | export default router 12 | -------------------------------------------------------------------------------- /Chapter11/mern-vrgame/server/routes/game.routes.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import userCtrl from '../controllers/user.controller' 3 | import authCtrl from '../controllers/auth.controller' 4 | import gameCtrl from '../controllers/game.controller' 5 | 6 | const router = express.Router() 7 | 8 | router.route('/api/games') 9 | .get(gameCtrl.list) 10 | 11 | router.route('/api/games/by/:userId') 12 | .post(authCtrl.requireSignin, authCtrl.hasAuthorization, gameCtrl.create) 13 | .get(gameCtrl.listByMaker) 14 | 15 | router.route('/api/game/:gameId') 16 | .get(gameCtrl.read) 17 | 18 | router.route('/api/games/:gameId') 19 | .put(authCtrl.requireSignin, gameCtrl.isMaker, gameCtrl.update) 20 | .delete(authCtrl.requireSignin, gameCtrl.isMaker, gameCtrl.remove) 21 | 22 | router.route('/game/play') 23 | .get(gameCtrl.playGame) 24 | 25 | router.param('gameId', gameCtrl.gameByID) 26 | router.param('userId', userCtrl.userByID) 27 | 28 | export default router 29 | -------------------------------------------------------------------------------- /Chapter11/mern-vrgame/server/routes/user.routes.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import userCtrl from '../controllers/user.controller' 3 | import authCtrl from '../controllers/auth.controller' 4 | 5 | const router = express.Router() 6 | 7 | router.route('/api/users') 8 | .get(userCtrl.list) 9 | .post(userCtrl.create) 10 | 11 | router.route('/api/users/:userId') 12 | .get(authCtrl.requireSignin, userCtrl.read) 13 | .put(authCtrl.requireSignin, authCtrl.hasAuthorization, userCtrl.update) 14 | .delete(authCtrl.requireSignin, authCtrl.hasAuthorization, userCtrl.remove) 15 | 16 | router.param('userId', userCtrl.userByID) 17 | 18 | export default router 19 | -------------------------------------------------------------------------------- /Chapter11/mern-vrgame/server/server.js: -------------------------------------------------------------------------------- 1 | import config from './../config/config' 2 | import app from './express' 3 | import mongoose from 'mongoose' 4 | 5 | // Connection URL 6 | mongoose.Promise = global.Promise 7 | mongoose.connect(config.mongoUri) 8 | mongoose.connection.on('error', () => { 9 | throw new Error(`unable to connect to database: ${mongoUri}`) 10 | }) 11 | 12 | app.listen(config.port, (err) => { 13 | if (err) { 14 | console.log(err) 15 | } 16 | console.info('Server started on port %s.', config.port) 17 | }) 18 | -------------------------------------------------------------------------------- /Chapter11/mern-vrgame/server/vr/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | MERNVR 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Chapter11/mern-vrgame/template.js: -------------------------------------------------------------------------------- 1 | export default ({markup, css}) => { 2 | return ` 3 | 4 | 5 | 6 | MERN VR Game 7 | 8 | 9 | 14 | 15 | 16 |
${markup}
17 | 18 | 19 | 20 | ` 21 | } 22 | -------------------------------------------------------------------------------- /Chapter11/mern-vrgame/webpack.config.client.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | const CURRENT_WORKING_DIR = process.cwd() 4 | 5 | const config = { 6 | name: "browser", 7 | mode: "development", 8 | devtool: 'eval-source-map', 9 | entry: [ 10 | 'react-hot-loader/patch', 11 | 'webpack-hot-middleware/client?reload=true', 12 | path.join(CURRENT_WORKING_DIR, 'client/main.js') 13 | ], 14 | output: { 15 | path: path.join(CURRENT_WORKING_DIR , '/dist'), 16 | filename: 'bundle.js', 17 | publicPath: '/dist/' 18 | }, 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.jsx?$/, 23 | exclude: /node_modules/, 24 | use: [ 25 | 'babel-loader' 26 | ] 27 | }, 28 | { 29 | test: /\.(ttf|eot|svg|gif|jpg|png)(\?[\s\S]+)?$/, 30 | use: 'file-loader' 31 | } 32 | ] 33 | }, plugins: [ 34 | new webpack.HotModuleReplacementPlugin(), 35 | new webpack.NoEmitOnErrorsPlugin() 36 | ] 37 | } 38 | 39 | module.exports = config 40 | -------------------------------------------------------------------------------- /Chapter11/mern-vrgame/webpack.config.client.production.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | const CURRENT_WORKING_DIR = process.cwd() 4 | 5 | const config = { 6 | mode: "production", 7 | entry: [ 8 | path.join(CURRENT_WORKING_DIR, 'client/main.js') 9 | ], 10 | output: { 11 | path: path.join(CURRENT_WORKING_DIR , '/dist'), 12 | filename: 'bundle.js', 13 | publicPath: "/dist/" 14 | }, 15 | module: { 16 | rules: [ 17 | { 18 | test: /\.jsx?$/, 19 | exclude: /node_modules/, 20 | use: [ 21 | 'babel-loader' 22 | ] 23 | }, 24 | { 25 | test: /\.(ttf|eot|svg|gif|jpg|png)(\?[\s\S]+)?$/, 26 | use: 'file-loader' 27 | } 28 | ] 29 | } 30 | } 31 | 32 | module.exports = config 33 | -------------------------------------------------------------------------------- /Chapter11/mern-vrgame/webpack.config.server.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | const nodeExternals = require('webpack-node-externals') 4 | const CURRENT_WORKING_DIR = process.cwd() 5 | 6 | const config = { 7 | name: "server", 8 | entry: [ path.join(CURRENT_WORKING_DIR , './server/server.js') ], 9 | target: "node", 10 | output: { 11 | path: path.join(CURRENT_WORKING_DIR , '/dist/'), 12 | filename: "server.generated.js", 13 | publicPath: '/dist/', 14 | libraryTarget: "commonjs2" 15 | }, 16 | externals: [nodeExternals()], 17 | module: { 18 | rules: [ 19 | { 20 | test: /\.js$/, 21 | exclude: /node_modules/, 22 | use: [ 'babel-loader' ] 23 | }, 24 | { 25 | test: /\.(ttf|eot|svg|gif|jpg|png)(\?[\s\S]+)?$/, 26 | use: 'file-loader' 27 | } 28 | ] 29 | } 30 | } 31 | 32 | module.exports = config 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Packt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | --------------------------------------------------------------------------------