├── 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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------