├── .gitattributes
├── 9781484243909.jpg
├── Contributing.md
├── LICENSE.txt
├── README.md
├── errata.md
├── pro-mern-stack-2-02.08-automate
├── .gitignore
├── README.md
├── commands.md
├── package.json
├── public
│ └── index.html
├── server
│ └── server.js
└── src
│ ├── .babelrc
│ └── App.jsx
├── pro-mern-stack-2-03.05-dynamic-composition
├── .gitignore
├── README.md
├── commands.md
├── package.json
├── public
│ └── index.html
├── server
│ └── server.js
└── src
│ ├── .babelrc
│ └── App.jsx
├── pro-mern-stack-2-04.06-stateless-components
├── .gitignore
├── README.md
├── commands.md
├── package.json
├── public
│ └── index.html
├── server
│ └── server.js
└── src
│ ├── .babelrc
│ └── App.jsx
├── pro-mern-stack-2-05.10-displaying-errors
├── .gitignore
├── README.md
├── commands.md
├── package.json
├── public
│ └── index.html
├── server
│ ├── schema.graphql
│ └── server.js
└── src
│ ├── .babelrc
│ └── App.jsx
├── pro-mern-stack-2-06.06-writing-to-mongodb
├── .gitignore
├── README.md
├── commands.md
├── mongoCommands.md
├── package.json
├── public
│ └── index.html
├── scripts
│ ├── init.mongo.js
│ └── trymongo.js
├── server
│ ├── schema.graphql
│ └── server.js
└── src
│ ├── .babelrc
│ └── App.jsx
├── pro-mern-stack-2-07.06-react-proptypes
├── .gitignore
├── README.md
├── api
│ ├── .eslintrc
│ ├── package.json
│ ├── sample.env
│ ├── schema.graphql
│ ├── scripts
│ │ ├── init.mongo.js
│ │ └── trymongo.js
│ └── server.js
├── commands.md
├── mongoCommands.md
└── ui
│ ├── .eslintrc
│ ├── package.json
│ ├── public
│ └── index.html
│ ├── sample.env
│ ├── src
│ ├── .babelrc
│ ├── .eslintrc
│ └── App.jsx
│ └── uiserver.js
├── pro-mern-stack-2-08.06-debugging
├── .gitignore
├── README.md
├── api
│ ├── .eslintrc
│ ├── about.js
│ ├── api_handler.js
│ ├── db.js
│ ├── graphql_date.js
│ ├── issue.js
│ ├── package.json
│ ├── sample.env
│ ├── schema.graphql
│ ├── scripts
│ │ ├── init.mongo.js
│ │ └── trymongo.js
│ └── server.js
├── commands.md
├── mongoCommands.md
└── ui
│ ├── .eslintrc
│ ├── package.json
│ ├── public
│ └── index.html
│ ├── sample.env
│ ├── src
│ ├── .babelrc
│ ├── .eslintrc
│ ├── App.jsx
│ ├── IssueAdd.jsx
│ ├── IssueFilter.jsx
│ ├── IssueList.jsx
│ ├── IssueTable.jsx
│ └── graphQLFetch.js
│ ├── uiserver.js
│ └── webpack.config.js
├── pro-mern-stack-2-09.07-browser-history-router
├── .gitignore
├── README.md
├── api
│ ├── .eslintrc
│ ├── about.js
│ ├── api_handler.js
│ ├── db.js
│ ├── graphql_date.js
│ ├── issue.js
│ ├── package.json
│ ├── sample.env
│ ├── schema.graphql
│ ├── scripts
│ │ ├── init.mongo.js
│ │ └── trymongo.js
│ └── server.js
├── commands.md
├── mongoCommands.md
└── ui
│ ├── .eslintrc
│ ├── package.json
│ ├── public
│ └── index.html
│ ├── sample.env
│ ├── src
│ ├── .babelrc
│ ├── .eslintrc
│ ├── App.jsx
│ ├── Contents.jsx
│ ├── IssueAdd.jsx
│ ├── IssueDetail.jsx
│ ├── IssueEdit.jsx
│ ├── IssueFilter.jsx
│ ├── IssueList.jsx
│ ├── IssueReport.jsx
│ ├── IssueTable.jsx
│ ├── Page.jsx
│ └── graphQLFetch.js
│ ├── uiserver.js
│ └── webpack.config.js
├── pro-mern-stack-2-10.13-deleting-an-issue
├── .gitignore
├── README.md
├── api
│ ├── .eslintrc
│ ├── about.js
│ ├── api_handler.js
│ ├── db.js
│ ├── graphql_date.js
│ ├── issue.js
│ ├── package.json
│ ├── sample.env
│ ├── schema.graphql
│ ├── scripts
│ │ ├── init.mongo.js
│ │ └── trymongo.js
│ └── server.js
├── commands.md
├── mongoCommands.md
└── ui
│ ├── .eslintrc
│ ├── package.json
│ ├── public
│ └── index.html
│ ├── sample.env
│ ├── src
│ ├── .babelrc
│ ├── .eslintrc
│ ├── App.jsx
│ ├── Contents.jsx
│ ├── DateInput.jsx
│ ├── IssueAdd.jsx
│ ├── IssueDetail.jsx
│ ├── IssueEdit.jsx
│ ├── IssueFilter.jsx
│ ├── IssueList.jsx
│ ├── IssueReport.jsx
│ ├── IssueTable.jsx
│ ├── NumInput.jsx
│ ├── Page.jsx
│ ├── TextInput.jsx
│ └── graphQLFetch.js
│ ├── uiserver.js
│ └── webpack.config.js
├── pro-mern-stack-2-11.12-modals
├── .gitignore
├── README.md
├── api
│ ├── .eslintrc
│ ├── about.js
│ ├── api_handler.js
│ ├── db.js
│ ├── graphql_date.js
│ ├── issue.js
│ ├── package.json
│ ├── sample.env
│ ├── schema.graphql
│ ├── scripts
│ │ ├── init.mongo.js
│ │ └── trymongo.js
│ └── server.js
├── commands.md
├── mongoCommands.md
└── ui
│ ├── .eslintrc
│ ├── package.json
│ ├── public
│ ├── bootstrap
│ └── index.html
│ ├── sample.env
│ ├── src
│ ├── .babelrc
│ ├── .eslintrc
│ ├── App.jsx
│ ├── Contents.jsx
│ ├── DateInput.jsx
│ ├── IssueAddNavItem.jsx
│ ├── IssueDetail.jsx
│ ├── IssueEdit.jsx
│ ├── IssueFilter.jsx
│ ├── IssueList.jsx
│ ├── IssueReport.jsx
│ ├── IssueTable.jsx
│ ├── NumInput.jsx
│ ├── Page.jsx
│ ├── TextInput.jsx
│ ├── Toast.jsx
│ └── graphQLFetch.js
│ ├── uiserver.js
│ └── webpack.config.js
├── pro-mern-stack-2-12.14-redirects
├── .gitignore
├── README.md
├── api
│ ├── .eslintrc
│ ├── about.js
│ ├── api_handler.js
│ ├── db.js
│ ├── graphql_date.js
│ ├── issue.js
│ ├── package.json
│ ├── sample.env
│ ├── schema.graphql
│ ├── scripts
│ │ ├── init.mongo.js
│ │ └── trymongo.js
│ └── server.js
├── commands.md
├── mongoCommands.md
└── ui
│ ├── .eslintrc
│ ├── browser
│ ├── .eslintrc
│ └── App.jsx
│ ├── package.json
│ ├── public
│ └── bootstrap
│ ├── sample.env
│ ├── server
│ ├── .eslintrc
│ ├── render.jsx
│ ├── template.js
│ └── uiserver.js
│ ├── src
│ ├── .eslintrc
│ ├── About.jsx
│ ├── Contents.jsx
│ ├── DateInput.jsx
│ ├── IssueAddNavItem.jsx
│ ├── IssueDetail.jsx
│ ├── IssueEdit.jsx
│ ├── IssueFilter.jsx
│ ├── IssueList.jsx
│ ├── IssueReport.jsx
│ ├── IssueTable.jsx
│ ├── NotFound.jsx
│ ├── NumInput.jsx
│ ├── Page.jsx
│ ├── TextInput.jsx
│ ├── Toast.jsx
│ ├── graphQLFetch.js
│ ├── routes.js
│ └── store.js
│ ├── webpack.config.js
│ └── webpack.serverHMR.js
├── pro-mern-stack-2-13.10-search-bar
├── .gitignore
├── README.md
├── api
│ ├── .eslintrc
│ ├── about.js
│ ├── api_handler.js
│ ├── db.js
│ ├── graphql_date.js
│ ├── issue.js
│ ├── package.json
│ ├── sample.env
│ ├── schema.graphql
│ ├── scripts
│ │ ├── generate_data.mongo.js
│ │ ├── init.mongo.js
│ │ └── trymongo.js
│ └── server.js
├── commands.md
├── mongoCommands.md
└── ui
│ ├── .eslintrc
│ ├── browser
│ ├── .eslintrc
│ └── App.jsx
│ ├── package.json
│ ├── public
│ └── bootstrap
│ ├── sample.env
│ ├── server
│ ├── .eslintrc
│ ├── render.jsx
│ ├── template.js
│ └── uiserver.js
│ ├── src
│ ├── .eslintrc
│ ├── About.jsx
│ ├── Contents.jsx
│ ├── DateInput.jsx
│ ├── IssueAddNavItem.jsx
│ ├── IssueDetail.jsx
│ ├── IssueEdit.jsx
│ ├── IssueFilter.jsx
│ ├── IssueList.jsx
│ ├── IssueReport.jsx
│ ├── IssueTable.jsx
│ ├── NotFound.jsx
│ ├── NumInput.jsx
│ ├── Page.jsx
│ ├── Search.jsx
│ ├── TextInput.jsx
│ ├── Toast.jsx
│ ├── graphQLFetch.js
│ ├── routes.js
│ ├── store.js
│ └── withToast.jsx
│ ├── webpack.config.js
│ └── webpack.serverHMR.js
├── pro-mern-stack-2-14.11-cookie-domain
├── .gitignore
├── README.md
├── api
│ ├── .eslintrc
│ ├── about.js
│ ├── api_handler.js
│ ├── auth.js
│ ├── db.js
│ ├── graphql_date.js
│ ├── issue.js
│ ├── package.json
│ ├── sample.env
│ ├── schema.graphql
│ ├── scripts
│ │ ├── generate_data.mongo.js
│ │ ├── init.mongo.js
│ │ └── trymongo.js
│ └── server.js
├── commands.md
├── mongoCommands.md
└── ui
│ ├── .eslintrc
│ ├── browser
│ ├── .eslintrc
│ └── App.jsx
│ ├── package.json
│ ├── public
│ └── bootstrap
│ ├── sample.env
│ ├── server
│ ├── .eslintrc
│ ├── render.jsx
│ ├── template.js
│ └── uiserver.js
│ ├── src
│ ├── .eslintrc
│ ├── About.jsx
│ ├── Contents.jsx
│ ├── DateInput.jsx
│ ├── IssueAddNavItem.jsx
│ ├── IssueDetail.jsx
│ ├── IssueEdit.jsx
│ ├── IssueFilter.jsx
│ ├── IssueList.jsx
│ ├── IssueReport.jsx
│ ├── IssueTable.jsx
│ ├── NotFound.jsx
│ ├── NumInput.jsx
│ ├── Page.jsx
│ ├── Search.jsx
│ ├── SignInNavItem.jsx
│ ├── TextInput.jsx
│ ├── Toast.jsx
│ ├── UserContext.js
│ ├── graphQLFetch.js
│ ├── routes.js
│ ├── store.js
│ └── withToast.jsx
│ ├── webpack.config.js
│ └── webpack.serverHMR.js
└── pro-mern-stack-2-15.07-non-proxy-mode
├── .gitignore
├── README.md
├── api
├── .eslintrc
├── .gitignore
├── about.js
├── api_handler.js
├── auth.js
├── db.js
├── graphql_date.js
├── issue.js
├── package.json
├── sample.env
├── schema.graphql
├── scripts
│ ├── generate_data.mongo.js
│ ├── init.mongo.js
│ └── trymongo.js
└── server.js
├── commands.md
├── mongoCommands.md
└── ui
├── .eslintrc
├── .gitignore
├── browser
├── .eslintrc
└── App.jsx
├── package.json
├── public
└── bootstrap
├── sample.env
├── server
├── .eslintrc
├── render.jsx
├── template.js
└── uiserver.js
├── src
├── .eslintrc
├── About.jsx
├── Contents.jsx
├── DateInput.jsx
├── IssueAddNavItem.jsx
├── IssueDetail.jsx
├── IssueEdit.jsx
├── IssueFilter.jsx
├── IssueList.jsx
├── IssueReport.jsx
├── IssueTable.jsx
├── NotFound.jsx
├── NumInput.jsx
├── Page.jsx
├── Search.jsx
├── SignInNavItem.jsx
├── TextInput.jsx
├── Toast.jsx
├── UserContext.js
├── graphQLFetch.js
├── routes.js
├── store.js
└── withToast.jsx
├── webpack.config.js
└── webpack.serverHMR.js
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/9781484243909.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Apress/pro-mern-stack/811109bb3c40c8f886202c4647dd182f595ac30c/9781484243909.jpg
--------------------------------------------------------------------------------
/Contributing.md:
--------------------------------------------------------------------------------
1 | # Contributing to Apress Source Code
2 |
3 | Copyright for Apress source code belongs to the author(s). However, under fair use you are encouraged to fork and contribute minor corrections and updates for the benefit of the author(s) and other readers.
4 |
5 | ## How to Contribute
6 |
7 | 1. Make sure you have a GitHub account.
8 | 2. Fork the repository for the relevant book.
9 | 3. Create a new branch on which to make your change, e.g.
10 | `git checkout -b my_code_contribution`
11 | 4. Commit your change. Include a commit message describing the correction. Please note that if your commit message is not clear, the correction will not be accepted.
12 | 5. Submit a pull request.
13 |
14 | Thank you for your contribution!
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Freeware License, some rights reserved
2 |
3 | Copyright (c) 2019 Vasan Subramanian
4 |
5 | Permission is hereby granted, free of charge, to anyone obtaining a copy
6 | of this software and associated documentation files (the "Software"),
7 | to work with the Software within the limits of freeware distribution and fair use.
8 | This includes the rights to use, copy, and modify the Software for personal use.
9 | Users are also allowed and encouraged to submit corrections and modifications
10 | to the Software for the benefit of other users.
11 |
12 | It is not allowed to reuse, modify, or redistribute the Software for
13 | commercial use in any way, or for a user’s educational materials such as books
14 | or blog articles without prior permission from the copyright holder.
15 |
16 | The above copyright notice and this permission notice need to be included
17 | in all copies or substantial portions of the software.
18 |
19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | AUTHORS OR COPYRIGHT HOLDERS OR APRESS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25 | SOFTWARE.
26 |
27 |
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Apress Source Code
2 |
3 | This repository accompanies [*Pro MERN Stack*](https://www.apress.com/9781484243909) by Vasan Subramanian (Apress, 2019).
4 |
5 | [comment]: #cover
6 | 
7 |
8 | Download the files as a zip using the green button, or clone the repository to your machine using Git.
9 |
10 | ## Releases
11 |
12 | Release v1.0 corresponds to the code in the published book, without corrections or updates.
13 |
14 | ## Contributions
15 |
16 | See the file Contributing.md for more information on how you can contribute to this repository.
--------------------------------------------------------------------------------
/errata.md:
--------------------------------------------------------------------------------
1 | # Errata for *Book Title*
2 |
3 | On **page xx** [Summary of error]:
4 |
5 | Details of error here. Highlight key pieces in **bold**.
6 |
7 | ***
8 |
9 | On **page xx** [Summary of error]:
10 |
11 | Details of error here. Highlight key pieces in **bold**.
12 |
13 | ***
--------------------------------------------------------------------------------
/pro-mern-stack-2-02.08-automate/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | package-lock.json
3 | public/App.js
4 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-02.08-automate/README.md:
--------------------------------------------------------------------------------
1 | # Pro MERN Stack - 2nd Edition
2 |
3 | You are browsing the source code at the end of one of the sections in the book.
4 |
5 | The project's README which contains the list of all chapters, sections
6 | their sources and other useful information can be found in the
7 | [master branch](https://github.com/vasansr/pro-mern-stack-2).
8 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-02.08-automate/commands.md:
--------------------------------------------------------------------------------
1 | # Command-line commands
2 |
3 | This is a list of all command-line commands used in the book. It includes
4 | installation and other shell-based commands used to try out things or
5 | run scripts manually.
6 |
7 | ## Chapter 2: Hello World
8 |
9 | ### Project Set Up
10 |
11 | ```
12 | nvm install 10
13 | nvm alias default 10
14 | node --version
15 | npm --version
16 | npm install -g npm@6
17 | npm init
18 | npm install express
19 | npm uninstall express
20 | npm install express@4
21 | ```
22 |
23 | ### Express
24 | ```
25 | node server.js
26 | npm start
27 | ```
28 |
29 | ### JSX Transform
30 | ```
31 | npm install --save-dev @babel/core@7 @babel/cli@7
32 | node_modules/.bin/babel --version
33 | npx babel --version
34 | npm install --save-dev @babel/preset-react@7
35 | npx babel src --presets @babel/react --out-dir public
36 | ```
37 |
38 | ### Older Browsers Support
39 | ```
40 | npm install --no-save @babel/plugin-transform-arrow-functions@7
41 | npx babel src --presets @babel/react --plugins=@babel/plugin-transform-arrow-functions --out-dir public
42 | npm uninstall @babel/plugin-transform-arrow-functions@7
43 | npm install --save-dev @babel/preset-env
44 | npx babel src --out-dir public
45 | ```
46 |
47 | ### Automate
48 | ```
49 | npm install nodemon@1
50 | ```
51 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-02.08-automate/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pro-mern-stack-2",
3 | "version": "1.0.0",
4 | "description": "Pro MERN Stack (2nd Edition)",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "nodemon -w server server/server.js",
8 | "compile": "babel src --out-dir public",
9 | "watch": "babel src --out-dir public --watch --verbose",
10 | "test": "echo \"Error: no test specified\" && exit 1"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "git+https://github.com/vasansr/pro-mern-stack-2.git"
15 | },
16 | "author": "vasan.promern@gmail.com",
17 | "license": "ISC",
18 | "homepage": "https://github.com/vasansr/pro-mern-stack-2",
19 | "dependencies": {
20 | "express": "^4.16.4",
21 | "nodemon": "^1.18.9"
22 | },
23 | "devDependencies": {
24 | "@babel/cli": "^7.2.3",
25 | "@babel/core": "^7.2.2",
26 | "@babel/preset-env": "^7.2.3",
27 | "@babel/preset-react": "^7.0.0"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-02.08-automate/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Pro MERN Stack
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-02.08-automate/server/server.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 |
3 | const app = express();
4 |
5 | app.use(express.static('public'));
6 |
7 | app.listen(3000, function () {
8 | console.log('App started on port 3000');
9 | });
10 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-02.08-automate/src/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["@babel/preset-env", {
4 | "targets": {
5 | "ie": "11",
6 | "edge": "15",
7 | "safari": "10",
8 | "firefox": "50",
9 | "chrome": "49"
10 | }
11 | }],
12 | "@babel/preset-react"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-02.08-automate/src/App.jsx:
--------------------------------------------------------------------------------
1 | const continents = ['Africa','America','Asia','Australia','Europe'];
2 | const helloContinents = Array.from(continents, c => `Hello ${c}!`);
3 | const message = helloContinents.join(' ');
4 |
5 | const element = (
6 |
7 |
{message}
8 |
9 | );
10 |
11 | ReactDOM.render(element, document.getElementById('contents'));
12 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-03.05-dynamic-composition/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | package-lock.json
3 | public/App.js
4 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-03.05-dynamic-composition/README.md:
--------------------------------------------------------------------------------
1 | # Pro MERN Stack - 2nd Edition
2 |
3 | You are browsing the source code at the end of one of the sections in the book.
4 |
5 | The project's README which contains the list of all chapters, sections
6 | their sources and other useful information can be found in the
7 | [master branch](https://github.com/vasansr/pro-mern-stack-2).
8 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-03.05-dynamic-composition/commands.md:
--------------------------------------------------------------------------------
1 | # Command-line commands
2 |
3 | This is a list of all command-line commands used in the book. It includes
4 | installation and other shell-based commands used to try out things or
5 | run scripts manually.
6 |
7 | ## Chapter 2: Hello World
8 |
9 | ### Project Set Up
10 |
11 | ```
12 | nvm install 10
13 | nvm alias default 10
14 | node --version
15 | npm --version
16 | npm install -g npm@6
17 | npm init
18 | npm install express
19 | npm uninstall express
20 | npm install express@4
21 | ```
22 |
23 | ### Express
24 | ```
25 | node server.js
26 | npm start
27 | ```
28 |
29 | ### JSX Transform
30 | ```
31 | npm install --save-dev @babel/core@7 @babel/cli@7
32 | node_modules/.bin/babel --version
33 | npx babel --version
34 | npm install --save-dev @babel/preset-react@7
35 | npx babel src --presets @babel/react --out-dir public
36 | ```
37 |
38 | ### Older Browsers Support
39 | ```
40 | npm install --no-save @babel/plugin-transform-arrow-functions@7
41 | npx babel src --presets @babel/react --plugins=@babel/plugin-transform-arrow-functions --out-dir public
42 | npm uninstall @babel/plugin-transform-arrow-functions@7
43 | npm install --save-dev @babel/preset-env
44 | npx babel src --out-dir public
45 | ```
46 |
47 | ### Automate
48 | ```
49 | npm install nodemon@1
50 | ```
51 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-03.05-dynamic-composition/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pro-mern-stack-2",
3 | "version": "1.0.0",
4 | "description": "Pro MERN Stack (2nd Edition)",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "nodemon -w server server/server.js",
8 | "compile": "babel src --out-dir public",
9 | "watch": "babel src --out-dir public --watch --verbose",
10 | "test": "echo \"Error: no test specified\" && exit 1"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "git+https://github.com/vasansr/pro-mern-stack-2.git"
15 | },
16 | "author": "vasan.promern@gmail.com",
17 | "license": "ISC",
18 | "homepage": "https://github.com/vasansr/pro-mern-stack-2",
19 | "dependencies": {
20 | "express": "^4.16.4",
21 | "nodemon": "^1.18.9"
22 | },
23 | "devDependencies": {
24 | "@babel/cli": "^7.2.3",
25 | "@babel/core": "^7.2.2",
26 | "@babel/preset-env": "^7.2.3",
27 | "@babel/preset-react": "^7.0.0"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-03.05-dynamic-composition/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Pro MERN Stack
7 |
8 |
9 |
10 |
11 |
12 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-03.05-dynamic-composition/server/server.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 |
3 | const app = express();
4 |
5 | app.use(express.static('public'));
6 |
7 | app.listen(3000, function () {
8 | console.log('App started on port 3000');
9 | });
10 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-03.05-dynamic-composition/src/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["@babel/preset-env", {
4 | "targets": {
5 | "ie": "11",
6 | "edge": "15",
7 | "safari": "10",
8 | "firefox": "50",
9 | "chrome": "49"
10 | }
11 | }],
12 | "@babel/preset-react"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-04.06-stateless-components/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | package-lock.json
3 | public/App.js
4 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-04.06-stateless-components/README.md:
--------------------------------------------------------------------------------
1 | # Pro MERN Stack - 2nd Edition
2 |
3 | You are browsing the source code at the end of one of the sections in the book.
4 |
5 | The project's README which contains the list of all chapters, sections
6 | their sources and other useful information can be found in the
7 | [master branch](https://github.com/vasansr/pro-mern-stack-2).
8 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-04.06-stateless-components/commands.md:
--------------------------------------------------------------------------------
1 | # Command-line commands
2 |
3 | This is a list of all command-line commands used in the book. It includes
4 | installation and other shell-based commands used to try out things or
5 | run scripts manually.
6 |
7 | ## Chapter 2: Hello World
8 |
9 | ### Project Set Up
10 |
11 | ```
12 | nvm install 10
13 | nvm alias default 10
14 | node --version
15 | npm --version
16 | npm install -g npm@6
17 | npm init
18 | npm install express
19 | npm uninstall express
20 | npm install express@4
21 | ```
22 |
23 | ### Express
24 | ```
25 | node server.js
26 | npm start
27 | ```
28 |
29 | ### JSX Transform
30 | ```
31 | npm install --save-dev @babel/core@7 @babel/cli@7
32 | node_modules/.bin/babel --version
33 | npx babel --version
34 | npm install --save-dev @babel/preset-react@7
35 | npx babel src --presets @babel/react --out-dir public
36 | ```
37 |
38 | ### Older Browsers Support
39 | ```
40 | npm install --no-save @babel/plugin-transform-arrow-functions@7
41 | npx babel src --presets @babel/react --plugins=@babel/plugin-transform-arrow-functions --out-dir public
42 | npm uninstall @babel/plugin-transform-arrow-functions@7
43 | npm install --save-dev @babel/preset-env
44 | npx babel src --out-dir public
45 | ```
46 |
47 | ### Automate
48 | ```
49 | npm install nodemon@1
50 | ```
51 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-04.06-stateless-components/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pro-mern-stack-2",
3 | "version": "1.0.0",
4 | "description": "Pro MERN Stack (2nd Edition)",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "nodemon -w server server/server.js",
8 | "compile": "babel src --out-dir public",
9 | "watch": "babel src --out-dir public --watch --verbose",
10 | "test": "echo \"Error: no test specified\" && exit 1"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "git+https://github.com/vasansr/pro-mern-stack-2.git"
15 | },
16 | "author": "vasan.promern@gmail.com",
17 | "license": "ISC",
18 | "homepage": "https://github.com/vasansr/pro-mern-stack-2",
19 | "dependencies": {
20 | "express": "^4.16.4",
21 | "nodemon": "^1.18.9"
22 | },
23 | "devDependencies": {
24 | "@babel/cli": "^7.2.3",
25 | "@babel/core": "^7.2.2",
26 | "@babel/preset-env": "^7.2.3",
27 | "@babel/preset-react": "^7.0.0"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-04.06-stateless-components/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Pro MERN Stack
7 |
8 |
9 |
10 |
11 |
12 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-04.06-stateless-components/server/server.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 |
3 | const app = express();
4 |
5 | app.use(express.static('public'));
6 |
7 | app.listen(3000, function () {
8 | console.log('App started on port 3000');
9 | });
10 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-04.06-stateless-components/src/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["@babel/preset-env", {
4 | "targets": {
5 | "ie": "11",
6 | "edge": "15",
7 | "safari": "10",
8 | "firefox": "50",
9 | "chrome": "49"
10 | }
11 | }],
12 | "@babel/preset-react"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-05.10-displaying-errors/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | package-lock.json
3 | public/App.js
4 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-05.10-displaying-errors/README.md:
--------------------------------------------------------------------------------
1 | # Pro MERN Stack - 2nd Edition
2 |
3 | You are browsing the source code at the end of one of the sections in the book.
4 |
5 | The project's README which contains the list of all chapters, sections
6 | their sources and other useful information can be found in the
7 | [master branch](https://github.com/vasansr/pro-mern-stack-2).
8 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-05.10-displaying-errors/commands.md:
--------------------------------------------------------------------------------
1 | # Command-line commands
2 |
3 | This is a list of all command-line commands used in the book. It includes
4 | installation and other shell-based commands used to try out things or
5 | run scripts manually.
6 |
7 | ## Chapter 2: Hello World
8 |
9 | ### Project Set Up
10 |
11 | ```
12 | nvm install 10
13 | nvm alias default 10
14 | node --version
15 | npm --version
16 | npm install -g npm@6
17 | npm init
18 | npm install express
19 | npm uninstall express
20 | npm install express@4
21 | ```
22 |
23 | ### Express
24 | ```
25 | node server.js
26 | npm start
27 | ```
28 |
29 | ### JSX Transform
30 | ```
31 | npm install --save-dev @babel/core@7 @babel/cli@7
32 | node_modules/.bin/babel --version
33 | npx babel --version
34 | npm install --save-dev @babel/preset-react@7
35 | npx babel src --presets @babel/react --out-dir public
36 | ```
37 |
38 | ### Older Browsers Support
39 | ```
40 | npm install --no-save @babel/plugin-transform-arrow-functions@7
41 | npx babel src --presets @babel/react --plugins=@babel/plugin-transform-arrow-functions --out-dir public
42 | npm uninstall @babel/plugin-transform-arrow-functions@7
43 | npm install --save-dev @babel/preset-env
44 | npx babel src --out-dir public
45 | ```
46 |
47 | ### Automate
48 | ```
49 | npm install nodemon@1
50 | ```
51 |
52 | ## Chapter 5: Express GraphQL APIs
53 |
54 | ### About API
55 | ```
56 | npm install graphql@0 apollo-server-express@2
57 | curl "http://localhost:3000/graphql?query=query+\{+about+\}"
58 | ```
59 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-05.10-displaying-errors/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pro-mern-stack-2",
3 | "version": "1.0.0",
4 | "description": "Pro MERN Stack (2nd Edition)",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "nodemon -w server -e js,graphql server/server.js",
8 | "compile": "babel src --out-dir public",
9 | "watch": "babel src --out-dir public --watch --verbose",
10 | "test": "echo \"Error: no test specified\" && exit 1"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "git+https://github.com/vasansr/pro-mern-stack-2.git"
15 | },
16 | "author": "vasan.promern@gmail.com",
17 | "license": "ISC",
18 | "homepage": "https://github.com/vasansr/pro-mern-stack-2",
19 | "dependencies": {
20 | "apollo-server-express": "^2.3.1",
21 | "express": "^4.16.4",
22 | "graphql": "^0.13.2",
23 | "nodemon": "^1.18.9"
24 | },
25 | "devDependencies": {
26 | "@babel/cli": "^7.2.3",
27 | "@babel/core": "^7.2.2",
28 | "@babel/preset-env": "^7.2.3",
29 | "@babel/preset-react": "^7.0.0"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-05.10-displaying-errors/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Pro MERN Stack
7 |
8 |
9 |
10 |
11 |
12 |
13 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-05.10-displaying-errors/server/schema.graphql:
--------------------------------------------------------------------------------
1 | scalar GraphQLDate
2 |
3 | enum StatusType {
4 | New
5 | Assigned
6 | Fixed
7 | Closed
8 | }
9 |
10 | type Issue {
11 | id: Int!
12 | title: String!
13 | status: StatusType!
14 | owner: String
15 | effort: Int
16 | created: GraphQLDate!
17 | due: GraphQLDate
18 | }
19 |
20 | "Toned down Issue, used as inputs, without server generated values."
21 | input IssueInputs {
22 | title: String!
23 | "Optional, if not supplied, will be set to 'New'"
24 | status: StatusType = New
25 | owner: String
26 | effort: Int
27 | due: GraphQLDate
28 | }
29 |
30 | ##### Top level declarations
31 |
32 | type Query {
33 | about: String!
34 | issueList: [Issue!]!
35 | }
36 |
37 | type Mutation {
38 | setAboutMessage(message: String!): String
39 | issueAdd(issue: IssueInputs!): Issue!
40 | }
41 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-05.10-displaying-errors/src/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["@babel/preset-env", {
4 | "targets": {
5 | "ie": "11",
6 | "edge": "15",
7 | "safari": "10",
8 | "firefox": "50",
9 | "chrome": "49"
10 | }
11 | }],
12 | "@babel/preset-react"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-06.06-writing-to-mongodb/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | package-lock.json
3 | public/App.js
4 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-06.06-writing-to-mongodb/README.md:
--------------------------------------------------------------------------------
1 | # Pro MERN Stack - 2nd Edition
2 |
3 | You are browsing the source code at the end of one of the sections in the book.
4 |
5 | The project's README which contains the list of all chapters, sections
6 | their sources and other useful information can be found in the
7 | [master branch](https://github.com/vasansr/pro-mern-stack-2).
8 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-06.06-writing-to-mongodb/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pro-mern-stack-2",
3 | "version": "1.0.0",
4 | "description": "Pro MERN Stack (2nd Edition)",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "nodemon -w server -e js,graphql server/server.js",
8 | "compile": "babel src --out-dir public",
9 | "watch": "babel src --out-dir public --watch --verbose",
10 | "test": "echo \"Error: no test specified\" && exit 1"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "git+https://github.com/vasansr/pro-mern-stack-2.git"
15 | },
16 | "author": "vasan.promern@gmail.com",
17 | "license": "ISC",
18 | "homepage": "https://github.com/vasansr/pro-mern-stack-2",
19 | "dependencies": {
20 | "apollo-server-express": "^2.3.1",
21 | "express": "^4.16.4",
22 | "graphql": "^0.13.2",
23 | "mongodb": "^3.1.10",
24 | "nodemon": "^1.18.9"
25 | },
26 | "devDependencies": {
27 | "@babel/cli": "^7.2.3",
28 | "@babel/core": "^7.2.2",
29 | "@babel/preset-env": "^7.2.3",
30 | "@babel/preset-react": "^7.0.0"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-06.06-writing-to-mongodb/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Pro MERN Stack
7 |
8 |
9 |
10 |
11 |
12 |
13 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-06.06-writing-to-mongodb/scripts/init.mongo.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Run using the mongo shell. For remote databases, ensure that the
3 | * connection string is supplied in the command line. For example:
4 | * localhost:
5 | * mongo issuetracker scripts/init.mongo.js
6 | * Atlas:
7 | * mongo mongodb+srv://user:pwd@xxx.mongodb.net/issuetracker scripts/init.mongo.js
8 | * MLab:
9 | * mongo mongodb://user:pwd@xxx.mlab.com:33533/issuetracker scripts/init.mongo.js
10 | */
11 |
12 | db.issues.remove({});
13 |
14 | const issuesDB = [
15 | {
16 | id: 1, status: 'New', owner: 'Ravan', effort: 5,
17 | created: new Date('2019-01-15'), due: undefined,
18 | title: 'Error in console when clicking Add',
19 | },
20 | {
21 | id: 2, status: 'Assigned', owner: 'Eddie', effort: 14,
22 | created: new Date('2019-01-16'), due: new Date('2019-02-01'),
23 | title: 'Missing bottom border on panel',
24 | },
25 | ];
26 |
27 | db.issues.insertMany(issuesDB);
28 | const count = db.issues.count();
29 | print('Inserted', count, 'issues');
30 |
31 | db.counters.remove({ _id: 'issues' });
32 | db.counters.insert({ _id: 'issues', current: count });
33 |
34 | db.issues.createIndex({ id: 1 }, { unique: true });
35 | db.issues.createIndex({ status: 1 });
36 | db.issues.createIndex({ owner: 1 });
37 | db.issues.createIndex({ created: 1 });
38 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-06.06-writing-to-mongodb/server/schema.graphql:
--------------------------------------------------------------------------------
1 | scalar GraphQLDate
2 |
3 | enum StatusType {
4 | New
5 | Assigned
6 | Fixed
7 | Closed
8 | }
9 |
10 | type Issue {
11 | _id: ID!
12 | id: Int!
13 | title: String!
14 | status: StatusType!
15 | owner: String
16 | effort: Int
17 | created: GraphQLDate!
18 | due: GraphQLDate
19 | }
20 |
21 | "Toned down Issue, used as inputs, without server generated values."
22 | input IssueInputs {
23 | title: String!
24 | "Optional, if not supplied, will be set to 'New'"
25 | status: StatusType = New
26 | owner: String
27 | effort: Int
28 | due: GraphQLDate
29 | }
30 |
31 | ##### Top level declarations
32 |
33 | type Query {
34 | about: String!
35 | issueList: [Issue!]!
36 | }
37 |
38 | type Mutation {
39 | setAboutMessage(message: String!): String
40 | issueAdd(issue: IssueInputs!): Issue!
41 | }
42 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-06.06-writing-to-mongodb/src/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["@babel/preset-env", {
4 | "targets": {
5 | "ie": "11",
6 | "edge": "15",
7 | "safari": "10",
8 | "firefox": "50",
9 | "chrome": "49"
10 | }
11 | }],
12 | "@babel/preset-react"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-07.06-react-proptypes/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | package-lock.json
3 | ui/public/App.js
4 | .env
5 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-07.06-react-proptypes/README.md:
--------------------------------------------------------------------------------
1 | # Pro MERN Stack - 2nd Edition
2 |
3 | You are browsing the source code at the end of one of the sections in the book.
4 |
5 | The project's README which contains the list of all chapters, sections
6 | their sources and other useful information can be found in the
7 | [master branch](https://github.com/vasansr/pro-mern-stack-2).
8 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-07.06-react-proptypes/api/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb-base",
3 | "env": {
4 | "node": "true"
5 | },
6 | rules: {
7 | "no-console": "off"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-07.06-react-proptypes/api/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pro-mern-stack-2-api",
3 | "version": "1.0.0",
4 | "description": "Pro MERN Stack (2nd Edition) API",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "nodemon -e js,graphql -w . -w .env server.js",
8 | "lint": "eslint .",
9 | "test": "echo \"Error: no test specified\" && exit 1"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git+https://github.com/vasansr/pro-mern-stack-2.git"
14 | },
15 | "author": "vasan.promern@gmail.com",
16 | "license": "ISC",
17 | "homepage": "https://github.com/vasansr/pro-mern-stack-2",
18 | "dependencies": {
19 | "apollo-server-express": "^2.3.1",
20 | "dotenv": "^6.2.0",
21 | "express": "^4.16.4",
22 | "graphql": "^0.13.2",
23 | "mongodb": "^3.1.10",
24 | "nodemon": "^1.18.9"
25 | },
26 | "devDependencies": {
27 | "eslint": "^5.12.0",
28 | "eslint-config-airbnb-base": "^13.1.0",
29 | "eslint-plugin-import": "^2.14.0"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-07.06-react-proptypes/api/sample.env:
--------------------------------------------------------------------------------
1 | ## DB
2 | # Local
3 | DB_URL=mongodb://localhost/issuetracker
4 |
5 | # Atlas - replace UUU: user, PPP: password, XXX: hostname
6 | # DB_URL=mongodb+srv://UUU:PPP@XXX.mongodb.net/issuetracker?retryWrites=true
7 |
8 | # mLab - replace UUU: user, PPP: password, XXX: hostname, YYY: port
9 | # DB_URL=mongodb://UUU:PPP@XXX.mlab.com:YYY/issuetracker
10 |
11 |
12 | ## Server Port
13 | API_SERVER_PORT=3000
14 |
15 | ## Enable CORS (default: true)
16 | # ENABLE_CORS=false
17 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-07.06-react-proptypes/api/schema.graphql:
--------------------------------------------------------------------------------
1 | scalar GraphQLDate
2 |
3 | enum StatusType {
4 | New
5 | Assigned
6 | Fixed
7 | Closed
8 | }
9 |
10 | type Issue {
11 | _id: ID!
12 | id: Int!
13 | title: String!
14 | status: StatusType!
15 | owner: String
16 | effort: Int
17 | created: GraphQLDate!
18 | due: GraphQLDate
19 | }
20 |
21 | "Toned down Issue, used as inputs, without server generated values."
22 | input IssueInputs {
23 | title: String!
24 | "Optional, if not supplied, will be set to 'New'"
25 | status: StatusType = New
26 | owner: String
27 | effort: Int
28 | due: GraphQLDate
29 | }
30 |
31 | ##### Top level declarations
32 |
33 | type Query {
34 | about: String!
35 | issueList: [Issue!]!
36 | }
37 |
38 | type Mutation {
39 | setAboutMessage(message: String!): String
40 | issueAdd(issue: IssueInputs!): Issue!
41 | }
42 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-07.06-react-proptypes/api/scripts/init.mongo.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Run using the mongo shell. For remote databases, ensure that the
3 | * connection string is supplied in the command line. For example:
4 | * localhost:
5 | * mongo issuetracker scripts/init.mongo.js
6 | * Atlas:
7 | * mongo mongodb+srv://user:pwd@xxx.mongodb.net/issuetracker scripts/init.mongo.js
8 | * MLab:
9 | * mongo mongodb://user:pwd@xxx.mlab.com:33533/issuetracker scripts/init.mongo.js
10 | */
11 |
12 | /* global db print */
13 | /* eslint no-restricted-globals: "off" */
14 |
15 | db.issues.remove({});
16 |
17 | const issuesDB = [
18 | {
19 | id: 1,
20 | status: 'New',
21 | owner: 'Ravan',
22 | effort: 5,
23 | created: new Date('2019-01-15'),
24 | due: undefined,
25 | title: 'Error in console when clicking Add',
26 | },
27 | {
28 | id: 2,
29 | status: 'Assigned',
30 | owner: 'Eddie',
31 | effort: 14,
32 | created: new Date('2019-01-16'),
33 | due: new Date('2019-02-01'),
34 | title: 'Missing bottom border on panel',
35 | },
36 | ];
37 |
38 | db.issues.insertMany(issuesDB);
39 | const count = db.issues.count();
40 | print('Inserted', count, 'issues');
41 |
42 | db.counters.remove({ _id: 'issues' });
43 | db.counters.insert({ _id: 'issues', current: count });
44 |
45 | db.issues.createIndex({ id: 1 }, { unique: true });
46 | db.issues.createIndex({ status: 1 });
47 | db.issues.createIndex({ owner: 1 });
48 | db.issues.createIndex({ created: 1 });
49 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-07.06-react-proptypes/ui/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb-base",
3 | "env": {
4 | "node": true
5 | },
6 | "rules": {
7 | "no-console": "off"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-07.06-react-proptypes/ui/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pro-mern-stack-2-ui",
3 | "version": "1.0.0",
4 | "description": "Pro MERN Stack (2nd Edition) - UI",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "nodemon -w uiserver.js -w .env uiserver.js",
8 | "lint": "eslint . --ext js,jsx --ignore-pattern public",
9 | "compile": "babel src --out-dir public",
10 | "watch": "babel src --out-dir public --watch --verbose"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "git+https://github.com/vasansr/pro-mern-stack-2.git"
15 | },
16 | "author": "vasan.promern@gmail.com",
17 | "license": "ISC",
18 | "homepage": "https://github.com/vasansr/pro-mern-stack-2",
19 | "dependencies": {
20 | "dotenv": "^6.2.0",
21 | "express": "^4.16.4",
22 | "http-proxy-middleware": "^0.19.1",
23 | "nodemon": "^1.18.9"
24 | },
25 | "devDependencies": {
26 | "@babel/cli": "^7.2.3",
27 | "@babel/core": "^7.2.2",
28 | "@babel/preset-env": "^7.2.3",
29 | "@babel/preset-react": "^7.0.0",
30 | "eslint": "^5.12.0",
31 | "eslint-config-airbnb": "^17.1.0",
32 | "eslint-plugin-import": "^2.14.0",
33 | "eslint-plugin-jsx-a11y": "^6.1.2",
34 | "eslint-plugin-react": "^7.12.3"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-07.06-react-proptypes/ui/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Pro MERN Stack
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-07.06-react-proptypes/ui/sample.env:
--------------------------------------------------------------------------------
1 | UI_SERVER_PORT=8000
2 | UI_API_ENDPOINT=http://localhost:3000/graphql
3 | # API_PROXY_TARGET=http://localhost:3000
4 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-07.06-react-proptypes/ui/src/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["@babel/preset-env", {
4 | "targets": {
5 | "ie": "11",
6 | "edge": "15",
7 | "safari": "10",
8 | "firefox": "50",
9 | "chrome": "49"
10 | }
11 | }],
12 | "@babel/preset-react"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-07.06-react-proptypes/ui/src/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb",
3 | "env": {
4 | "browser": true
5 | },
6 | rules: {
7 | "react/prop-types": "off"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-07.06-react-proptypes/ui/uiserver.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config();
2 | const express = require('express');
3 | const proxy = require('http-proxy-middleware');
4 |
5 | const app = express();
6 |
7 | app.use(express.static('public'));
8 |
9 | const apiProxyTarget = process.env.API_PROXY_TARGET;
10 | if (apiProxyTarget) {
11 | app.use('/graphql', proxy({ target: apiProxyTarget }));
12 | }
13 |
14 | const UI_API_ENDPOINT = process.env.UI_API_ENDPOINT
15 | || 'http://localhost:3000/graphql';
16 | const env = { UI_API_ENDPOINT };
17 |
18 | app.get('/env.js', (req, res) => {
19 | res.send(`window.ENV = ${JSON.stringify(env)}`);
20 | });
21 |
22 | const port = process.env.UI_SERVER_PORT || 8000;
23 |
24 | app.listen(port, () => {
25 | console.log(`UI started on port ${port}`);
26 | });
27 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-08.06-debugging/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | package-lock.json
3 | ui/public/*.js
4 | ui/public/*.map
5 | .env
6 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-08.06-debugging/README.md:
--------------------------------------------------------------------------------
1 | # Pro MERN Stack - 2nd Edition
2 |
3 | You are browsing the source code at the end of one of the sections in the book.
4 |
5 | The project's README which contains the list of all chapters, sections
6 | their sources and other useful information can be found in the
7 | [master branch](https://github.com/vasansr/pro-mern-stack-2).
8 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-08.06-debugging/api/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb-base",
3 | "env": {
4 | "node": "true"
5 | },
6 | rules: {
7 | "no-console": "off"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-08.06-debugging/api/about.js:
--------------------------------------------------------------------------------
1 | let aboutMessage = 'Issue Tracker API v1.0';
2 |
3 | function setMessage(_, { message }) {
4 | aboutMessage = message;
5 | return aboutMessage;
6 | }
7 |
8 | function getMessage() {
9 | return aboutMessage;
10 | }
11 |
12 | module.exports = { getMessage, setMessage };
13 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-08.06-debugging/api/api_handler.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | require('dotenv').config();
3 | const { ApolloServer } = require('apollo-server-express');
4 |
5 | const GraphQLDate = require('./graphql_date.js');
6 | const about = require('./about.js');
7 | const issue = require('./issue.js');
8 |
9 | const resolvers = {
10 | Query: {
11 | about: about.getMessage,
12 | issueList: issue.list,
13 | },
14 | Mutation: {
15 | setAboutMessage: about.setMessage,
16 | issueAdd: issue.add,
17 | },
18 | GraphQLDate,
19 | };
20 |
21 | const server = new ApolloServer({
22 | typeDefs: fs.readFileSync('schema.graphql', 'utf-8'),
23 | resolvers,
24 | formatError: (error) => {
25 | console.log(error);
26 | return error;
27 | },
28 | });
29 |
30 | function installHandler(app) {
31 | const enableCors = (process.env.ENABLE_CORS || 'true') === 'true';
32 | console.log('CORS setting:', enableCors);
33 | server.applyMiddleware({ app, path: '/graphql', cors: enableCors });
34 | }
35 |
36 | module.exports = { installHandler };
37 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-08.06-debugging/api/db.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config();
2 | const { MongoClient } = require('mongodb');
3 |
4 | let db;
5 |
6 | async function connectToDb() {
7 | const url = process.env.DB_URL || 'mongodb://localhost/issuetracker';
8 | const client = new MongoClient(url, { useNewUrlParser: true });
9 | await client.connect();
10 | console.log('Connected to MongoDB at', url);
11 | db = client.db();
12 | }
13 |
14 | async function getNextSequence(name) {
15 | const result = await db.collection('counters').findOneAndUpdate(
16 | { _id: name },
17 | { $inc: { current: 1 } },
18 | { returnOriginal: false },
19 | );
20 | return result.value.current;
21 | }
22 |
23 | function getDb() {
24 | return db;
25 | }
26 |
27 | module.exports = { connectToDb, getNextSequence, getDb };
28 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-08.06-debugging/api/graphql_date.js:
--------------------------------------------------------------------------------
1 | const { GraphQLScalarType } = require('graphql');
2 | const { Kind } = require('graphql/language');
3 |
4 | const GraphQLDate = new GraphQLScalarType({
5 | name: 'GraphQLDate',
6 | description: 'A Date() type in GraphQL as a scalar',
7 | serialize(value) {
8 | return value.toISOString();
9 | },
10 | parseValue(value) {
11 | const dateValue = new Date(value);
12 | return Number.isNaN(dateValue.getTime()) ? undefined : dateValue;
13 | },
14 | parseLiteral(ast) {
15 | if (ast.kind === Kind.STRING) {
16 | const value = new Date(ast.value);
17 | return Number.isNaN(value.getTime()) ? undefined : value;
18 | }
19 | return undefined;
20 | },
21 | });
22 |
23 | module.exports = GraphQLDate;
24 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-08.06-debugging/api/issue.js:
--------------------------------------------------------------------------------
1 | const { UserInputError } = require('apollo-server-express');
2 | const { getDb, getNextSequence } = require('./db.js');
3 |
4 | async function list() {
5 | const db = getDb();
6 | const issues = await db.collection('issues').find({}).toArray();
7 | return issues;
8 | }
9 |
10 | function validate(issue) {
11 | const errors = [];
12 | if (issue.title.length < 3) {
13 | errors.push('Field "title" must be at least 3 characters long.');
14 | }
15 | if (issue.status === 'Assigned' && !issue.owner) {
16 | errors.push('Field "owner" is required when status is "Assigned"');
17 | }
18 | if (errors.length > 0) {
19 | throw new UserInputError('Invalid input(s)', { errors });
20 | }
21 | }
22 |
23 | async function add(_, { issue }) {
24 | const db = getDb();
25 | validate(issue);
26 |
27 | const newIssue = Object.assign({}, issue);
28 | newIssue.created = new Date();
29 | newIssue.id = await getNextSequence('issues');
30 |
31 | const result = await db.collection('issues').insertOne(newIssue);
32 | const savedIssue = await db.collection('issues')
33 | .findOne({ _id: result.insertedId });
34 | return savedIssue;
35 | }
36 |
37 | module.exports = { list, add };
38 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-08.06-debugging/api/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pro-mern-stack-2-api",
3 | "version": "1.0.0",
4 | "description": "Pro MERN Stack (2nd Edition) API",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "nodemon -e js,graphql -w . -w .env server.js",
8 | "lint": "eslint .",
9 | "test": "echo \"Error: no test specified\" && exit 1"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git+https://github.com/vasansr/pro-mern-stack-2.git"
14 | },
15 | "author": "vasan.promern@gmail.com",
16 | "license": "ISC",
17 | "homepage": "https://github.com/vasansr/pro-mern-stack-2",
18 | "dependencies": {
19 | "apollo-server-express": "^2.3.1",
20 | "dotenv": "^6.2.0",
21 | "express": "^4.16.4",
22 | "graphql": "^0.13.2",
23 | "mongodb": "^3.1.10",
24 | "nodemon": "^1.18.9"
25 | },
26 | "devDependencies": {
27 | "eslint": "^5.12.0",
28 | "eslint-config-airbnb-base": "^13.1.0",
29 | "eslint-plugin-import": "^2.14.0"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-08.06-debugging/api/sample.env:
--------------------------------------------------------------------------------
1 | ## DB
2 | # Local
3 | DB_URL=mongodb://localhost/issuetracker
4 |
5 | # Atlas - replace UUU: user, PPP: password, XXX: hostname
6 | # DB_URL=mongodb+srv://UUU:PPP@XXX.mongodb.net/issuetracker?retryWrites=true
7 |
8 | # mLab - replace UUU: user, PPP: password, XXX: hostname, YYY: port
9 | # DB_URL=mongodb://UUU:PPP@XXX.mlab.com:YYY/issuetracker
10 |
11 |
12 | ## Server Port
13 | API_SERVER_PORT=3000
14 |
15 | ## Enable CORS (default: true)
16 | # ENABLE_CORS=false
17 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-08.06-debugging/api/schema.graphql:
--------------------------------------------------------------------------------
1 | scalar GraphQLDate
2 |
3 | enum StatusType {
4 | New
5 | Assigned
6 | Fixed
7 | Closed
8 | }
9 |
10 | type Issue {
11 | _id: ID!
12 | id: Int!
13 | title: String!
14 | status: StatusType!
15 | owner: String
16 | effort: Int
17 | created: GraphQLDate!
18 | due: GraphQLDate
19 | }
20 |
21 | "Toned down Issue, used as inputs, without server generated values."
22 | input IssueInputs {
23 | title: String!
24 | "Optional, if not supplied, will be set to 'New'"
25 | status: StatusType = New
26 | owner: String
27 | effort: Int
28 | due: GraphQLDate
29 | }
30 |
31 | ##### Top level declarations
32 |
33 | type Query {
34 | about: String!
35 | issueList: [Issue!]!
36 | }
37 |
38 | type Mutation {
39 | setAboutMessage(message: String!): String
40 | issueAdd(issue: IssueInputs!): Issue!
41 | }
42 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-08.06-debugging/api/scripts/init.mongo.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Run using the mongo shell. For remote databases, ensure that the
3 | * connection string is supplied in the command line. For example:
4 | * localhost:
5 | * mongo issuetracker scripts/init.mongo.js
6 | * Atlas:
7 | * mongo mongodb+srv://user:pwd@xxx.mongodb.net/issuetracker scripts/init.mongo.js
8 | * MLab:
9 | * mongo mongodb://user:pwd@xxx.mlab.com:33533/issuetracker scripts/init.mongo.js
10 | */
11 |
12 | /* global db print */
13 | /* eslint no-restricted-globals: "off" */
14 |
15 | db.issues.remove({});
16 |
17 | const issuesDB = [
18 | {
19 | id: 1,
20 | status: 'New',
21 | owner: 'Ravan',
22 | effort: 5,
23 | created: new Date('2019-01-15'),
24 | due: undefined,
25 | title: 'Error in console when clicking Add',
26 | },
27 | {
28 | id: 2,
29 | status: 'Assigned',
30 | owner: 'Eddie',
31 | effort: 14,
32 | created: new Date('2019-01-16'),
33 | due: new Date('2019-02-01'),
34 | title: 'Missing bottom border on panel',
35 | },
36 | ];
37 |
38 | db.issues.insertMany(issuesDB);
39 | const count = db.issues.count();
40 | print('Inserted', count, 'issues');
41 |
42 | db.counters.remove({ _id: 'issues' });
43 | db.counters.insert({ _id: 'issues', current: count });
44 |
45 | db.issues.createIndex({ id: 1 }, { unique: true });
46 | db.issues.createIndex({ status: 1 });
47 | db.issues.createIndex({ owner: 1 });
48 | db.issues.createIndex({ created: 1 });
49 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-08.06-debugging/api/server.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config();
2 | const express = require('express');
3 | const { connectToDb } = require('./db.js');
4 | const { installHandler } = require('./api_handler.js');
5 |
6 | const app = express();
7 |
8 | installHandler(app);
9 |
10 | const port = process.env.API_SERVER_PORT || 3000;
11 |
12 | (async function start() {
13 | try {
14 | await connectToDb();
15 | app.listen(port, () => {
16 | console.log(`API server started on port ${port}`);
17 | });
18 | } catch (err) {
19 | console.log('ERROR:', err);
20 | }
21 | }());
22 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-08.06-debugging/ui/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb-base",
3 | "env": {
4 | "node": true
5 | },
6 | "rules": {
7 | "no-console": "off"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-08.06-debugging/ui/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Pro MERN Stack
7 |
8 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-08.06-debugging/ui/sample.env:
--------------------------------------------------------------------------------
1 | UI_SERVER_PORT=8000
2 | UI_API_ENDPOINT=http://localhost:3000/graphql
3 | # API_PROXY_TARGET=http://localhost:3000
4 | # ENABLE_HMR=true
5 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-08.06-debugging/ui/src/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["@babel/preset-env", {
4 | "targets": {
5 | "ie": "11",
6 | "edge": "15",
7 | "safari": "10",
8 | "firefox": "50",
9 | "chrome": "49"
10 | }
11 | }],
12 | "@babel/preset-react"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-08.06-debugging/ui/src/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb",
3 | "env": {
4 | "browser": true
5 | },
6 | "rules": {
7 | "import/extensions": [ "error", "always", { "ignorePackages": true } ],
8 | "react/prop-types": "off"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-08.06-debugging/ui/src/App.jsx:
--------------------------------------------------------------------------------
1 | import 'babel-polyfill';
2 | import 'whatwg-fetch';
3 | import React from 'react';
4 | import ReactDOM from 'react-dom';
5 |
6 | import IssueList from './IssueList.jsx';
7 |
8 | const element = ;
9 |
10 | ReactDOM.render(element, document.getElementById('contents'));
11 |
12 | if (module.hot) {
13 | module.hot.accept();
14 | }
15 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-08.06-debugging/ui/src/IssueAdd.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | export default class IssueAdd extends React.Component {
5 | constructor() {
6 | super();
7 | this.handleSubmit = this.handleSubmit.bind(this);
8 | }
9 |
10 | handleSubmit(e) {
11 | e.preventDefault();
12 | const form = document.forms.issueAdd;
13 | const issue = {
14 | owner: form.owner.value,
15 | title: form.title.value,
16 | due: new Date(new Date().getTime() + 1000 * 60 * 60 * 24 * 10),
17 | };
18 | const { createIssue } = this.props;
19 | createIssue(issue);
20 | form.owner.value = ''; form.title.value = '';
21 | }
22 |
23 | render() {
24 | return (
25 |
30 | );
31 | }
32 | }
33 |
34 | IssueAdd.propTypes = {
35 | createIssue: PropTypes.func.isRequired,
36 | };
37 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-08.06-debugging/ui/src/IssueFilter.jsx:
--------------------------------------------------------------------------------
1 | /* eslint "react/prefer-stateless-function": "off" */
2 |
3 | import React from 'react';
4 |
5 | export default class IssueFilter extends React.Component {
6 | render() {
7 | return (
8 | This is a placeholder for the issue filter.
9 | );
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-08.06-debugging/ui/src/IssueList.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import IssueFilter from './IssueFilter.jsx';
4 | import IssueTable from './IssueTable.jsx';
5 | import IssueAdd from './IssueAdd.jsx';
6 | import graphQLFetch from './graphQLFetch.js';
7 |
8 | export default class IssueList extends React.Component {
9 | constructor() {
10 | super();
11 | this.state = { issues: [] };
12 | this.createIssue = this.createIssue.bind(this);
13 | }
14 |
15 | componentDidMount() {
16 | this.loadData();
17 | }
18 |
19 | async loadData() {
20 | const query = `query {
21 | issueList {
22 | id title status owner
23 | created effort due
24 | }
25 | }`;
26 |
27 | const data = await graphQLFetch(query);
28 | if (data) {
29 | this.setState({ issues: data.issueList });
30 | }
31 | }
32 |
33 | async createIssue(issue) {
34 | const query = `mutation issueAdd($issue: IssueInputs!) {
35 | issueAdd(issue: $issue) {
36 | id
37 | }
38 | }`;
39 |
40 | const data = await graphQLFetch(query, { issue });
41 | if (data) {
42 | this.loadData();
43 | }
44 | }
45 |
46 | render() {
47 | const { issues } = this.state;
48 | return (
49 |
50 | Issue Tracker
51 |
52 |
53 |
54 |
55 |
56 |
57 | );
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-08.06-debugging/ui/src/IssueTable.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function IssueRow({ issue }) {
4 | return (
5 |
6 | {issue.id} |
7 | {issue.status} |
8 | {issue.owner} |
9 | {issue.created.toDateString()} |
10 | {issue.effort} |
11 | {issue.due ? issue.due.toDateString() : ''} |
12 | {issue.title} |
13 |
14 | );
15 | }
16 |
17 | export default function IssueTable({ issues }) {
18 | const issueRows = issues.map(issue => (
19 |
20 | ));
21 |
22 | return (
23 |
24 |
25 |
26 | ID |
27 | Status |
28 | Owner |
29 | Created |
30 | Effort |
31 | Due Date |
32 | Title |
33 |
34 |
35 |
36 | {issueRows}
37 |
38 |
39 | );
40 | }
41 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-08.06-debugging/ui/src/graphQLFetch.js:
--------------------------------------------------------------------------------
1 | /* eslint "no-alert": "off" */
2 |
3 | const dateRegex = new RegExp('^\\d\\d\\d\\d-\\d\\d-\\d\\d');
4 |
5 | function jsonDateReviver(key, value) {
6 | if (dateRegex.test(value)) return new Date(value);
7 | return value;
8 | }
9 |
10 | export default async function graphQLFetch(query, variables = {}) {
11 | try {
12 | const response = await fetch(window.ENV.UI_API_ENDPOINT, {
13 | method: 'POST',
14 | headers: { 'Content-Type': 'application/json' },
15 | body: JSON.stringify({ query, variables }),
16 | });
17 | const body = await response.text();
18 | const result = JSON.parse(body, jsonDateReviver);
19 |
20 | if (result.errors) {
21 | const error = result.errors[0];
22 | if (error.extensions.code === 'BAD_USER_INPUT') {
23 | const details = error.extensions.exception.errors.join('\n ');
24 | alert(`${error.message}:\n ${details}`);
25 | } else {
26 | alert(`${error.extensions.code}: ${error.message}`);
27 | }
28 | }
29 | return result.data;
30 | } catch (e) {
31 | alert(`Error in sending data to server: ${e.message}`);
32 | return null;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-08.06-debugging/ui/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | mode: 'development',
5 | entry: { app: ['./src/App.jsx'] },
6 | output: {
7 | filename: '[name].bundle.js',
8 | path: path.resolve(__dirname, 'public'),
9 | },
10 | module: {
11 | rules: [
12 | {
13 | test: /\.jsx?$/,
14 | exclude: /node_modules/,
15 | use: 'babel-loader',
16 | },
17 | ],
18 | },
19 | optimization: {
20 | splitChunks: {
21 | name: 'vendor',
22 | chunks: 'all',
23 | },
24 | },
25 | devtool: 'source-map',
26 | };
27 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-09.07-browser-history-router/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | package-lock.json
3 | ui/public/*.js
4 | ui/public/*.map
5 | .env
6 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-09.07-browser-history-router/README.md:
--------------------------------------------------------------------------------
1 | # Pro MERN Stack - 2nd Edition
2 |
3 | You are browsing the source code at the end of one of the sections in the book.
4 |
5 | The project's README which contains the list of all chapters, sections
6 | their sources and other useful information can be found in the
7 | [master branch](https://github.com/vasansr/pro-mern-stack-2).
8 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-09.07-browser-history-router/api/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb-base",
3 | "env": {
4 | "node": "true"
5 | },
6 | rules: {
7 | "no-console": "off"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-09.07-browser-history-router/api/about.js:
--------------------------------------------------------------------------------
1 | let aboutMessage = 'Issue Tracker API v1.0';
2 |
3 | function setMessage(_, { message }) {
4 | aboutMessage = message;
5 | return aboutMessage;
6 | }
7 |
8 | function getMessage() {
9 | return aboutMessage;
10 | }
11 |
12 | module.exports = { getMessage, setMessage };
13 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-09.07-browser-history-router/api/api_handler.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | require('dotenv').config();
3 | const { ApolloServer } = require('apollo-server-express');
4 |
5 | const GraphQLDate = require('./graphql_date.js');
6 | const about = require('./about.js');
7 | const issue = require('./issue.js');
8 |
9 | const resolvers = {
10 | Query: {
11 | about: about.getMessage,
12 | issueList: issue.list,
13 | issue: issue.get,
14 | },
15 | Mutation: {
16 | setAboutMessage: about.setMessage,
17 | issueAdd: issue.add,
18 | },
19 | GraphQLDate,
20 | };
21 |
22 | const server = new ApolloServer({
23 | typeDefs: fs.readFileSync('schema.graphql', 'utf-8'),
24 | resolvers,
25 | formatError: (error) => {
26 | console.log(error);
27 | return error;
28 | },
29 | });
30 |
31 | function installHandler(app) {
32 | const enableCors = (process.env.ENABLE_CORS || 'true') === 'true';
33 | console.log('CORS setting:', enableCors);
34 | server.applyMiddleware({ app, path: '/graphql', cors: enableCors });
35 | }
36 |
37 | module.exports = { installHandler };
38 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-09.07-browser-history-router/api/db.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config();
2 | const { MongoClient } = require('mongodb');
3 |
4 | let db;
5 |
6 | async function connectToDb() {
7 | const url = process.env.DB_URL || 'mongodb://localhost/issuetracker';
8 | const client = new MongoClient(url, { useNewUrlParser: true });
9 | await client.connect();
10 | console.log('Connected to MongoDB at', url);
11 | db = client.db();
12 | }
13 |
14 | async function getNextSequence(name) {
15 | const result = await db.collection('counters').findOneAndUpdate(
16 | { _id: name },
17 | { $inc: { current: 1 } },
18 | { returnOriginal: false },
19 | );
20 | return result.value.current;
21 | }
22 |
23 | function getDb() {
24 | return db;
25 | }
26 |
27 | module.exports = { connectToDb, getNextSequence, getDb };
28 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-09.07-browser-history-router/api/graphql_date.js:
--------------------------------------------------------------------------------
1 | const { GraphQLScalarType } = require('graphql');
2 | const { Kind } = require('graphql/language');
3 |
4 | const GraphQLDate = new GraphQLScalarType({
5 | name: 'GraphQLDate',
6 | description: 'A Date() type in GraphQL as a scalar',
7 | serialize(value) {
8 | return value.toISOString();
9 | },
10 | parseValue(value) {
11 | const dateValue = new Date(value);
12 | return Number.isNaN(dateValue.getTime()) ? undefined : dateValue;
13 | },
14 | parseLiteral(ast) {
15 | if (ast.kind === Kind.STRING) {
16 | const value = new Date(ast.value);
17 | return Number.isNaN(value.getTime()) ? undefined : value;
18 | }
19 | return undefined;
20 | },
21 | });
22 |
23 | module.exports = GraphQLDate;
24 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-09.07-browser-history-router/api/issue.js:
--------------------------------------------------------------------------------
1 | const { UserInputError } = require('apollo-server-express');
2 | const { getDb, getNextSequence } = require('./db.js');
3 |
4 | async function get(_, { id }) {
5 | const db = getDb();
6 | const issue = await db.collection('issues').findOne({ id });
7 | return issue;
8 | }
9 |
10 | async function list(_, { status }) {
11 | const db = getDb();
12 | const filter = {};
13 | if (status) filter.status = status;
14 | const issues = await db.collection('issues').find(filter).toArray();
15 | return issues;
16 | }
17 |
18 | function validate(issue) {
19 | const errors = [];
20 | if (issue.title.length < 3) {
21 | errors.push('Field "title" must be at least 3 characters long.');
22 | }
23 | if (issue.status === 'Assigned' && !issue.owner) {
24 | errors.push('Field "owner" is required when status is "Assigned"');
25 | }
26 | if (errors.length > 0) {
27 | throw new UserInputError('Invalid input(s)', { errors });
28 | }
29 | }
30 |
31 | async function add(_, { issue }) {
32 | const db = getDb();
33 | validate(issue);
34 |
35 | const newIssue = Object.assign({}, issue);
36 | newIssue.created = new Date();
37 | newIssue.id = await getNextSequence('issues');
38 |
39 | const result = await db.collection('issues').insertOne(newIssue);
40 | const savedIssue = await db.collection('issues')
41 | .findOne({ _id: result.insertedId });
42 | return savedIssue;
43 | }
44 |
45 | module.exports = { list, add, get };
46 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-09.07-browser-history-router/api/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pro-mern-stack-2-api",
3 | "version": "1.0.0",
4 | "description": "Pro MERN Stack (2nd Edition) API",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "nodemon -e js,graphql -w . -w .env server.js",
8 | "lint": "eslint .",
9 | "test": "echo \"Error: no test specified\" && exit 1"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git+https://github.com/vasansr/pro-mern-stack-2.git"
14 | },
15 | "author": "vasan.promern@gmail.com",
16 | "license": "ISC",
17 | "homepage": "https://github.com/vasansr/pro-mern-stack-2",
18 | "dependencies": {
19 | "apollo-server-express": "^2.3.1",
20 | "dotenv": "^6.2.0",
21 | "express": "^4.16.4",
22 | "graphql": "^0.13.2",
23 | "mongodb": "^3.1.10",
24 | "nodemon": "^1.18.9"
25 | },
26 | "devDependencies": {
27 | "eslint": "^5.12.0",
28 | "eslint-config-airbnb-base": "^13.1.0",
29 | "eslint-plugin-import": "^2.14.0"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-09.07-browser-history-router/api/sample.env:
--------------------------------------------------------------------------------
1 | ## DB
2 | # Local
3 | DB_URL=mongodb://localhost/issuetracker
4 |
5 | # Atlas - replace UUU: user, PPP: password, XXX: hostname
6 | # DB_URL=mongodb+srv://UUU:PPP@XXX.mongodb.net/issuetracker?retryWrites=true
7 |
8 | # mLab - replace UUU: user, PPP: password, XXX: hostname, YYY: port
9 | # DB_URL=mongodb://UUU:PPP@XXX.mlab.com:YYY/issuetracker
10 |
11 |
12 | ## Server Port
13 | API_SERVER_PORT=3000
14 |
15 | ## Enable CORS (default: true)
16 | # ENABLE_CORS=false
17 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-09.07-browser-history-router/api/schema.graphql:
--------------------------------------------------------------------------------
1 | scalar GraphQLDate
2 |
3 | enum StatusType {
4 | New
5 | Assigned
6 | Fixed
7 | Closed
8 | }
9 |
10 | type Issue {
11 | _id: ID!
12 | id: Int!
13 | title: String!
14 | status: StatusType!
15 | owner: String
16 | effort: Int
17 | created: GraphQLDate!
18 | due: GraphQLDate
19 | description: String
20 | }
21 |
22 | "Toned down Issue, used as inputs, without server generated values."
23 | input IssueInputs {
24 | title: String!
25 | "Optional, if not supplied, will be set to 'New'"
26 | status: StatusType = New
27 | owner: String
28 | effort: Int
29 | due: GraphQLDate
30 | description: String
31 | }
32 |
33 | ##### Top level declarations
34 |
35 | type Query {
36 | about: String!
37 | issueList(status: StatusType): [Issue!]!
38 | issue(id: Int!): Issue!
39 | }
40 |
41 | type Mutation {
42 | setAboutMessage(message: String!): String
43 | issueAdd(issue: IssueInputs!): Issue!
44 | }
45 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-09.07-browser-history-router/api/server.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config();
2 | const express = require('express');
3 | const { connectToDb } = require('./db.js');
4 | const { installHandler } = require('./api_handler.js');
5 |
6 | const app = express();
7 |
8 | installHandler(app);
9 |
10 | const port = process.env.API_SERVER_PORT || 3000;
11 |
12 | (async function start() {
13 | try {
14 | await connectToDb();
15 | app.listen(port, () => {
16 | console.log(`API server started on port ${port}`);
17 | });
18 | } catch (err) {
19 | console.log('ERROR:', err);
20 | }
21 | }());
22 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-09.07-browser-history-router/ui/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb-base",
3 | "env": {
4 | "node": true
5 | },
6 | "rules": {
7 | "no-console": "off"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-09.07-browser-history-router/ui/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Pro MERN Stack
7 |
8 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-09.07-browser-history-router/ui/sample.env:
--------------------------------------------------------------------------------
1 | UI_SERVER_PORT=8000
2 | UI_API_ENDPOINT=http://localhost:3000/graphql
3 | # API_PROXY_TARGET=http://localhost:3000
4 | # ENABLE_HMR=true
5 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-09.07-browser-history-router/ui/src/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["@babel/preset-env", {
4 | "targets": {
5 | "ie": "11",
6 | "edge": "15",
7 | "safari": "10",
8 | "firefox": "50",
9 | "chrome": "49"
10 | }
11 | }],
12 | "@babel/preset-react"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-09.07-browser-history-router/ui/src/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb",
3 | "env": {
4 | "browser": true
5 | },
6 | "rules": {
7 | "import/extensions": [ "error", "always", { "ignorePackages": true } ],
8 | "react/prop-types": "off"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-09.07-browser-history-router/ui/src/App.jsx:
--------------------------------------------------------------------------------
1 | import 'babel-polyfill';
2 | import 'whatwg-fetch';
3 | import React from 'react';
4 | import ReactDOM from 'react-dom';
5 | import { BrowserRouter as Router } from 'react-router-dom';
6 |
7 | import Page from './Page.jsx';
8 |
9 | const element = (
10 |
11 |
12 |
13 | );
14 |
15 | ReactDOM.render(element, document.getElementById('contents'));
16 |
17 | if (module.hot) {
18 | module.hot.accept();
19 | }
20 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-09.07-browser-history-router/ui/src/Contents.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Switch, Route, Redirect } from 'react-router-dom';
3 |
4 | import IssueList from './IssueList.jsx';
5 | import IssueReport from './IssueReport.jsx';
6 | import IssueEdit from './IssueEdit.jsx';
7 |
8 | const NotFound = () => Page Not Found
;
9 |
10 | export default function Contents() {
11 | return (
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-09.07-browser-history-router/ui/src/IssueAdd.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | export default class IssueAdd extends React.Component {
5 | constructor() {
6 | super();
7 | this.handleSubmit = this.handleSubmit.bind(this);
8 | }
9 |
10 | handleSubmit(e) {
11 | e.preventDefault();
12 | const form = document.forms.issueAdd;
13 | const issue = {
14 | owner: form.owner.value,
15 | title: form.title.value,
16 | due: new Date(new Date().getTime() + 1000 * 60 * 60 * 24 * 10),
17 | };
18 | const { createIssue } = this.props;
19 | createIssue(issue);
20 | form.owner.value = ''; form.title.value = '';
21 | }
22 |
23 | render() {
24 | return (
25 |
30 | );
31 | }
32 | }
33 |
34 | IssueAdd.propTypes = {
35 | createIssue: PropTypes.func.isRequired,
36 | };
37 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-09.07-browser-history-router/ui/src/IssueDetail.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import graphQLFetch from './graphQLFetch.js';
4 |
5 | export default class IssueDetail extends React.Component {
6 | constructor() {
7 | super();
8 | this.state = { issue: {} };
9 | }
10 |
11 | componentDidMount() {
12 | this.loadData();
13 | }
14 |
15 | componentDidUpdate(prevProps) {
16 | const { match: { params: { id: prevId } } } = prevProps;
17 | const { match: { params: { id } } } = this.props;
18 | if (prevId !== id) {
19 | this.loadData();
20 | }
21 | }
22 |
23 | async loadData() {
24 | const { match: { params: { id } } } = this.props;
25 | const query = `query issue($id: Int!) {
26 | issue (id: $id) {
27 | id description
28 | }
29 | }`;
30 |
31 | const data = await graphQLFetch(query, { id });
32 | if (data) {
33 | this.setState({ issue: data.issue });
34 | } else {
35 | this.setState({ issue: {} });
36 | }
37 | }
38 |
39 | render() {
40 | const { issue: { description } } = this.state;
41 | return (
42 |
43 |
Description
44 |
{description}
45 |
46 | );
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-09.07-browser-history-router/ui/src/IssueEdit.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default function IssueEdit({ match }) {
4 | const { id } = match.params;
5 | return (
6 | {`This is a placeholder for editing issue ${id}`}
7 | );
8 | }
9 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-09.07-browser-history-router/ui/src/IssueFilter.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { withRouter } from 'react-router-dom';
3 |
4 | class IssueFilter extends React.Component {
5 | constructor() {
6 | super();
7 | this.onChangeStatus = this.onChangeStatus.bind(this);
8 | }
9 |
10 | onChangeStatus(e) {
11 | const status = e.target.value;
12 | const { history } = this.props;
13 | history.push({
14 | pathname: '/issues',
15 | search: status ? `?status=${status}` : '',
16 | });
17 | }
18 |
19 | render() {
20 | return (
21 |
22 | Status:
23 | {' '}
24 |
31 |
32 | );
33 | }
34 | }
35 |
36 | export default withRouter(IssueFilter);
37 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-09.07-browser-history-router/ui/src/IssueReport.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default function IssueReport() {
4 | return (
5 |
6 |
This is a placeholder for the Issue Report
7 |
8 | );
9 | }
10 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-09.07-browser-history-router/ui/src/IssueTable.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link, NavLink, withRouter } from 'react-router-dom';
3 |
4 | const IssueRow = withRouter(({ issue, location: { search } }) => {
5 | const selectLocation = { pathname: `/issues/${issue.id}`, search };
6 | return (
7 |
8 | {issue.id} |
9 | {issue.status} |
10 | {issue.owner} |
11 | {issue.created.toDateString()} |
12 | {issue.effort} |
13 | {issue.due ? issue.due.toDateString() : ''} |
14 | {issue.title} |
15 |
16 | Edit
17 | {' | '}
18 | Select
19 | |
20 |
21 | );
22 | });
23 |
24 | export default function IssueTable({ issues }) {
25 | const issueRows = issues.map(issue => (
26 |
27 | ));
28 |
29 | return (
30 |
31 |
32 |
33 | ID |
34 | Status |
35 | Owner |
36 | Created |
37 | Effort |
38 | Due Date |
39 | Title |
40 | Action |
41 |
42 |
43 |
44 | {issueRows}
45 |
46 |
47 | );
48 | }
49 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-09.07-browser-history-router/ui/src/Page.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { NavLink } from 'react-router-dom';
3 |
4 | import Contents from './Contents.jsx';
5 |
6 | function NavBar() {
7 | return (
8 |
15 | );
16 | }
17 |
18 | export default function Page() {
19 | return (
20 |
21 |
22 |
23 |
24 | );
25 | }
26 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-09.07-browser-history-router/ui/src/graphQLFetch.js:
--------------------------------------------------------------------------------
1 | /* eslint "no-alert": "off" */
2 |
3 | const dateRegex = new RegExp('^\\d\\d\\d\\d-\\d\\d-\\d\\d');
4 |
5 | function jsonDateReviver(key, value) {
6 | if (dateRegex.test(value)) return new Date(value);
7 | return value;
8 | }
9 |
10 | export default async function graphQLFetch(query, variables = {}) {
11 | try {
12 | const response = await fetch(window.ENV.UI_API_ENDPOINT, {
13 | method: 'POST',
14 | headers: { 'Content-Type': 'application/json' },
15 | body: JSON.stringify({ query, variables }),
16 | });
17 | const body = await response.text();
18 | const result = JSON.parse(body, jsonDateReviver);
19 |
20 | if (result.errors) {
21 | const error = result.errors[0];
22 | if (error.extensions.code === 'BAD_USER_INPUT') {
23 | const details = error.extensions.exception.errors.join('\n ');
24 | alert(`${error.message}:\n ${details}`);
25 | } else {
26 | alert(`${error.extensions.code}: ${error.message}`);
27 | }
28 | }
29 | return result.data;
30 | } catch (e) {
31 | alert(`Error in sending data to server: ${e.message}`);
32 | return null;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-09.07-browser-history-router/ui/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | mode: 'development',
5 | entry: { app: ['./src/App.jsx'] },
6 | output: {
7 | filename: '[name].bundle.js',
8 | path: path.resolve(__dirname, 'public'),
9 | publicPath: '/',
10 | },
11 | module: {
12 | rules: [
13 | {
14 | test: /\.jsx?$/,
15 | exclude: /node_modules/,
16 | use: 'babel-loader',
17 | },
18 | ],
19 | },
20 | optimization: {
21 | splitChunks: {
22 | name: 'vendor',
23 | chunks: 'all',
24 | },
25 | },
26 | devtool: 'source-map',
27 | };
28 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-10.13-deleting-an-issue/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | package-lock.json
3 | ui/public/*.js
4 | ui/public/*.map
5 | .env
6 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-10.13-deleting-an-issue/README.md:
--------------------------------------------------------------------------------
1 | # Pro MERN Stack - 2nd Edition
2 |
3 | You are browsing the source code at the end of one of the sections in the book.
4 |
5 | The project's README which contains the list of all chapters, sections
6 | their sources and other useful information can be found in the
7 | [master branch](https://github.com/vasansr/pro-mern-stack-2).
8 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-10.13-deleting-an-issue/api/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb-base",
3 | "env": {
4 | "node": "true"
5 | },
6 | rules: {
7 | "no-console": "off"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-10.13-deleting-an-issue/api/about.js:
--------------------------------------------------------------------------------
1 | let aboutMessage = 'Issue Tracker API v1.0';
2 |
3 | function setMessage(_, { message }) {
4 | aboutMessage = message;
5 | return aboutMessage;
6 | }
7 |
8 | function getMessage() {
9 | return aboutMessage;
10 | }
11 |
12 | module.exports = { getMessage, setMessage };
13 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-10.13-deleting-an-issue/api/api_handler.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | require('dotenv').config();
3 | const { ApolloServer } = require('apollo-server-express');
4 |
5 | const GraphQLDate = require('./graphql_date.js');
6 | const about = require('./about.js');
7 | const issue = require('./issue.js');
8 |
9 | const resolvers = {
10 | Query: {
11 | about: about.getMessage,
12 | issueList: issue.list,
13 | issue: issue.get,
14 | },
15 | Mutation: {
16 | setAboutMessage: about.setMessage,
17 | issueAdd: issue.add,
18 | issueUpdate: issue.update,
19 | issueDelete: issue.delete,
20 | },
21 | GraphQLDate,
22 | };
23 |
24 | const server = new ApolloServer({
25 | typeDefs: fs.readFileSync('schema.graphql', 'utf-8'),
26 | resolvers,
27 | formatError: (error) => {
28 | console.log(error);
29 | return error;
30 | },
31 | });
32 |
33 | function installHandler(app) {
34 | const enableCors = (process.env.ENABLE_CORS || 'true') === 'true';
35 | console.log('CORS setting:', enableCors);
36 | server.applyMiddleware({ app, path: '/graphql', cors: enableCors });
37 | }
38 |
39 | module.exports = { installHandler };
40 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-10.13-deleting-an-issue/api/db.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config();
2 | const { MongoClient } = require('mongodb');
3 |
4 | let db;
5 |
6 | async function connectToDb() {
7 | const url = process.env.DB_URL || 'mongodb://localhost/issuetracker';
8 | const client = new MongoClient(url, { useNewUrlParser: true });
9 | await client.connect();
10 | console.log('Connected to MongoDB at', url);
11 | db = client.db();
12 | }
13 |
14 | async function getNextSequence(name) {
15 | const result = await db.collection('counters').findOneAndUpdate(
16 | { _id: name },
17 | { $inc: { current: 1 } },
18 | { returnOriginal: false },
19 | );
20 | return result.value.current;
21 | }
22 |
23 | function getDb() {
24 | return db;
25 | }
26 |
27 | module.exports = { connectToDb, getNextSequence, getDb };
28 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-10.13-deleting-an-issue/api/graphql_date.js:
--------------------------------------------------------------------------------
1 | const { GraphQLScalarType } = require('graphql');
2 | const { Kind } = require('graphql/language');
3 |
4 | const GraphQLDate = new GraphQLScalarType({
5 | name: 'GraphQLDate',
6 | description: 'A Date() type in GraphQL as a scalar',
7 | serialize(value) {
8 | return value.toISOString();
9 | },
10 | parseValue(value) {
11 | const dateValue = new Date(value);
12 | return Number.isNaN(dateValue.getTime()) ? undefined : dateValue;
13 | },
14 | parseLiteral(ast) {
15 | if (ast.kind === Kind.STRING) {
16 | const value = new Date(ast.value);
17 | return Number.isNaN(value.getTime()) ? undefined : value;
18 | }
19 | return undefined;
20 | },
21 | });
22 |
23 | module.exports = GraphQLDate;
24 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-10.13-deleting-an-issue/api/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pro-mern-stack-2-api",
3 | "version": "1.0.0",
4 | "description": "Pro MERN Stack (2nd Edition) API",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "nodemon -e js,graphql -w . -w .env server.js",
8 | "lint": "eslint .",
9 | "test": "echo \"Error: no test specified\" && exit 1"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git+https://github.com/vasansr/pro-mern-stack-2.git"
14 | },
15 | "author": "vasan.promern@gmail.com",
16 | "license": "ISC",
17 | "homepage": "https://github.com/vasansr/pro-mern-stack-2",
18 | "dependencies": {
19 | "apollo-server-express": "^2.3.1",
20 | "dotenv": "^6.2.0",
21 | "express": "^4.16.4",
22 | "graphql": "^0.13.2",
23 | "mongodb": "^3.1.10",
24 | "nodemon": "^1.18.9"
25 | },
26 | "devDependencies": {
27 | "eslint": "^5.12.0",
28 | "eslint-config-airbnb-base": "^13.1.0",
29 | "eslint-plugin-import": "^2.14.0"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-10.13-deleting-an-issue/api/sample.env:
--------------------------------------------------------------------------------
1 | ## DB
2 | # Local
3 | DB_URL=mongodb://localhost/issuetracker
4 |
5 | # Atlas - replace UUU: user, PPP: password, XXX: hostname
6 | # DB_URL=mongodb+srv://UUU:PPP@XXX.mongodb.net/issuetracker?retryWrites=true
7 |
8 | # mLab - replace UUU: user, PPP: password, XXX: hostname, YYY: port
9 | # DB_URL=mongodb://UUU:PPP@XXX.mlab.com:YYY/issuetracker
10 |
11 |
12 | ## Server Port
13 | API_SERVER_PORT=3000
14 |
15 | ## Enable CORS (default: true)
16 | # ENABLE_CORS=false
17 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-10.13-deleting-an-issue/api/schema.graphql:
--------------------------------------------------------------------------------
1 | scalar GraphQLDate
2 |
3 | enum StatusType {
4 | New
5 | Assigned
6 | Fixed
7 | Closed
8 | }
9 |
10 | type Issue {
11 | _id: ID!
12 | id: Int!
13 | title: String!
14 | status: StatusType!
15 | owner: String
16 | effort: Int
17 | created: GraphQLDate!
18 | due: GraphQLDate
19 | description: String
20 | }
21 |
22 | "Toned down Issue, used as inputs, without server generated values."
23 | input IssueInputs {
24 | title: String!
25 | "Optional, if not supplied, will be set to 'New'"
26 | status: StatusType = New
27 | owner: String
28 | effort: Int
29 | due: GraphQLDate
30 | description: String
31 | }
32 |
33 | """Inputs for issueUpdate: all are optional. Whichever is specified will
34 | be set to the given value, undefined fields will remain unmodified."""
35 | input IssueUpdateInputs {
36 | title: String
37 | status: StatusType
38 | owner: String
39 | effort: Int
40 | due: GraphQLDate
41 | description: String
42 | }
43 |
44 | ##### Top level declarations
45 |
46 | type Query {
47 | about: String!
48 | issueList(
49 | status: StatusType
50 | effortMin: Int
51 | effortMax: Int
52 | ): [Issue!]!
53 | issue(id: Int!): Issue!
54 | }
55 |
56 | type Mutation {
57 | setAboutMessage(message: String!): String
58 | issueAdd(issue: IssueInputs!): Issue!
59 | issueUpdate(id: Int!, changes: IssueUpdateInputs!): Issue!
60 | issueDelete(id: Int!): Boolean!
61 | }
62 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-10.13-deleting-an-issue/api/server.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config();
2 | const express = require('express');
3 | const { connectToDb } = require('./db.js');
4 | const { installHandler } = require('./api_handler.js');
5 |
6 | const app = express();
7 |
8 | installHandler(app);
9 |
10 | const port = process.env.API_SERVER_PORT || 3000;
11 |
12 | (async function start() {
13 | try {
14 | await connectToDb();
15 | app.listen(port, () => {
16 | console.log(`API server started on port ${port}`);
17 | });
18 | } catch (err) {
19 | console.log('ERROR:', err);
20 | }
21 | }());
22 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-10.13-deleting-an-issue/ui/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb-base",
3 | "env": {
4 | "node": true
5 | },
6 | "rules": {
7 | "no-console": "off"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-10.13-deleting-an-issue/ui/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Pro MERN Stack
7 |
8 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-10.13-deleting-an-issue/ui/sample.env:
--------------------------------------------------------------------------------
1 | UI_SERVER_PORT=8000
2 | UI_API_ENDPOINT=http://localhost:3000/graphql
3 | # API_PROXY_TARGET=http://localhost:3000
4 | # ENABLE_HMR=true
5 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-10.13-deleting-an-issue/ui/src/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["@babel/preset-env", {
4 | "targets": {
5 | "ie": "11",
6 | "edge": "15",
7 | "safari": "10",
8 | "firefox": "50",
9 | "chrome": "49"
10 | }
11 | }],
12 | "@babel/preset-react"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-10.13-deleting-an-issue/ui/src/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb",
3 | "env": {
4 | "browser": true
5 | },
6 | "rules": {
7 | "import/extensions": [ "error", "always", { "ignorePackages": true } ],
8 | "react/prop-types": "off"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-10.13-deleting-an-issue/ui/src/App.jsx:
--------------------------------------------------------------------------------
1 | import 'babel-polyfill';
2 | import 'whatwg-fetch';
3 | import React from 'react';
4 | import ReactDOM from 'react-dom';
5 | import { BrowserRouter as Router } from 'react-router-dom';
6 |
7 | import Page from './Page.jsx';
8 |
9 | const element = (
10 |
11 |
12 |
13 | );
14 |
15 | ReactDOM.render(element, document.getElementById('contents'));
16 |
17 | if (module.hot) {
18 | module.hot.accept();
19 | }
20 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-10.13-deleting-an-issue/ui/src/Contents.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Switch, Route, Redirect } from 'react-router-dom';
3 |
4 | import IssueList from './IssueList.jsx';
5 | import IssueReport from './IssueReport.jsx';
6 | import IssueEdit from './IssueEdit.jsx';
7 |
8 | const NotFound = () => Page Not Found
;
9 |
10 | export default function Contents() {
11 | return (
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-10.13-deleting-an-issue/ui/src/IssueAdd.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | export default class IssueAdd extends React.Component {
5 | constructor() {
6 | super();
7 | this.handleSubmit = this.handleSubmit.bind(this);
8 | }
9 |
10 | handleSubmit(e) {
11 | e.preventDefault();
12 | const form = document.forms.issueAdd;
13 | const issue = {
14 | owner: form.owner.value,
15 | title: form.title.value,
16 | due: new Date(new Date().getTime() + 1000 * 60 * 60 * 24 * 10),
17 | };
18 | const { createIssue } = this.props;
19 | createIssue(issue);
20 | form.owner.value = ''; form.title.value = '';
21 | }
22 |
23 | render() {
24 | return (
25 |
30 | );
31 | }
32 | }
33 |
34 | IssueAdd.propTypes = {
35 | createIssue: PropTypes.func.isRequired,
36 | };
37 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-10.13-deleting-an-issue/ui/src/IssueDetail.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import graphQLFetch from './graphQLFetch.js';
4 |
5 | export default class IssueDetail extends React.Component {
6 | constructor() {
7 | super();
8 | this.state = { issue: {} };
9 | }
10 |
11 | componentDidMount() {
12 | this.loadData();
13 | }
14 |
15 | componentDidUpdate(prevProps) {
16 | const { match: { params: { id: prevId } } } = prevProps;
17 | const { match: { params: { id } } } = this.props;
18 | if (prevId !== id) {
19 | this.loadData();
20 | }
21 | }
22 |
23 | async loadData() {
24 | const { match: { params: { id } } } = this.props;
25 | const query = `query issue($id: Int!) {
26 | issue (id: $id) {
27 | id description
28 | }
29 | }`;
30 |
31 | const data = await graphQLFetch(query, { id });
32 | if (data) {
33 | this.setState({ issue: data.issue });
34 | } else {
35 | this.setState({ issue: {} });
36 | }
37 | }
38 |
39 | render() {
40 | const { issue: { description } } = this.state;
41 | return (
42 |
43 |
Description
44 |
{description}
45 |
46 | );
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-10.13-deleting-an-issue/ui/src/IssueReport.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default function IssueReport() {
4 | return (
5 |
6 |
This is a placeholder for the Issue Report
7 |
8 | );
9 | }
10 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-10.13-deleting-an-issue/ui/src/NumInput.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function format(num) {
4 | return num != null ? num.toString() : '';
5 | }
6 |
7 | function unformat(str) {
8 | const val = parseInt(str, 10);
9 | return Number.isNaN(val) ? null : val;
10 | }
11 |
12 | export default class NumInput extends React.Component {
13 | constructor(props) {
14 | super(props);
15 | this.state = { value: format(props.value) };
16 | this.onBlur = this.onBlur.bind(this);
17 | this.onChange = this.onChange.bind(this);
18 | }
19 |
20 | onChange(e) {
21 | if (e.target.value.match(/^\d*$/)) {
22 | this.setState({ value: e.target.value });
23 | }
24 | }
25 |
26 | onBlur(e) {
27 | const { onChange } = this.props;
28 | const { value } = this.state;
29 | onChange(e, unformat(value));
30 | }
31 |
32 | render() {
33 | const { value } = this.state;
34 | return (
35 |
42 | );
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-10.13-deleting-an-issue/ui/src/Page.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { NavLink } from 'react-router-dom';
3 |
4 | import Contents from './Contents.jsx';
5 |
6 | function NavBar() {
7 | return (
8 |
15 | );
16 | }
17 |
18 | export default function Page() {
19 | return (
20 |
21 |
22 |
23 |
24 | );
25 | }
26 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-10.13-deleting-an-issue/ui/src/TextInput.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function format(text) {
4 | return text != null ? text : '';
5 | }
6 |
7 | function unformat(text) {
8 | return text.trim().length === 0 ? null : text;
9 | }
10 |
11 | export default class TextInput extends React.Component {
12 | constructor(props) {
13 | super(props);
14 | this.state = { value: format(props.value) };
15 | this.onBlur = this.onBlur.bind(this);
16 | this.onChange = this.onChange.bind(this);
17 | }
18 |
19 | onChange(e) {
20 | this.setState({ value: e.target.value });
21 | }
22 |
23 | onBlur(e) {
24 | const { onChange } = this.props;
25 | const { value } = this.state;
26 | onChange(e, unformat(value));
27 | }
28 |
29 | render() {
30 | const { value } = this.state;
31 | const { tag = 'input', ...props } = this.props;
32 | return React.createElement(tag, {
33 | ...props,
34 | value,
35 | onBlur: this.onBlur,
36 | onChange: this.onChange,
37 | });
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-10.13-deleting-an-issue/ui/src/graphQLFetch.js:
--------------------------------------------------------------------------------
1 | /* eslint "no-alert": "off" */
2 |
3 | const dateRegex = new RegExp('^\\d\\d\\d\\d-\\d\\d-\\d\\d');
4 |
5 | function jsonDateReviver(key, value) {
6 | if (dateRegex.test(value)) return new Date(value);
7 | return value;
8 | }
9 |
10 | export default async function graphQLFetch(query, variables = {}) {
11 | try {
12 | const response = await fetch(window.ENV.UI_API_ENDPOINT, {
13 | method: 'POST',
14 | headers: { 'Content-Type': 'application/json' },
15 | body: JSON.stringify({ query, variables }),
16 | });
17 | const body = await response.text();
18 | const result = JSON.parse(body, jsonDateReviver);
19 |
20 | if (result.errors) {
21 | const error = result.errors[0];
22 | if (error.extensions.code === 'BAD_USER_INPUT') {
23 | const details = error.extensions.exception.errors.join('\n ');
24 | alert(`${error.message}:\n ${details}`);
25 | } else {
26 | alert(`${error.extensions.code}: ${error.message}`);
27 | }
28 | }
29 | return result.data;
30 | } catch (e) {
31 | alert(`Error in sending data to server: ${e.message}`);
32 | return null;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-10.13-deleting-an-issue/ui/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | mode: 'development',
5 | entry: { app: ['./src/App.jsx'] },
6 | output: {
7 | filename: '[name].bundle.js',
8 | path: path.resolve(__dirname, 'public'),
9 | publicPath: '/',
10 | },
11 | module: {
12 | rules: [
13 | {
14 | test: /\.jsx?$/,
15 | exclude: /node_modules/,
16 | use: 'babel-loader',
17 | },
18 | ],
19 | },
20 | optimization: {
21 | splitChunks: {
22 | name: 'vendor',
23 | chunks: 'all',
24 | },
25 | },
26 | devtool: 'source-map',
27 | };
28 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-11.12-modals/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | package-lock.json
3 | ui/public/*.js
4 | ui/public/*.map
5 | .env
6 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-11.12-modals/README.md:
--------------------------------------------------------------------------------
1 | # Pro MERN Stack - 2nd Edition
2 |
3 | You are browsing the source code at the end of one of the sections in the book.
4 |
5 | The project's README which contains the list of all chapters, sections
6 | their sources and other useful information can be found in the
7 | [master branch](https://github.com/vasansr/pro-mern-stack-2).
8 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-11.12-modals/api/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb-base",
3 | "env": {
4 | "node": "true"
5 | },
6 | rules: {
7 | "no-console": "off"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-11.12-modals/api/about.js:
--------------------------------------------------------------------------------
1 | let aboutMessage = 'Issue Tracker API v1.0';
2 |
3 | function setMessage(_, { message }) {
4 | aboutMessage = message;
5 | return aboutMessage;
6 | }
7 |
8 | function getMessage() {
9 | return aboutMessage;
10 | }
11 |
12 | module.exports = { getMessage, setMessage };
13 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-11.12-modals/api/api_handler.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | require('dotenv').config();
3 | const { ApolloServer } = require('apollo-server-express');
4 |
5 | const GraphQLDate = require('./graphql_date.js');
6 | const about = require('./about.js');
7 | const issue = require('./issue.js');
8 |
9 | const resolvers = {
10 | Query: {
11 | about: about.getMessage,
12 | issueList: issue.list,
13 | issue: issue.get,
14 | },
15 | Mutation: {
16 | setAboutMessage: about.setMessage,
17 | issueAdd: issue.add,
18 | issueUpdate: issue.update,
19 | issueDelete: issue.delete,
20 | },
21 | GraphQLDate,
22 | };
23 |
24 | const server = new ApolloServer({
25 | typeDefs: fs.readFileSync('schema.graphql', 'utf-8'),
26 | resolvers,
27 | formatError: (error) => {
28 | console.log(error);
29 | return error;
30 | },
31 | });
32 |
33 | function installHandler(app) {
34 | const enableCors = (process.env.ENABLE_CORS || 'true') === 'true';
35 | console.log('CORS setting:', enableCors);
36 | server.applyMiddleware({ app, path: '/graphql', cors: enableCors });
37 | }
38 |
39 | module.exports = { installHandler };
40 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-11.12-modals/api/db.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config();
2 | const { MongoClient } = require('mongodb');
3 |
4 | let db;
5 |
6 | async function connectToDb() {
7 | const url = process.env.DB_URL || 'mongodb://localhost/issuetracker';
8 | const client = new MongoClient(url, { useNewUrlParser: true });
9 | await client.connect();
10 | console.log('Connected to MongoDB at', url);
11 | db = client.db();
12 | }
13 |
14 | async function getNextSequence(name) {
15 | const result = await db.collection('counters').findOneAndUpdate(
16 | { _id: name },
17 | { $inc: { current: 1 } },
18 | { returnOriginal: false },
19 | );
20 | return result.value.current;
21 | }
22 |
23 | function getDb() {
24 | return db;
25 | }
26 |
27 | module.exports = { connectToDb, getNextSequence, getDb };
28 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-11.12-modals/api/graphql_date.js:
--------------------------------------------------------------------------------
1 | const { GraphQLScalarType } = require('graphql');
2 | const { Kind } = require('graphql/language');
3 |
4 | const GraphQLDate = new GraphQLScalarType({
5 | name: 'GraphQLDate',
6 | description: 'A Date() type in GraphQL as a scalar',
7 | serialize(value) {
8 | return value.toISOString();
9 | },
10 | parseValue(value) {
11 | const dateValue = new Date(value);
12 | return Number.isNaN(dateValue.getTime()) ? undefined : dateValue;
13 | },
14 | parseLiteral(ast) {
15 | if (ast.kind === Kind.STRING) {
16 | const value = new Date(ast.value);
17 | return Number.isNaN(value.getTime()) ? undefined : value;
18 | }
19 | return undefined;
20 | },
21 | });
22 |
23 | module.exports = GraphQLDate;
24 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-11.12-modals/api/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pro-mern-stack-2-api",
3 | "version": "1.0.0",
4 | "description": "Pro MERN Stack (2nd Edition) API",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "nodemon -e js,graphql -w . -w .env server.js",
8 | "lint": "eslint .",
9 | "test": "echo \"Error: no test specified\" && exit 1"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git+https://github.com/vasansr/pro-mern-stack-2.git"
14 | },
15 | "author": "vasan.promern@gmail.com",
16 | "license": "ISC",
17 | "homepage": "https://github.com/vasansr/pro-mern-stack-2",
18 | "dependencies": {
19 | "apollo-server-express": "^2.3.1",
20 | "dotenv": "^6.2.0",
21 | "express": "^4.16.4",
22 | "graphql": "^0.13.2",
23 | "mongodb": "^3.1.10",
24 | "nodemon": "^1.18.9"
25 | },
26 | "devDependencies": {
27 | "eslint": "^5.12.0",
28 | "eslint-config-airbnb-base": "^13.1.0",
29 | "eslint-plugin-import": "^2.14.0"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-11.12-modals/api/sample.env:
--------------------------------------------------------------------------------
1 | ## DB
2 | # Local
3 | DB_URL=mongodb://localhost/issuetracker
4 |
5 | # Atlas - replace UUU: user, PPP: password, XXX: hostname
6 | # DB_URL=mongodb+srv://UUU:PPP@XXX.mongodb.net/issuetracker?retryWrites=true
7 |
8 | # mLab - replace UUU: user, PPP: password, XXX: hostname, YYY: port
9 | # DB_URL=mongodb://UUU:PPP@XXX.mlab.com:YYY/issuetracker
10 |
11 |
12 | ## Server Port
13 | API_SERVER_PORT=3000
14 |
15 | ## Enable CORS (default: true)
16 | # ENABLE_CORS=false
17 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-11.12-modals/api/schema.graphql:
--------------------------------------------------------------------------------
1 | scalar GraphQLDate
2 |
3 | enum StatusType {
4 | New
5 | Assigned
6 | Fixed
7 | Closed
8 | }
9 |
10 | type Issue {
11 | _id: ID!
12 | id: Int!
13 | title: String!
14 | status: StatusType!
15 | owner: String
16 | effort: Int
17 | created: GraphQLDate!
18 | due: GraphQLDate
19 | description: String
20 | }
21 |
22 | "Toned down Issue, used as inputs, without server generated values."
23 | input IssueInputs {
24 | title: String!
25 | "Optional, if not supplied, will be set to 'New'"
26 | status: StatusType = New
27 | owner: String
28 | effort: Int
29 | due: GraphQLDate
30 | description: String
31 | }
32 |
33 | """Inputs for issueUpdate: all are optional. Whichever is specified will
34 | be set to the given value, undefined fields will remain unmodified."""
35 | input IssueUpdateInputs {
36 | title: String
37 | status: StatusType
38 | owner: String
39 | effort: Int
40 | due: GraphQLDate
41 | description: String
42 | }
43 |
44 | ##### Top level declarations
45 |
46 | type Query {
47 | about: String!
48 | issueList(
49 | status: StatusType
50 | effortMin: Int
51 | effortMax: Int
52 | ): [Issue!]!
53 | issue(id: Int!): Issue!
54 | }
55 |
56 | type Mutation {
57 | setAboutMessage(message: String!): String
58 | issueAdd(issue: IssueInputs!): Issue!
59 | issueUpdate(id: Int!, changes: IssueUpdateInputs!): Issue!
60 | issueDelete(id: Int!): Boolean!
61 | }
62 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-11.12-modals/api/server.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config();
2 | const express = require('express');
3 | const { connectToDb } = require('./db.js');
4 | const { installHandler } = require('./api_handler.js');
5 |
6 | const app = express();
7 |
8 | installHandler(app);
9 |
10 | const port = process.env.API_SERVER_PORT || 3000;
11 |
12 | (async function start() {
13 | try {
14 | await connectToDb();
15 | app.listen(port, () => {
16 | console.log(`API server started on port ${port}`);
17 | });
18 | } catch (err) {
19 | console.log('ERROR:', err);
20 | }
21 | }());
22 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-11.12-modals/ui/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb-base",
3 | "env": {
4 | "node": true
5 | },
6 | "rules": {
7 | "no-console": "off"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-11.12-modals/ui/public/bootstrap:
--------------------------------------------------------------------------------
1 | ../node_modules/bootstrap/dist
--------------------------------------------------------------------------------
/pro-mern-stack-2-11.12-modals/ui/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Pro MERN Stack
7 |
8 |
9 |
10 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-11.12-modals/ui/sample.env:
--------------------------------------------------------------------------------
1 | UI_SERVER_PORT=8000
2 | UI_API_ENDPOINT=http://localhost:3000/graphql
3 | # API_PROXY_TARGET=http://localhost:3000
4 | # ENABLE_HMR=true
5 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-11.12-modals/ui/src/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["@babel/preset-env", {
4 | "targets": {
5 | "ie": "11",
6 | "edge": "15",
7 | "safari": "10",
8 | "firefox": "50",
9 | "chrome": "49"
10 | }
11 | }],
12 | "@babel/preset-react"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-11.12-modals/ui/src/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb",
3 | "env": {
4 | "browser": true
5 | },
6 | "rules": {
7 | "import/extensions": [ "error", "always", { "ignorePackages": true } ],
8 | "react/prop-types": "off"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-11.12-modals/ui/src/App.jsx:
--------------------------------------------------------------------------------
1 | import 'babel-polyfill';
2 | import 'whatwg-fetch';
3 | import React from 'react';
4 | import ReactDOM from 'react-dom';
5 | import { BrowserRouter as Router } from 'react-router-dom';
6 |
7 | import Page from './Page.jsx';
8 |
9 | const element = (
10 |
11 |
12 |
13 | );
14 |
15 | ReactDOM.render(element, document.getElementById('contents'));
16 |
17 | if (module.hot) {
18 | module.hot.accept();
19 | }
20 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-11.12-modals/ui/src/Contents.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Switch, Route, Redirect } from 'react-router-dom';
3 |
4 | import IssueList from './IssueList.jsx';
5 | import IssueReport from './IssueReport.jsx';
6 | import IssueEdit from './IssueEdit.jsx';
7 |
8 | const NotFound = () => Page Not Found
;
9 |
10 | export default function Contents() {
11 | return (
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-11.12-modals/ui/src/IssueReport.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default function IssueReport() {
4 | return (
5 |
6 |
This is a placeholder for the Issue Report
7 |
8 | );
9 | }
10 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-11.12-modals/ui/src/NumInput.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function format(num) {
4 | return num != null ? num.toString() : '';
5 | }
6 |
7 | function unformat(str) {
8 | const val = parseInt(str, 10);
9 | return Number.isNaN(val) ? null : val;
10 | }
11 |
12 | export default class NumInput extends React.Component {
13 | constructor(props) {
14 | super(props);
15 | this.state = { value: format(props.value) };
16 | this.onBlur = this.onBlur.bind(this);
17 | this.onChange = this.onChange.bind(this);
18 | }
19 |
20 | onChange(e) {
21 | if (e.target.value.match(/^\d*$/)) {
22 | this.setState({ value: e.target.value });
23 | }
24 | }
25 |
26 | onBlur(e) {
27 | const { onChange } = this.props;
28 | const { value } = this.state;
29 | onChange(e, unformat(value));
30 | }
31 |
32 | render() {
33 | const { value } = this.state;
34 | return (
35 |
42 | );
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-11.12-modals/ui/src/TextInput.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function format(text) {
4 | return text != null ? text : '';
5 | }
6 |
7 | function unformat(text) {
8 | return text.trim().length === 0 ? null : text;
9 | }
10 |
11 | export default class TextInput extends React.Component {
12 | constructor(props) {
13 | super(props);
14 | this.state = { value: format(props.value) };
15 | this.onBlur = this.onBlur.bind(this);
16 | this.onChange = this.onChange.bind(this);
17 | }
18 |
19 | onChange(e) {
20 | this.setState({ value: e.target.value });
21 | }
22 |
23 | onBlur(e) {
24 | const { onChange } = this.props;
25 | const { value } = this.state;
26 | onChange(e, unformat(value));
27 | }
28 |
29 | render() {
30 | const { value } = this.state;
31 | const { tag = 'input', ...props } = this.props;
32 | return React.createElement(tag, {
33 | ...props,
34 | value,
35 | onBlur: this.onBlur,
36 | onChange: this.onChange,
37 | });
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-11.12-modals/ui/src/Toast.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Alert, Collapse } from 'react-bootstrap';
3 |
4 | export default class Toast extends React.Component {
5 | componentDidUpdate() {
6 | const { showing, onDismiss } = this.props;
7 | if (showing) {
8 | clearTimeout(this.dismissTimer);
9 | this.dismissTimer = setTimeout(onDismiss, 5000);
10 | }
11 | }
12 |
13 | componentWillUnmount() {
14 | clearTimeout(this.dismissTimer);
15 | }
16 |
17 | render() {
18 | const {
19 | showing, bsStyle, onDismiss, children,
20 | } = this.props;
21 | return (
22 |
23 |
24 |
25 | {children}
26 |
27 |
28 |
29 | );
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-11.12-modals/ui/src/graphQLFetch.js:
--------------------------------------------------------------------------------
1 | const dateRegex = new RegExp('^\\d\\d\\d\\d-\\d\\d-\\d\\d');
2 |
3 | function jsonDateReviver(key, value) {
4 | if (dateRegex.test(value)) return new Date(value);
5 | return value;
6 | }
7 |
8 | export default async function
9 | graphQLFetch(query, variables = {}, showError = null) {
10 | try {
11 | const response = await fetch(window.ENV.UI_API_ENDPOINT, {
12 | method: 'POST',
13 | headers: { 'Content-Type': 'application/json' },
14 | body: JSON.stringify({ query, variables }),
15 | });
16 | const body = await response.text();
17 | const result = JSON.parse(body, jsonDateReviver);
18 |
19 | if (result.errors) {
20 | const error = result.errors[0];
21 | if (error.extensions.code === 'BAD_USER_INPUT') {
22 | const details = error.extensions.exception.errors.join('\n ');
23 | if (showError) showError(`${error.message}:\n ${details}`);
24 | } else if (showError) {
25 | showError(`${error.extensions.code}: ${error.message}`);
26 | }
27 | }
28 | return result.data;
29 | } catch (e) {
30 | if (showError) showError(`Error in sending data to server: ${e.message}`);
31 | return null;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-11.12-modals/ui/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | mode: 'development',
5 | entry: { app: ['./src/App.jsx'] },
6 | output: {
7 | filename: '[name].bundle.js',
8 | path: path.resolve(__dirname, 'public'),
9 | publicPath: '/',
10 | },
11 | module: {
12 | rules: [
13 | {
14 | test: /\.jsx?$/,
15 | exclude: /node_modules/,
16 | use: 'babel-loader',
17 | },
18 | ],
19 | },
20 | optimization: {
21 | splitChunks: {
22 | name: 'vendor',
23 | chunks: 'all',
24 | },
25 | },
26 | devtool: 'source-map',
27 | };
28 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-12.14-redirects/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | package-lock.json
3 | ui/public/*.js
4 | ui/public/*.map
5 | .env
6 | ui/dist
7 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-12.14-redirects/README.md:
--------------------------------------------------------------------------------
1 | # Pro MERN Stack - 2nd Edition
2 |
3 | You are browsing the source code at the end of one of the sections in the book.
4 |
5 | The project's README which contains the list of all chapters, sections
6 | their sources and other useful information can be found in the
7 | [master branch](https://github.com/vasansr/pro-mern-stack-2).
8 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-12.14-redirects/api/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb-base",
3 | "env": {
4 | "node": "true"
5 | },
6 | rules: {
7 | "no-console": "off"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-12.14-redirects/api/about.js:
--------------------------------------------------------------------------------
1 | let aboutMessage = 'Issue Tracker API v1.0';
2 |
3 | function setMessage(_, { message }) {
4 | aboutMessage = message;
5 | return aboutMessage;
6 | }
7 |
8 | function getMessage() {
9 | return aboutMessage;
10 | }
11 |
12 | module.exports = { getMessage, setMessage };
13 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-12.14-redirects/api/api_handler.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | require('dotenv').config();
3 | const { ApolloServer } = require('apollo-server-express');
4 |
5 | const GraphQLDate = require('./graphql_date.js');
6 | const about = require('./about.js');
7 | const issue = require('./issue.js');
8 |
9 | const resolvers = {
10 | Query: {
11 | about: about.getMessage,
12 | issueList: issue.list,
13 | issue: issue.get,
14 | },
15 | Mutation: {
16 | setAboutMessage: about.setMessage,
17 | issueAdd: issue.add,
18 | issueUpdate: issue.update,
19 | issueDelete: issue.delete,
20 | },
21 | GraphQLDate,
22 | };
23 |
24 | const server = new ApolloServer({
25 | typeDefs: fs.readFileSync('schema.graphql', 'utf-8'),
26 | resolvers,
27 | formatError: (error) => {
28 | console.log(error);
29 | return error;
30 | },
31 | });
32 |
33 | function installHandler(app) {
34 | const enableCors = (process.env.ENABLE_CORS || 'true') === 'true';
35 | console.log('CORS setting:', enableCors);
36 | server.applyMiddleware({ app, path: '/graphql', cors: enableCors });
37 | }
38 |
39 | module.exports = { installHandler };
40 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-12.14-redirects/api/db.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config();
2 | const { MongoClient } = require('mongodb');
3 |
4 | let db;
5 |
6 | async function connectToDb() {
7 | const url = process.env.DB_URL || 'mongodb://localhost/issuetracker';
8 | const client = new MongoClient(url, { useNewUrlParser: true });
9 | await client.connect();
10 | console.log('Connected to MongoDB at', url);
11 | db = client.db();
12 | }
13 |
14 | async function getNextSequence(name) {
15 | const result = await db.collection('counters').findOneAndUpdate(
16 | { _id: name },
17 | { $inc: { current: 1 } },
18 | { returnOriginal: false },
19 | );
20 | return result.value.current;
21 | }
22 |
23 | function getDb() {
24 | return db;
25 | }
26 |
27 | module.exports = { connectToDb, getNextSequence, getDb };
28 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-12.14-redirects/api/graphql_date.js:
--------------------------------------------------------------------------------
1 | const { GraphQLScalarType } = require('graphql');
2 | const { Kind } = require('graphql/language');
3 |
4 | const GraphQLDate = new GraphQLScalarType({
5 | name: 'GraphQLDate',
6 | description: 'A Date() type in GraphQL as a scalar',
7 | serialize(value) {
8 | return value.toISOString();
9 | },
10 | parseValue(value) {
11 | const dateValue = new Date(value);
12 | return Number.isNaN(dateValue.getTime()) ? undefined : dateValue;
13 | },
14 | parseLiteral(ast) {
15 | if (ast.kind === Kind.STRING) {
16 | const value = new Date(ast.value);
17 | return Number.isNaN(value.getTime()) ? undefined : value;
18 | }
19 | return undefined;
20 | },
21 | });
22 |
23 | module.exports = GraphQLDate;
24 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-12.14-redirects/api/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pro-mern-stack-2-api",
3 | "version": "1.0.0",
4 | "description": "Pro MERN Stack (2nd Edition) API",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "nodemon -e js,graphql -w . -w .env server.js",
8 | "lint": "eslint .",
9 | "test": "echo \"Error: no test specified\" && exit 1"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git+https://github.com/vasansr/pro-mern-stack-2.git"
14 | },
15 | "author": "vasan.promern@gmail.com",
16 | "license": "ISC",
17 | "homepage": "https://github.com/vasansr/pro-mern-stack-2",
18 | "dependencies": {
19 | "apollo-server-express": "^2.3.1",
20 | "dotenv": "^6.2.0",
21 | "express": "^4.16.4",
22 | "graphql": "^0.13.2",
23 | "mongodb": "^3.1.10",
24 | "nodemon": "^1.18.9"
25 | },
26 | "devDependencies": {
27 | "eslint": "^5.12.0",
28 | "eslint-config-airbnb-base": "^13.1.0",
29 | "eslint-plugin-import": "^2.14.0"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-12.14-redirects/api/sample.env:
--------------------------------------------------------------------------------
1 | ## DB
2 | # Local
3 | DB_URL=mongodb://localhost/issuetracker
4 |
5 | # Atlas - replace UUU: user, PPP: password, XXX: hostname
6 | # DB_URL=mongodb+srv://UUU:PPP@XXX.mongodb.net/issuetracker?retryWrites=true
7 |
8 | # mLab - replace UUU: user, PPP: password, XXX: hostname, YYY: port
9 | # DB_URL=mongodb://UUU:PPP@XXX.mlab.com:YYY/issuetracker
10 |
11 |
12 | ## Server Port
13 | API_SERVER_PORT=3000
14 |
15 | ## Enable CORS (default: true)
16 | # ENABLE_CORS=false
17 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-12.14-redirects/api/schema.graphql:
--------------------------------------------------------------------------------
1 | scalar GraphQLDate
2 |
3 | enum StatusType {
4 | New
5 | Assigned
6 | Fixed
7 | Closed
8 | }
9 |
10 | type Issue {
11 | _id: ID!
12 | id: Int!
13 | title: String!
14 | status: StatusType!
15 | owner: String
16 | effort: Int
17 | created: GraphQLDate!
18 | due: GraphQLDate
19 | description: String
20 | }
21 |
22 | "Toned down Issue, used as inputs, without server generated values."
23 | input IssueInputs {
24 | title: String!
25 | "Optional, if not supplied, will be set to 'New'"
26 | status: StatusType = New
27 | owner: String
28 | effort: Int
29 | due: GraphQLDate
30 | description: String
31 | }
32 |
33 | """Inputs for issueUpdate: all are optional. Whichever is specified will
34 | be set to the given value, undefined fields will remain unmodified."""
35 | input IssueUpdateInputs {
36 | title: String
37 | status: StatusType
38 | owner: String
39 | effort: Int
40 | due: GraphQLDate
41 | description: String
42 | }
43 |
44 | ##### Top level declarations
45 |
46 | type Query {
47 | about: String!
48 | issueList(
49 | status: StatusType
50 | effortMin: Int
51 | effortMax: Int
52 | ): [Issue!]!
53 | issue(id: Int!): Issue!
54 | }
55 |
56 | type Mutation {
57 | setAboutMessage(message: String!): String
58 | issueAdd(issue: IssueInputs!): Issue!
59 | issueUpdate(id: Int!, changes: IssueUpdateInputs!): Issue!
60 | issueDelete(id: Int!): Boolean!
61 | }
62 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-12.14-redirects/api/server.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config();
2 | const express = require('express');
3 | const { connectToDb } = require('./db.js');
4 | const { installHandler } = require('./api_handler.js');
5 |
6 | const app = express();
7 |
8 | installHandler(app);
9 |
10 | const port = process.env.API_SERVER_PORT || 3000;
11 |
12 | (async function start() {
13 | try {
14 | await connectToDb();
15 | app.listen(port, () => {
16 | console.log(`API server started on port ${port}`);
17 | });
18 | } catch (err) {
19 | console.log('ERROR:', err);
20 | }
21 | }());
22 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-12.14-redirects/ui/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb",
3 | }
4 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-12.14-redirects/ui/browser/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true
4 | },
5 | rules: {
6 | "import/extensions": [ 'error', 'always', { ignorePackages: true } ],
7 | "react/prop-types": "off"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-12.14-redirects/ui/browser/App.jsx:
--------------------------------------------------------------------------------
1 | import 'babel-polyfill';
2 | import React from 'react';
3 | import ReactDOM from 'react-dom';
4 | import { BrowserRouter as Router } from 'react-router-dom';
5 |
6 | import Page from '../src/Page.jsx';
7 | import store from '../src/store.js';
8 |
9 | // eslint-disable-next-line no-underscore-dangle
10 | store.initialData = window.__INITIAL_DATA__;
11 |
12 | const element = (
13 |
14 |
15 |
16 | );
17 |
18 | ReactDOM.hydrate(element, document.getElementById('contents'));
19 |
20 | if (module.hot) {
21 | module.hot.accept();
22 | }
23 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-12.14-redirects/ui/public/bootstrap:
--------------------------------------------------------------------------------
1 | ../node_modules/bootstrap/dist
--------------------------------------------------------------------------------
/pro-mern-stack-2-12.14-redirects/ui/sample.env:
--------------------------------------------------------------------------------
1 | UI_SERVER_PORT=8000
2 | UI_API_ENDPOINT=http://localhost:3000/graphql
3 | # UI_SERVER_API_ENDPOINT=http://localhost:3000/graphql
4 | # API_PROXY_TARGET=http://localhost:3000
5 | # ENABLE_HMR=true
6 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-12.14-redirects/ui/server/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "node": true
4 | },
5 | "rules": {
6 | "no-console": "off",
7 | "import/extensions": "off"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-12.14-redirects/ui/server/render.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOMServer from 'react-dom/server';
3 | import { StaticRouter, matchPath } from 'react-router-dom';
4 |
5 | import Page from '../src/Page.jsx';
6 | import template from './template.js';
7 | import store from '../src/store.js';
8 | import routes from '../src/routes.js';
9 |
10 | async function render(req, res) {
11 | const activeRoute = routes.find(
12 | route => matchPath(req.path, route),
13 | );
14 |
15 | let initialData;
16 | if (activeRoute && activeRoute.component.fetchData) {
17 | const match = matchPath(req.path, activeRoute);
18 | const index = req.url.indexOf('?');
19 | const search = index !== -1 ? req.url.substr(index) : null;
20 | initialData = await activeRoute.component.fetchData(match, search);
21 | }
22 |
23 | store.initialData = initialData;
24 | const context = {};
25 | const element = (
26 |
27 |
28 |
29 | );
30 | const body = ReactDOMServer.renderToString(element);
31 |
32 | if (context.url) {
33 | res.redirect(301, context.url);
34 | } else {
35 | res.send(template(body, initialData));
36 | }
37 | }
38 |
39 | export default render;
40 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-12.14-redirects/ui/server/template.js:
--------------------------------------------------------------------------------
1 | import serialize from 'serialize-javascript';
2 |
3 | export default function template(body, data) {
4 | return `
5 |
6 |
7 |
8 |
9 | Pro MERN Stack
10 |
11 |
12 |
13 |
17 |
18 |
19 |
20 |
21 | ${body}
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | `;
31 | }
32 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-12.14-redirects/ui/src/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "node": true
5 | },
6 | "rules": {
7 | "import/extensions": [ "error", "always", { "ignorePackages": true } ],
8 | "react/prop-types": "off"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-12.14-redirects/ui/src/About.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import store from './store.js';
3 | import graphQLFetch from './graphQLFetch.js';
4 |
5 | export default class About extends React.Component {
6 | static async fetchData() {
7 | const data = await graphQLFetch('query {about}');
8 | return data;
9 | }
10 |
11 | constructor(props) {
12 | super(props);
13 | const apiAbout = store.initialData ? store.initialData.about : null;
14 | delete store.initialData;
15 | this.state = { apiAbout };
16 | }
17 |
18 | async componentDidMount() {
19 | const { apiAbout } = this.state;
20 | if (apiAbout == null) {
21 | const data = await About.fetchData();
22 | this.setState({ apiAbout: data.about });
23 | }
24 | }
25 |
26 | render() {
27 | const { apiAbout } = this.state;
28 | return (
29 |
30 |
Issue Tracker version 0.9
31 |
32 | {apiAbout}
33 |
34 |
35 | );
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-12.14-redirects/ui/src/Contents.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Switch, Route, Redirect } from 'react-router-dom';
3 |
4 | import routes from './routes.js';
5 |
6 | export default function Contents() {
7 | return (
8 |
9 |
10 | {routes.map(attrs => )}
11 |
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-12.14-redirects/ui/src/IssueDetail.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default function IssueDetail({ issue }) {
4 | if (issue) {
5 | return (
6 |
7 |
Description
8 |
{issue.description}
9 |
10 | );
11 | }
12 | return null;
13 | }
14 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-12.14-redirects/ui/src/IssueReport.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default function IssueReport() {
4 | return (
5 |
6 |
This is a placeholder for the Issue Report
7 |
8 | );
9 | }
10 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-12.14-redirects/ui/src/NotFound.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function NotFound() {
4 | return Page Not Found
;
5 | }
6 |
7 | export default NotFound;
8 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-12.14-redirects/ui/src/NumInput.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function format(num) {
4 | return num != null ? num.toString() : '';
5 | }
6 |
7 | function unformat(str) {
8 | const val = parseInt(str, 10);
9 | return Number.isNaN(val) ? null : val;
10 | }
11 |
12 | export default class NumInput extends React.Component {
13 | constructor(props) {
14 | super(props);
15 | this.state = { value: format(props.value) };
16 | this.onBlur = this.onBlur.bind(this);
17 | this.onChange = this.onChange.bind(this);
18 | }
19 |
20 | onChange(e) {
21 | if (e.target.value.match(/^\d*$/)) {
22 | this.setState({ value: e.target.value });
23 | }
24 | }
25 |
26 | onBlur(e) {
27 | const { onChange } = this.props;
28 | const { value } = this.state;
29 | onChange(e, unformat(value));
30 | }
31 |
32 | render() {
33 | const { value } = this.state;
34 | return (
35 |
42 | );
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-12.14-redirects/ui/src/TextInput.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function format(text) {
4 | return text != null ? text : '';
5 | }
6 |
7 | function unformat(text) {
8 | return text.trim().length === 0 ? null : text;
9 | }
10 |
11 | export default class TextInput extends React.Component {
12 | constructor(props) {
13 | super(props);
14 | this.state = { value: format(props.value) };
15 | this.onBlur = this.onBlur.bind(this);
16 | this.onChange = this.onChange.bind(this);
17 | }
18 |
19 | onChange(e) {
20 | this.setState({ value: e.target.value });
21 | }
22 |
23 | onBlur(e) {
24 | const { onChange } = this.props;
25 | const { value } = this.state;
26 | onChange(e, unformat(value));
27 | }
28 |
29 | render() {
30 | const { value } = this.state;
31 | const { tag = 'input', ...props } = this.props;
32 | return React.createElement(tag, {
33 | ...props,
34 | value,
35 | onBlur: this.onBlur,
36 | onChange: this.onChange,
37 | });
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-12.14-redirects/ui/src/Toast.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Alert, Collapse } from 'react-bootstrap';
3 |
4 | export default class Toast extends React.Component {
5 | componentDidUpdate() {
6 | const { showing, onDismiss } = this.props;
7 | if (showing) {
8 | clearTimeout(this.dismissTimer);
9 | this.dismissTimer = setTimeout(onDismiss, 5000);
10 | }
11 | }
12 |
13 | componentWillUnmount() {
14 | clearTimeout(this.dismissTimer);
15 | }
16 |
17 | render() {
18 | const {
19 | showing, bsStyle, onDismiss, children,
20 | } = this.props;
21 | return (
22 |
23 |
24 |
25 | {children}
26 |
27 |
28 |
29 | );
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-12.14-redirects/ui/src/graphQLFetch.js:
--------------------------------------------------------------------------------
1 | import fetch from 'isomorphic-fetch';
2 |
3 | const dateRegex = new RegExp('^\\d\\d\\d\\d-\\d\\d-\\d\\d');
4 |
5 | function jsonDateReviver(key, value) {
6 | if (dateRegex.test(value)) return new Date(value);
7 | return value;
8 | }
9 |
10 | export default async function
11 | graphQLFetch(query, variables = {}, showError = null) {
12 | const apiEndpoint = (__isBrowser__) // eslint-disable-line no-undef
13 | ? window.ENV.UI_API_ENDPOINT
14 | : process.env.UI_SERVER_API_ENDPOINT;
15 | try {
16 | const response = await fetch(apiEndpoint, {
17 | method: 'POST',
18 | headers: { 'Content-Type': 'application/json' },
19 | body: JSON.stringify({ query, variables }),
20 | });
21 | const body = await response.text();
22 | const result = JSON.parse(body, jsonDateReviver);
23 |
24 | if (result.errors) {
25 | const error = result.errors[0];
26 | if (error.extensions.code === 'BAD_USER_INPUT') {
27 | const details = error.extensions.exception.errors.join('\n ');
28 | if (showError) showError(`${error.message}:\n ${details}`);
29 | } else if (showError) {
30 | showError(`${error.extensions.code}: ${error.message}`);
31 | }
32 | }
33 | return result.data;
34 | } catch (e) {
35 | if (showError) showError(`Error in sending data to server: ${e.message}`);
36 | return null;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-12.14-redirects/ui/src/routes.js:
--------------------------------------------------------------------------------
1 | import IssueList from './IssueList.jsx';
2 | import IssueReport from './IssueReport.jsx';
3 | import IssueEdit from './IssueEdit.jsx';
4 | import About from './About.jsx';
5 | import NotFound from './NotFound.jsx';
6 |
7 | const routes = [
8 | { path: '/issues/:id?', component: IssueList },
9 | { path: '/edit/:id', component: IssueEdit },
10 | { path: '/report', component: IssueReport },
11 | { path: '/about', component: About },
12 | { path: '*', component: NotFound },
13 | ];
14 |
15 | export default routes;
16 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-12.14-redirects/ui/src/store.js:
--------------------------------------------------------------------------------
1 | const store = {};
2 |
3 | export default store;
4 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-12.14-redirects/ui/webpack.serverHMR.js:
--------------------------------------------------------------------------------
1 | /*
2 | eslint-disable import/no-extraneous-dependencies
3 | */
4 | const webpack = require('webpack');
5 | const merge = require('webpack-merge');
6 | const serverConfig = require('./webpack.config.js')[1];
7 |
8 | module.exports = merge(serverConfig, {
9 | entry: { server: ['./node_modules/webpack/hot/poll?1000'] },
10 | plugins: [
11 | new webpack.HotModuleReplacementPlugin(),
12 | ],
13 | });
14 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-13.10-search-bar/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | package-lock.json
3 | ui/public/*.js
4 | ui/public/*.map
5 | .env
6 | ui/dist
7 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-13.10-search-bar/README.md:
--------------------------------------------------------------------------------
1 | # Pro MERN Stack - 2nd Edition
2 |
3 | You are browsing the source code at the end of one of the sections in the book.
4 |
5 | The project's README which contains the list of all chapters, sections
6 | their sources and other useful information can be found in the
7 | [master branch](https://github.com/vasansr/pro-mern-stack-2).
8 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-13.10-search-bar/api/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb-base",
3 | "env": {
4 | "node": "true"
5 | },
6 | rules: {
7 | "no-console": "off"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-13.10-search-bar/api/about.js:
--------------------------------------------------------------------------------
1 | let aboutMessage = 'Issue Tracker API v1.0';
2 |
3 | function setMessage(_, { message }) {
4 | aboutMessage = message;
5 | return aboutMessage;
6 | }
7 |
8 | function getMessage() {
9 | return aboutMessage;
10 | }
11 |
12 | module.exports = { getMessage, setMessage };
13 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-13.10-search-bar/api/api_handler.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | require('dotenv').config();
3 | const { ApolloServer } = require('apollo-server-express');
4 |
5 | const GraphQLDate = require('./graphql_date.js');
6 | const about = require('./about.js');
7 | const issue = require('./issue.js');
8 |
9 | const resolvers = {
10 | Query: {
11 | about: about.getMessage,
12 | issueList: issue.list,
13 | issue: issue.get,
14 | issueCounts: issue.counts,
15 | },
16 | Mutation: {
17 | setAboutMessage: about.setMessage,
18 | issueAdd: issue.add,
19 | issueUpdate: issue.update,
20 | issueDelete: issue.delete,
21 | issueRestore: issue.restore,
22 | },
23 | GraphQLDate,
24 | };
25 |
26 | const server = new ApolloServer({
27 | typeDefs: fs.readFileSync('schema.graphql', 'utf-8'),
28 | resolvers,
29 | formatError: (error) => {
30 | console.log(error);
31 | return error;
32 | },
33 | });
34 |
35 | function installHandler(app) {
36 | const enableCors = (process.env.ENABLE_CORS || 'true') === 'true';
37 | console.log('CORS setting:', enableCors);
38 | server.applyMiddleware({ app, path: '/graphql', cors: enableCors });
39 | }
40 |
41 | module.exports = { installHandler };
42 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-13.10-search-bar/api/db.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config();
2 | const { MongoClient } = require('mongodb');
3 |
4 | let db;
5 |
6 | async function connectToDb() {
7 | const url = process.env.DB_URL || 'mongodb://localhost/issuetracker';
8 | const client = new MongoClient(url, { useNewUrlParser: true });
9 | await client.connect();
10 | console.log('Connected to MongoDB at', url);
11 | db = client.db();
12 | }
13 |
14 | async function getNextSequence(name) {
15 | const result = await db.collection('counters').findOneAndUpdate(
16 | { _id: name },
17 | { $inc: { current: 1 } },
18 | { returnOriginal: false },
19 | );
20 | return result.value.current;
21 | }
22 |
23 | function getDb() {
24 | return db;
25 | }
26 |
27 | module.exports = { connectToDb, getNextSequence, getDb };
28 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-13.10-search-bar/api/graphql_date.js:
--------------------------------------------------------------------------------
1 | const { GraphQLScalarType } = require('graphql');
2 | const { Kind } = require('graphql/language');
3 |
4 | const GraphQLDate = new GraphQLScalarType({
5 | name: 'GraphQLDate',
6 | description: 'A Date() type in GraphQL as a scalar',
7 | serialize(value) {
8 | return value.toISOString();
9 | },
10 | parseValue(value) {
11 | const dateValue = new Date(value);
12 | return Number.isNaN(dateValue.getTime()) ? undefined : dateValue;
13 | },
14 | parseLiteral(ast) {
15 | if (ast.kind === Kind.STRING) {
16 | const value = new Date(ast.value);
17 | return Number.isNaN(value.getTime()) ? undefined : value;
18 | }
19 | return undefined;
20 | },
21 | });
22 |
23 | module.exports = GraphQLDate;
24 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-13.10-search-bar/api/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pro-mern-stack-2-api",
3 | "version": "1.0.0",
4 | "description": "Pro MERN Stack (2nd Edition) API",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "nodemon -e js,graphql -w . -w .env server.js",
8 | "lint": "eslint .",
9 | "test": "echo \"Error: no test specified\" && exit 1"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git+https://github.com/vasansr/pro-mern-stack-2.git"
14 | },
15 | "author": "vasan.promern@gmail.com",
16 | "license": "ISC",
17 | "homepage": "https://github.com/vasansr/pro-mern-stack-2",
18 | "dependencies": {
19 | "apollo-server-express": "^2.3.1",
20 | "dotenv": "^6.2.0",
21 | "express": "^4.16.4",
22 | "graphql": "^0.13.2",
23 | "mongodb": "^3.1.10",
24 | "nodemon": "^1.18.9"
25 | },
26 | "devDependencies": {
27 | "eslint": "^5.12.0",
28 | "eslint-config-airbnb-base": "^13.1.0",
29 | "eslint-plugin-import": "^2.14.0"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-13.10-search-bar/api/sample.env:
--------------------------------------------------------------------------------
1 | ## DB
2 | # Local
3 | DB_URL=mongodb://localhost/issuetracker
4 |
5 | # Atlas - replace UUU: user, PPP: password, XXX: hostname
6 | # DB_URL=mongodb+srv://UUU:PPP@XXX.mongodb.net/issuetracker?retryWrites=true
7 |
8 | # mLab - replace UUU: user, PPP: password, XXX: hostname, YYY: port
9 | # DB_URL=mongodb://UUU:PPP@XXX.mlab.com:YYY/issuetracker
10 |
11 |
12 | ## Server Port
13 | API_SERVER_PORT=3000
14 |
15 | ## Enable CORS (default: true)
16 | # ENABLE_CORS=false
17 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-13.10-search-bar/api/scripts/generate_data.mongo.js:
--------------------------------------------------------------------------------
1 | /* global db print */
2 | /* eslint no-restricted-globals: "off" */
3 |
4 | const owners = ['Ravan', 'Eddie', 'Pieta', 'Parvati', 'Victor'];
5 | const statuses = ['New', 'Assigned', 'Fixed', 'Closed'];
6 |
7 | const initialCount = db.issues.count();
8 |
9 | for (let i = 0; i < 100; i += 1) {
10 | const randomCreatedDate = (new Date())
11 | - Math.floor(Math.random() * 60) * 1000 * 60 * 60 * 24;
12 | const created = new Date(randomCreatedDate);
13 | const randomDueDate = (new Date())
14 | - Math.floor(Math.random() * 60) * 1000 * 60 * 60 * 24;
15 | const due = new Date(randomDueDate);
16 |
17 | const owner = owners[Math.floor(Math.random() * 5)];
18 | const status = statuses[Math.floor(Math.random() * 4)];
19 | const effort = Math.ceil(Math.random() * 20);
20 | const title = `Lorem ipsum dolor sit amet, ${i}`;
21 | const id = initialCount + i + 1;
22 |
23 | const issue = {
24 | id, title, created, due, owner, status, effort,
25 | };
26 |
27 | db.issues.insertOne(issue);
28 | }
29 |
30 | const count = db.issues.count();
31 | db.counters.update({ _id: 'issues' }, { $set: { current: count } });
32 |
33 | print('New issue count:', count);
34 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-13.10-search-bar/api/server.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config();
2 | const express = require('express');
3 | const { connectToDb } = require('./db.js');
4 | const { installHandler } = require('./api_handler.js');
5 |
6 | const app = express();
7 |
8 | installHandler(app);
9 |
10 | const port = process.env.API_SERVER_PORT || 3000;
11 |
12 | (async function start() {
13 | try {
14 | await connectToDb();
15 | app.listen(port, () => {
16 | console.log(`API server started on port ${port}`);
17 | });
18 | } catch (err) {
19 | console.log('ERROR:', err);
20 | }
21 | }());
22 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-13.10-search-bar/ui/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb",
3 | }
4 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-13.10-search-bar/ui/browser/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true
4 | },
5 | rules: {
6 | "import/extensions": [ 'error', 'always', { ignorePackages: true } ],
7 | "react/prop-types": "off"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-13.10-search-bar/ui/browser/App.jsx:
--------------------------------------------------------------------------------
1 | import 'babel-polyfill';
2 | import React from 'react';
3 | import ReactDOM from 'react-dom';
4 | import { BrowserRouter as Router } from 'react-router-dom';
5 |
6 | import Page from '../src/Page.jsx';
7 | import store from '../src/store.js';
8 |
9 | // eslint-disable-next-line no-underscore-dangle
10 | store.initialData = window.__INITIAL_DATA__;
11 |
12 | const element = (
13 |
14 |
15 |
16 | );
17 |
18 | ReactDOM.hydrate(element, document.getElementById('contents'));
19 |
20 | if (module.hot) {
21 | module.hot.accept();
22 | }
23 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-13.10-search-bar/ui/public/bootstrap:
--------------------------------------------------------------------------------
1 | ../node_modules/bootstrap/dist
--------------------------------------------------------------------------------
/pro-mern-stack-2-13.10-search-bar/ui/sample.env:
--------------------------------------------------------------------------------
1 | UI_SERVER_PORT=8000
2 | UI_API_ENDPOINT=http://localhost:3000/graphql
3 | # UI_SERVER_API_ENDPOINT=http://localhost:3000/graphql
4 | # API_PROXY_TARGET=http://localhost:3000
5 | # ENABLE_HMR=true
6 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-13.10-search-bar/ui/server/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "node": true
4 | },
5 | "rules": {
6 | "no-console": "off",
7 | "import/extensions": "off"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-13.10-search-bar/ui/server/render.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOMServer from 'react-dom/server';
3 | import { StaticRouter, matchPath } from 'react-router-dom';
4 |
5 | import Page from '../src/Page.jsx';
6 | import template from './template.js';
7 | import store from '../src/store.js';
8 | import routes from '../src/routes.js';
9 |
10 | async function render(req, res) {
11 | const activeRoute = routes.find(
12 | route => matchPath(req.path, route),
13 | );
14 |
15 | let initialData;
16 | if (activeRoute && activeRoute.component.fetchData) {
17 | const match = matchPath(req.path, activeRoute);
18 | const index = req.url.indexOf('?');
19 | const search = index !== -1 ? req.url.substr(index) : null;
20 | initialData = await activeRoute.component.fetchData(match, search);
21 | }
22 |
23 | store.initialData = initialData;
24 | const context = {};
25 | const element = (
26 |
27 |
28 |
29 | );
30 | const body = ReactDOMServer.renderToString(element);
31 |
32 | if (context.url) {
33 | res.redirect(301, context.url);
34 | } else {
35 | res.send(template(body, initialData));
36 | }
37 | }
38 |
39 | export default render;
40 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-13.10-search-bar/ui/server/template.js:
--------------------------------------------------------------------------------
1 | import serialize from 'serialize-javascript';
2 |
3 | export default function template(body, data) {
4 | return `
5 |
6 |
7 |
8 |
9 | Pro MERN Stack
10 |
11 |
12 |
13 |
17 |
18 |
19 |
20 |
21 | ${body}
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | `;
31 | }
32 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-13.10-search-bar/ui/src/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "node": true
5 | },
6 | "rules": {
7 | "import/extensions": [ "error", "always", { "ignorePackages": true } ],
8 | "react/prop-types": "off"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-13.10-search-bar/ui/src/About.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import store from './store.js';
3 | import graphQLFetch from './graphQLFetch.js';
4 |
5 | export default class About extends React.Component {
6 | static async fetchData() {
7 | const data = await graphQLFetch('query {about}');
8 | return data;
9 | }
10 |
11 | constructor(props) {
12 | super(props);
13 | const apiAbout = store.initialData ? store.initialData.about : null;
14 | delete store.initialData;
15 | this.state = { apiAbout };
16 | }
17 |
18 | async componentDidMount() {
19 | const { apiAbout } = this.state;
20 | if (apiAbout == null) {
21 | const data = await About.fetchData();
22 | this.setState({ apiAbout: data.about });
23 | }
24 | }
25 |
26 | render() {
27 | const { apiAbout } = this.state;
28 | return (
29 |
30 |
Issue Tracker version 0.9
31 |
32 | {apiAbout}
33 |
34 |
35 | );
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-13.10-search-bar/ui/src/Contents.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Switch, Route, Redirect } from 'react-router-dom';
3 |
4 | import routes from './routes.js';
5 |
6 | export default function Contents() {
7 | return (
8 |
9 |
10 | {routes.map(attrs => )}
11 |
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-13.10-search-bar/ui/src/IssueDetail.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default function IssueDetail({ issue }) {
4 | if (issue) {
5 | return (
6 |
7 |
Description
8 |
{issue.description}
9 |
10 | );
11 | }
12 | return null;
13 | }
14 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-13.10-search-bar/ui/src/NotFound.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function NotFound() {
4 | return Page Not Found
;
5 | }
6 |
7 | export default NotFound;
8 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-13.10-search-bar/ui/src/NumInput.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function format(num) {
4 | return num != null ? num.toString() : '';
5 | }
6 |
7 | function unformat(str) {
8 | const val = parseInt(str, 10);
9 | return Number.isNaN(val) ? null : val;
10 | }
11 |
12 | export default class NumInput extends React.Component {
13 | constructor(props) {
14 | super(props);
15 | this.state = { value: format(props.value) };
16 | this.onBlur = this.onBlur.bind(this);
17 | this.onChange = this.onChange.bind(this);
18 | }
19 |
20 | onChange(e) {
21 | if (e.target.value.match(/^\d*$/)) {
22 | this.setState({ value: e.target.value });
23 | }
24 | }
25 |
26 | onBlur(e) {
27 | const { onChange } = this.props;
28 | const { value } = this.state;
29 | onChange(e, unformat(value));
30 | }
31 |
32 | render() {
33 | const { value } = this.state;
34 | return (
35 |
42 | );
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-13.10-search-bar/ui/src/Search.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import SelectAsync from 'react-select/lib/Async'; // eslint-disable-line
3 | import { withRouter } from 'react-router-dom';
4 |
5 | import graphQLFetch from './graphQLFetch.js';
6 | import withToast from './withToast.jsx';
7 |
8 | class Search extends React.Component {
9 | constructor(props) {
10 | super(props);
11 |
12 | this.onChangeSelection = this.onChangeSelection.bind(this);
13 | this.loadOptions = this.loadOptions.bind(this);
14 | }
15 |
16 | onChangeSelection({ value }) {
17 | const { history } = this.props;
18 | history.push(`/edit/${value}`);
19 | }
20 |
21 | async loadOptions(term) {
22 | if (term.length < 3) return [];
23 | const query = `query issueList($search: String) {
24 | issueList(search: $search) {
25 | issues {id title}
26 | }
27 | }`;
28 |
29 | const { showError } = this.props;
30 | const data = await graphQLFetch(query, { search: term }, showError);
31 | return data.issueList.issues.map(issue => ({
32 | label: `#${issue.id}: ${issue.title}`, value: issue.id,
33 | }));
34 | }
35 |
36 | render() {
37 | return (
38 | true}
43 | onChange={this.onChangeSelection}
44 | components={{ DropdownIndicator: null }}
45 | />
46 | );
47 | }
48 | }
49 |
50 | export default withRouter(withToast(Search));
51 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-13.10-search-bar/ui/src/TextInput.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function format(text) {
4 | return text != null ? text : '';
5 | }
6 |
7 | function unformat(text) {
8 | return text.trim().length === 0 ? null : text;
9 | }
10 |
11 | export default class TextInput extends React.Component {
12 | constructor(props) {
13 | super(props);
14 | this.state = { value: format(props.value) };
15 | this.onBlur = this.onBlur.bind(this);
16 | this.onChange = this.onChange.bind(this);
17 | }
18 |
19 | onChange(e) {
20 | this.setState({ value: e.target.value });
21 | }
22 |
23 | onBlur(e) {
24 | const { onChange } = this.props;
25 | const { value } = this.state;
26 | onChange(e, unformat(value));
27 | }
28 |
29 | render() {
30 | const { value } = this.state;
31 | const { tag = 'input', ...props } = this.props;
32 | return React.createElement(tag, {
33 | ...props,
34 | value,
35 | onBlur: this.onBlur,
36 | onChange: this.onChange,
37 | });
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-13.10-search-bar/ui/src/Toast.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Alert, Collapse } from 'react-bootstrap';
3 |
4 | export default class Toast extends React.Component {
5 | componentDidUpdate() {
6 | const { showing, onDismiss } = this.props;
7 | if (showing) {
8 | clearTimeout(this.dismissTimer);
9 | this.dismissTimer = setTimeout(onDismiss, 5000);
10 | }
11 | }
12 |
13 | componentWillUnmount() {
14 | clearTimeout(this.dismissTimer);
15 | }
16 |
17 | render() {
18 | const {
19 | showing, bsStyle, onDismiss, children,
20 | } = this.props;
21 | return (
22 |
23 |
27 |
28 | {children}
29 |
30 |
31 |
32 | );
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-13.10-search-bar/ui/src/graphQLFetch.js:
--------------------------------------------------------------------------------
1 | import fetch from 'isomorphic-fetch';
2 |
3 | const dateRegex = new RegExp('^\\d\\d\\d\\d-\\d\\d-\\d\\d');
4 |
5 | function jsonDateReviver(key, value) {
6 | if (dateRegex.test(value)) return new Date(value);
7 | return value;
8 | }
9 |
10 | export default async function
11 | graphQLFetch(query, variables = {}, showError = null) {
12 | const apiEndpoint = (__isBrowser__) // eslint-disable-line no-undef
13 | ? window.ENV.UI_API_ENDPOINT
14 | : process.env.UI_SERVER_API_ENDPOINT;
15 | try {
16 | const response = await fetch(apiEndpoint, {
17 | method: 'POST',
18 | headers: { 'Content-Type': 'application/json' },
19 | body: JSON.stringify({ query, variables }),
20 | });
21 | const body = await response.text();
22 | const result = JSON.parse(body, jsonDateReviver);
23 |
24 | if (result.errors) {
25 | const error = result.errors[0];
26 | if (error.extensions.code === 'BAD_USER_INPUT') {
27 | const details = error.extensions.exception.errors.join('\n ');
28 | if (showError) showError(`${error.message}:\n ${details}`);
29 | } else if (showError) {
30 | showError(`${error.extensions.code}: ${error.message}`);
31 | }
32 | }
33 | return result.data;
34 | } catch (e) {
35 | if (showError) showError(`Error in sending data to server: ${e.message}`);
36 | return null;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-13.10-search-bar/ui/src/routes.js:
--------------------------------------------------------------------------------
1 | import IssueList from './IssueList.jsx';
2 | import IssueReport from './IssueReport.jsx';
3 | import IssueEdit from './IssueEdit.jsx';
4 | import About from './About.jsx';
5 | import NotFound from './NotFound.jsx';
6 |
7 | const routes = [
8 | { path: '/issues/:id?', component: IssueList },
9 | { path: '/edit/:id', component: IssueEdit },
10 | { path: '/report', component: IssueReport },
11 | { path: '/about', component: About },
12 | { path: '*', component: NotFound },
13 | ];
14 |
15 | export default routes;
16 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-13.10-search-bar/ui/src/store.js:
--------------------------------------------------------------------------------
1 | const store = {};
2 |
3 | export default store;
4 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-13.10-search-bar/ui/src/withToast.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Toast from './Toast.jsx';
3 |
4 | export default function withToast(OriginalComponent) {
5 | return class ToastWrapper extends React.Component {
6 | constructor(props) {
7 | super(props);
8 | this.state = {
9 | toastVisible: false, toastMessage: '', toastType: 'success',
10 | };
11 | this.showSuccess = this.showSuccess.bind(this);
12 | this.showError = this.showError.bind(this);
13 | this.dismissToast = this.dismissToast.bind(this);
14 | }
15 |
16 | showSuccess(message) {
17 | this.setState({ toastVisible: true, toastMessage: message, toastType: 'success' });
18 | }
19 |
20 | showError(message) {
21 | this.setState({ toastVisible: true, toastMessage: message, toastType: 'danger' });
22 | }
23 |
24 | dismissToast() {
25 | this.setState({ toastVisible: false });
26 | }
27 |
28 | render() {
29 | const { toastType, toastVisible, toastMessage } = this.state;
30 | return (
31 |
32 |
38 |
43 | {toastMessage}
44 |
45 |
46 | );
47 | }
48 | };
49 | }
50 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-13.10-search-bar/ui/webpack.serverHMR.js:
--------------------------------------------------------------------------------
1 | /*
2 | eslint-disable import/no-extraneous-dependencies
3 | */
4 | const webpack = require('webpack');
5 | const merge = require('webpack-merge');
6 | const serverConfig = require('./webpack.config.js')[1];
7 |
8 | module.exports = merge(serverConfig, {
9 | entry: { server: ['./node_modules/webpack/hot/poll?1000'] },
10 | plugins: [
11 | new webpack.HotModuleReplacementPlugin(),
12 | ],
13 | });
14 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-14.11-cookie-domain/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | package-lock.json
3 | ui/public/*.js
4 | ui/public/*.map
5 | .env
6 | ui/dist
7 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-14.11-cookie-domain/README.md:
--------------------------------------------------------------------------------
1 | # Pro MERN Stack - 2nd Edition
2 |
3 | You are browsing the source code at the end of one of the sections in the book.
4 |
5 | The project's README which contains the list of all chapters, sections
6 | their sources and other useful information can be found in the
7 | [master branch](https://github.com/vasansr/pro-mern-stack-2).
8 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-14.11-cookie-domain/api/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb-base",
3 | "env": {
4 | "node": "true"
5 | },
6 | rules: {
7 | "no-console": "off"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-14.11-cookie-domain/api/about.js:
--------------------------------------------------------------------------------
1 | const { mustBeSignedIn } = require('./auth.js');
2 |
3 | let aboutMessage = 'Issue Tracker API v1.0';
4 |
5 | function setMessage(_, { message }) {
6 | aboutMessage = message;
7 | return aboutMessage;
8 | }
9 |
10 | function getMessage() {
11 | return aboutMessage;
12 | }
13 |
14 | module.exports = { getMessage, setMessage: mustBeSignedIn(setMessage) };
15 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-14.11-cookie-domain/api/db.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config();
2 | const { MongoClient } = require('mongodb');
3 |
4 | let db;
5 |
6 | async function connectToDb() {
7 | const url = process.env.DB_URL || 'mongodb://localhost/issuetracker';
8 | const client = new MongoClient(url, { useNewUrlParser: true });
9 | await client.connect();
10 | console.log('Connected to MongoDB at', url);
11 | db = client.db();
12 | }
13 |
14 | async function getNextSequence(name) {
15 | const result = await db.collection('counters').findOneAndUpdate(
16 | { _id: name },
17 | { $inc: { current: 1 } },
18 | { returnOriginal: false },
19 | );
20 | return result.value.current;
21 | }
22 |
23 | function getDb() {
24 | return db;
25 | }
26 |
27 | module.exports = { connectToDb, getNextSequence, getDb };
28 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-14.11-cookie-domain/api/graphql_date.js:
--------------------------------------------------------------------------------
1 | const { GraphQLScalarType } = require('graphql');
2 | const { Kind } = require('graphql/language');
3 |
4 | const GraphQLDate = new GraphQLScalarType({
5 | name: 'GraphQLDate',
6 | description: 'A Date() type in GraphQL as a scalar',
7 | serialize(value) {
8 | return value.toISOString();
9 | },
10 | parseValue(value) {
11 | const dateValue = new Date(value);
12 | return Number.isNaN(dateValue.getTime()) ? undefined : dateValue;
13 | },
14 | parseLiteral(ast) {
15 | if (ast.kind === Kind.STRING) {
16 | const value = new Date(ast.value);
17 | return Number.isNaN(value.getTime()) ? undefined : value;
18 | }
19 | return undefined;
20 | },
21 | });
22 |
23 | module.exports = GraphQLDate;
24 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-14.11-cookie-domain/api/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pro-mern-stack-2-api",
3 | "version": "1.0.0",
4 | "description": "Pro MERN Stack (2nd Edition) API",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "nodemon -e js,graphql -w . -w .env server.js",
8 | "lint": "eslint .",
9 | "test": "echo \"Error: no test specified\" && exit 1"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git+https://github.com/vasansr/pro-mern-stack-2.git"
14 | },
15 | "author": "vasan.promern@gmail.com",
16 | "license": "ISC",
17 | "homepage": "https://github.com/vasansr/pro-mern-stack-2",
18 | "dependencies": {
19 | "apollo-server-express": "^2.3.1",
20 | "body-parser": "^1.18.3",
21 | "cookie-parser": "^1.4.3",
22 | "cors": "^2.8.5",
23 | "dotenv": "^6.2.0",
24 | "express": "^4.16.4",
25 | "google-auth-library": "^2.0.2",
26 | "graphql": "^0.13.2",
27 | "jsonwebtoken": "^8.4.0",
28 | "mongodb": "^3.1.10",
29 | "nodemon": "^1.18.9"
30 | },
31 | "devDependencies": {
32 | "eslint": "^5.12.0",
33 | "eslint-config-airbnb-base": "^13.1.0",
34 | "eslint-plugin-import": "^2.14.0"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-14.11-cookie-domain/api/sample.env:
--------------------------------------------------------------------------------
1 | ## DB
2 | # Local
3 | DB_URL=mongodb://localhost/issuetracker
4 |
5 | # Atlas - replace UUU: user, PPP: password, XXX: hostname
6 | # DB_URL=mongodb+srv://UUU:PPP@XXX.mongodb.net/issuetracker?retryWrites=true
7 |
8 | # mLab - replace UUU: user, PPP: password, XXX: hostname, YYY: port
9 | # DB_URL=mongodb://UUU:PPP@XXX.mlab.com:YYY/issuetracker
10 |
11 |
12 | ## Server Port
13 | API_SERVER_PORT=3000
14 |
15 | ## Enable CORS (default: true)
16 | # ENABLE_CORS=false
17 |
18 | UI_SERVER_ORIGIN=http://ui.promernstack.com:8000
19 |
20 | COOKIE_DOMAIN=promernstack.com
21 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-14.11-cookie-domain/api/scripts/generate_data.mongo.js:
--------------------------------------------------------------------------------
1 | /* global db print */
2 | /* eslint no-restricted-globals: "off" */
3 |
4 | const owners = ['Ravan', 'Eddie', 'Pieta', 'Parvati', 'Victor'];
5 | const statuses = ['New', 'Assigned', 'Fixed', 'Closed'];
6 |
7 | const initialCount = db.issues.count();
8 |
9 | for (let i = 0; i < 100; i += 1) {
10 | const randomCreatedDate = (new Date())
11 | - Math.floor(Math.random() * 60) * 1000 * 60 * 60 * 24;
12 | const created = new Date(randomCreatedDate);
13 | const randomDueDate = (new Date())
14 | - Math.floor(Math.random() * 60) * 1000 * 60 * 60 * 24;
15 | const due = new Date(randomDueDate);
16 |
17 | const owner = owners[Math.floor(Math.random() * 5)];
18 | const status = statuses[Math.floor(Math.random() * 4)];
19 | const effort = Math.ceil(Math.random() * 20);
20 | const title = `Lorem ipsum dolor sit amet, ${i}`;
21 | const id = initialCount + i + 1;
22 |
23 | const issue = {
24 | id, title, created, due, owner, status, effort,
25 | };
26 |
27 | db.issues.insertOne(issue);
28 | }
29 |
30 | const count = db.issues.count();
31 | db.counters.update({ _id: 'issues' }, { $set: { current: count } });
32 |
33 | print('New issue count:', count);
34 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-14.11-cookie-domain/api/server.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config();
2 | const express = require('express');
3 | const cookieParser = require('cookie-parser');
4 |
5 | const { connectToDb } = require('./db.js');
6 | const { installHandler } = require('./api_handler.js');
7 | const auth = require('./auth.js');
8 |
9 | const app = express();
10 |
11 | app.use(cookieParser());
12 | app.use('/auth', auth.routes);
13 |
14 | installHandler(app);
15 |
16 | const port = process.env.API_SERVER_PORT || 3000;
17 |
18 | (async function start() {
19 | try {
20 | await connectToDb();
21 | app.listen(port, () => {
22 | console.log(`API server started on port ${port}`);
23 | });
24 | } catch (err) {
25 | console.log('ERROR:', err);
26 | }
27 | }());
28 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-14.11-cookie-domain/ui/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb",
3 | }
4 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-14.11-cookie-domain/ui/browser/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true
4 | },
5 | rules: {
6 | "import/extensions": [ 'error', 'always', { ignorePackages: true } ],
7 | "react/prop-types": "off"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-14.11-cookie-domain/ui/browser/App.jsx:
--------------------------------------------------------------------------------
1 | import 'babel-polyfill';
2 | import React from 'react';
3 | import ReactDOM from 'react-dom';
4 | import { BrowserRouter as Router } from 'react-router-dom';
5 |
6 | import Page from '../src/Page.jsx';
7 | import store from '../src/store.js';
8 |
9 | /* eslint-disable no-underscore-dangle */
10 | store.initialData = window.__INITIAL_DATA__;
11 | store.userData = window.__USER_DATA__;
12 |
13 | const element = (
14 |
15 |
16 |
17 | );
18 |
19 | ReactDOM.hydrate(element, document.getElementById('contents'));
20 |
21 | if (module.hot) {
22 | module.hot.accept();
23 | }
24 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-14.11-cookie-domain/ui/public/bootstrap:
--------------------------------------------------------------------------------
1 | ../node_modules/bootstrap/dist
--------------------------------------------------------------------------------
/pro-mern-stack-2-14.11-cookie-domain/ui/sample.env:
--------------------------------------------------------------------------------
1 | UI_SERVER_PORT=8000
2 | # ENABLE_HMR=true
3 | GOOGLE_CLIENT_ID=YOUR_CLIENT_ID.apps.googleusercontent.com
4 |
5 | # Regular config
6 | # UI_API_ENDPOINT=http://localhost:3000/graphql
7 | # UI_AUTH_ENDPOINT=http://localhost:3000/auth
8 |
9 | # Regular config with domains
10 | UI_API_ENDPOINT=http://api.promernstack.com:3000/graphql
11 | UI_AUTH_ENDPOINT=http://api.promernstack.com:3000/auth
12 |
13 | # Proxy Config
14 | # UI_API_ENDPOINT=http://localhost:8000/graphql
15 | # UI_AUTH_ENDPOINT=http://localhost:8000/auth
16 | # API_PROXY_TARGET=http://localhost:3000
17 | # UI_SERVER_API_ENDPOINT=http://localhost:3000/graphql
18 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-14.11-cookie-domain/ui/server/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "node": true
4 | },
5 | "rules": {
6 | "no-console": "off",
7 | "import/extensions": "off"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-14.11-cookie-domain/ui/server/render.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOMServer from 'react-dom/server';
3 | import { StaticRouter, matchPath } from 'react-router-dom';
4 |
5 | import Page from '../src/Page.jsx';
6 | import template from './template.js';
7 | import store from '../src/store.js';
8 | import routes from '../src/routes.js';
9 |
10 | async function render(req, res) {
11 | const activeRoute = routes.find(
12 | route => matchPath(req.path, route),
13 | );
14 |
15 | let initialData;
16 | if (activeRoute && activeRoute.component.fetchData) {
17 | const match = matchPath(req.path, activeRoute);
18 | const index = req.url.indexOf('?');
19 | const search = index !== -1 ? req.url.substr(index) : null;
20 | initialData = await activeRoute.component
21 | .fetchData(match, search, req.headers.cookie);
22 | }
23 |
24 | const userData = await Page.fetchData(req.headers.cookie);
25 |
26 | store.initialData = initialData;
27 | store.userData = userData;
28 |
29 | const context = {};
30 | const element = (
31 |
32 |
33 |
34 | );
35 | const body = ReactDOMServer.renderToString(element);
36 |
37 | if (context.url) {
38 | res.redirect(301, context.url);
39 | } else {
40 | res.send(template(body, initialData, userData));
41 | }
42 | }
43 |
44 | export default render;
45 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-14.11-cookie-domain/ui/server/template.js:
--------------------------------------------------------------------------------
1 | import serialize from 'serialize-javascript';
2 |
3 | export default function template(body, initialData, userData) {
4 | return `
5 |
6 |
7 |
8 |
9 | Pro MERN Stack
10 |
11 |
12 |
13 |
14 |
15 |
19 |
20 |
21 |
22 |
23 | ${body}
24 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | `;
36 | }
37 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-14.11-cookie-domain/ui/src/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "node": true
5 | },
6 | "rules": {
7 | "import/extensions": [ "error", "always", { "ignorePackages": true } ],
8 | "react/prop-types": "off"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-14.11-cookie-domain/ui/src/About.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import store from './store.js';
3 | import graphQLFetch from './graphQLFetch.js';
4 |
5 | export default class About extends React.Component {
6 | static async fetchData() {
7 | const data = await graphQLFetch('query {about}');
8 | return data;
9 | }
10 |
11 | constructor(props) {
12 | super(props);
13 | const apiAbout = store.initialData ? store.initialData.about : null;
14 | delete store.initialData;
15 | this.state = { apiAbout };
16 | }
17 |
18 | async componentDidMount() {
19 | const { apiAbout } = this.state;
20 | if (apiAbout == null) {
21 | const data = await About.fetchData();
22 | this.setState({ apiAbout: data.about });
23 | }
24 | }
25 |
26 | render() {
27 | const { apiAbout } = this.state;
28 | return (
29 |
30 |
Issue Tracker version 0.9
31 |
32 | {apiAbout}
33 |
34 |
35 | );
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-14.11-cookie-domain/ui/src/Contents.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Switch, Route, Redirect } from 'react-router-dom';
3 |
4 | import routes from './routes.js';
5 |
6 | export default function Contents() {
7 | return (
8 |
9 |
10 | {routes.map(attrs => )}
11 |
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-14.11-cookie-domain/ui/src/IssueDetail.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default function IssueDetail({ issue }) {
4 | if (issue) {
5 | return (
6 |
7 |
Description
8 |
{issue.description}
9 |
10 | );
11 | }
12 | return null;
13 | }
14 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-14.11-cookie-domain/ui/src/NotFound.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function NotFound() {
4 | return Page Not Found
;
5 | }
6 |
7 | export default NotFound;
8 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-14.11-cookie-domain/ui/src/NumInput.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function format(num) {
4 | return num != null ? num.toString() : '';
5 | }
6 |
7 | function unformat(str) {
8 | const val = parseInt(str, 10);
9 | return Number.isNaN(val) ? null : val;
10 | }
11 |
12 | export default class NumInput extends React.Component {
13 | constructor(props) {
14 | super(props);
15 | this.state = { value: format(props.value) };
16 | this.onBlur = this.onBlur.bind(this);
17 | this.onChange = this.onChange.bind(this);
18 | }
19 |
20 | onChange(e) {
21 | if (e.target.value.match(/^\d*$/)) {
22 | this.setState({ value: e.target.value });
23 | }
24 | }
25 |
26 | onBlur(e) {
27 | const { onChange } = this.props;
28 | const { value } = this.state;
29 | onChange(e, unformat(value));
30 | }
31 |
32 | render() {
33 | const { value } = this.state;
34 | return (
35 |
42 | );
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-14.11-cookie-domain/ui/src/Search.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import SelectAsync from 'react-select/lib/Async'; // eslint-disable-line
3 | import { withRouter } from 'react-router-dom';
4 |
5 | import graphQLFetch from './graphQLFetch.js';
6 | import withToast from './withToast.jsx';
7 |
8 | class Search extends React.Component {
9 | constructor(props) {
10 | super(props);
11 |
12 | this.onChangeSelection = this.onChangeSelection.bind(this);
13 | this.loadOptions = this.loadOptions.bind(this);
14 | }
15 |
16 | onChangeSelection({ value }) {
17 | const { history } = this.props;
18 | history.push(`/edit/${value}`);
19 | }
20 |
21 | async loadOptions(term) {
22 | if (term.length < 3) return [];
23 | const query = `query issueList($search: String) {
24 | issueList(search: $search) {
25 | issues {id title}
26 | }
27 | }`;
28 |
29 | const { showError } = this.props;
30 | const data = await graphQLFetch(query, { search: term }, showError);
31 | return data.issueList.issues.map(issue => ({
32 | label: `#${issue.id}: ${issue.title}`, value: issue.id,
33 | }));
34 | }
35 |
36 | render() {
37 | return (
38 | true}
43 | onChange={this.onChangeSelection}
44 | components={{ DropdownIndicator: null }}
45 | />
46 | );
47 | }
48 | }
49 |
50 | export default withRouter(withToast(Search));
51 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-14.11-cookie-domain/ui/src/TextInput.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function format(text) {
4 | return text != null ? text : '';
5 | }
6 |
7 | function unformat(text) {
8 | return text.trim().length === 0 ? null : text;
9 | }
10 |
11 | export default class TextInput extends React.Component {
12 | constructor(props) {
13 | super(props);
14 | this.state = { value: format(props.value) };
15 | this.onBlur = this.onBlur.bind(this);
16 | this.onChange = this.onChange.bind(this);
17 | }
18 |
19 | onChange(e) {
20 | this.setState({ value: e.target.value });
21 | }
22 |
23 | onBlur(e) {
24 | const { onChange } = this.props;
25 | const { value } = this.state;
26 | onChange(e, unformat(value));
27 | }
28 |
29 | render() {
30 | const { value } = this.state;
31 | const { tag = 'input', ...props } = this.props;
32 | return React.createElement(tag, {
33 | ...props,
34 | value,
35 | onBlur: this.onBlur,
36 | onChange: this.onChange,
37 | });
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-14.11-cookie-domain/ui/src/Toast.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Alert, Collapse } from 'react-bootstrap';
3 |
4 | export default class Toast extends React.Component {
5 | componentDidUpdate() {
6 | const { showing, onDismiss } = this.props;
7 | if (showing) {
8 | clearTimeout(this.dismissTimer);
9 | this.dismissTimer = setTimeout(onDismiss, 5000);
10 | }
11 | }
12 |
13 | componentWillUnmount() {
14 | clearTimeout(this.dismissTimer);
15 | }
16 |
17 | render() {
18 | const {
19 | showing, bsStyle, onDismiss, children,
20 | } = this.props;
21 | return (
22 |
23 |
27 |
28 | {children}
29 |
30 |
31 |
32 | );
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-14.11-cookie-domain/ui/src/UserContext.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const UserContext = React.createContext({
4 | signedIn: false,
5 | });
6 |
7 | export default UserContext;
8 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-14.11-cookie-domain/ui/src/graphQLFetch.js:
--------------------------------------------------------------------------------
1 | import fetch from 'isomorphic-fetch';
2 |
3 | const dateRegex = new RegExp('^\\d\\d\\d\\d-\\d\\d-\\d\\d');
4 |
5 | function jsonDateReviver(key, value) {
6 | if (dateRegex.test(value)) return new Date(value);
7 | return value;
8 | }
9 |
10 | export default async function
11 | graphQLFetch(query, variables = {}, showError = null, cookie = null) {
12 | const apiEndpoint = (__isBrowser__) // eslint-disable-line no-undef
13 | ? window.ENV.UI_API_ENDPOINT
14 | : process.env.UI_SERVER_API_ENDPOINT;
15 | try {
16 | const headers = { 'Content-Type': 'application/json' };
17 | if (cookie) headers.Cookie = cookie;
18 | const response = await fetch(apiEndpoint, {
19 | method: 'POST',
20 | credentials: 'include',
21 | headers,
22 | body: JSON.stringify({ query, variables }),
23 | });
24 | const body = await response.text();
25 | const result = JSON.parse(body, jsonDateReviver);
26 |
27 | if (result.errors) {
28 | const error = result.errors[0];
29 | if (error.extensions.code === 'BAD_USER_INPUT') {
30 | const details = error.extensions.exception.errors.join('\n ');
31 | if (showError) showError(`${error.message}:\n ${details}`);
32 | } else if (showError) {
33 | showError(`${error.extensions.code}: ${error.message}`);
34 | }
35 | }
36 | return result.data;
37 | } catch (e) {
38 | if (showError) showError(`Error in sending data to server: ${e.message}`);
39 | return null;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-14.11-cookie-domain/ui/src/routes.js:
--------------------------------------------------------------------------------
1 | import IssueList from './IssueList.jsx';
2 | import IssueReport from './IssueReport.jsx';
3 | import IssueEdit from './IssueEdit.jsx';
4 | import About from './About.jsx';
5 | import NotFound from './NotFound.jsx';
6 |
7 | const routes = [
8 | { path: '/issues/:id?', component: IssueList },
9 | { path: '/edit/:id', component: IssueEdit },
10 | { path: '/report', component: IssueReport },
11 | { path: '/about', component: About },
12 | { path: '*', component: NotFound },
13 | ];
14 |
15 | export default routes;
16 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-14.11-cookie-domain/ui/src/store.js:
--------------------------------------------------------------------------------
1 | const store = {};
2 |
3 | export default store;
4 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-14.11-cookie-domain/ui/src/withToast.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Toast from './Toast.jsx';
3 |
4 | export default function withToast(OriginalComponent) {
5 | return class ToastWrapper extends React.Component {
6 | constructor(props) {
7 | super(props);
8 | this.state = {
9 | toastVisible: false, toastMessage: '', toastType: 'success',
10 | };
11 | this.showSuccess = this.showSuccess.bind(this);
12 | this.showError = this.showError.bind(this);
13 | this.dismissToast = this.dismissToast.bind(this);
14 | }
15 |
16 | showSuccess(message) {
17 | this.setState({ toastVisible: true, toastMessage: message, toastType: 'success' });
18 | }
19 |
20 | showError(message) {
21 | this.setState({ toastVisible: true, toastMessage: message, toastType: 'danger' });
22 | }
23 |
24 | dismissToast() {
25 | this.setState({ toastVisible: false });
26 | }
27 |
28 | render() {
29 | const { toastType, toastVisible, toastMessage } = this.state;
30 | return (
31 |
32 |
38 |
43 | {toastMessage}
44 |
45 |
46 | );
47 | }
48 | };
49 | }
50 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-14.11-cookie-domain/ui/webpack.serverHMR.js:
--------------------------------------------------------------------------------
1 | /*
2 | eslint-disable import/no-extraneous-dependencies
3 | */
4 | const webpack = require('webpack');
5 | const merge = require('webpack-merge');
6 | const serverConfig = require('./webpack.config.js')[1];
7 |
8 | module.exports = merge(serverConfig, {
9 | entry: { server: ['./node_modules/webpack/hot/poll?1000'] },
10 | plugins: [
11 | new webpack.HotModuleReplacementPlugin(),
12 | ],
13 | });
14 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-15.07-non-proxy-mode/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | package-lock.json
3 | ui/public/*.js
4 | ui/public/*.map
5 | .env
6 | ui/dist
7 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-15.07-non-proxy-mode/README.md:
--------------------------------------------------------------------------------
1 | # Pro MERN Stack - 2nd Edition
2 |
3 | You are browsing the source code at the end of one of the sections in the book.
4 |
5 | The project's README which contains the list of all chapters, sections
6 | their sources and other useful information can be found in the
7 | [master branch](https://github.com/vasansr/pro-mern-stack-2).
8 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-15.07-non-proxy-mode/api/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb-base",
3 | "env": {
4 | "node": "true"
5 | },
6 | rules: {
7 | "no-console": "off"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-15.07-non-proxy-mode/api/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .env
3 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-15.07-non-proxy-mode/api/about.js:
--------------------------------------------------------------------------------
1 | const { mustBeSignedIn } = require('./auth.js');
2 |
3 | let aboutMessage = 'Issue Tracker API v1.0';
4 |
5 | function setMessage(_, { message }) {
6 | aboutMessage = message;
7 | return aboutMessage;
8 | }
9 |
10 | function getMessage() {
11 | return aboutMessage;
12 | }
13 |
14 | module.exports = { getMessage, setMessage: mustBeSignedIn(setMessage) };
15 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-15.07-non-proxy-mode/api/db.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config();
2 | const { MongoClient } = require('mongodb');
3 |
4 | let db;
5 |
6 | async function connectToDb() {
7 | const url = process.env.DB_URL || 'mongodb://localhost/issuetracker';
8 | const client = new MongoClient(url, { useNewUrlParser: true });
9 | await client.connect();
10 | console.log('Connected to MongoDB at', url);
11 | db = client.db();
12 | }
13 |
14 | async function getNextSequence(name) {
15 | const result = await db.collection('counters').findOneAndUpdate(
16 | { _id: name },
17 | { $inc: { current: 1 } },
18 | { returnOriginal: false },
19 | );
20 | return result.value.current;
21 | }
22 |
23 | function getDb() {
24 | return db;
25 | }
26 |
27 | module.exports = { connectToDb, getNextSequence, getDb };
28 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-15.07-non-proxy-mode/api/graphql_date.js:
--------------------------------------------------------------------------------
1 | const { GraphQLScalarType } = require('graphql');
2 | const { Kind } = require('graphql/language');
3 |
4 | const GraphQLDate = new GraphQLScalarType({
5 | name: 'GraphQLDate',
6 | description: 'A Date() type in GraphQL as a scalar',
7 | serialize(value) {
8 | return value.toISOString();
9 | },
10 | parseValue(value) {
11 | const dateValue = new Date(value);
12 | return Number.isNaN(dateValue.getTime()) ? undefined : dateValue;
13 | },
14 | parseLiteral(ast) {
15 | if (ast.kind === Kind.STRING) {
16 | const value = new Date(ast.value);
17 | return Number.isNaN(value.getTime()) ? undefined : value;
18 | }
19 | return undefined;
20 | },
21 | });
22 |
23 | module.exports = GraphQLDate;
24 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-15.07-non-proxy-mode/api/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pro-mern-stack-2-api",
3 | "version": "1.0.0",
4 | "description": "Pro MERN Stack (2nd Edition) API",
5 | "main": "index.js",
6 | "engines": {
7 | "node": "10.x",
8 | "npm": "6.x"
9 | },
10 | "scripts": {
11 | "start": "nodemon -e js,graphql -w . -w .env server.js",
12 | "lint": "eslint .",
13 | "test": "echo \"Error: no test specified\" && exit 1"
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "git+https://github.com/vasansr/pro-mern-stack-2.git"
18 | },
19 | "author": "vasan.promern@gmail.com",
20 | "license": "ISC",
21 | "homepage": "https://github.com/vasansr/pro-mern-stack-2",
22 | "dependencies": {
23 | "apollo-server-express": "^2.3.1",
24 | "body-parser": "^1.18.3",
25 | "cookie-parser": "^1.4.3",
26 | "cors": "^2.8.5",
27 | "dotenv": "^6.2.0",
28 | "express": "^4.16.4",
29 | "google-auth-library": "^2.0.2",
30 | "graphql": "^0.13.2",
31 | "jsonwebtoken": "^8.4.0",
32 | "mongodb": "^3.1.10",
33 | "nodemon": "^1.18.9"
34 | },
35 | "devDependencies": {
36 | "eslint": "^5.12.0",
37 | "eslint-config-airbnb-base": "^13.1.0",
38 | "eslint-plugin-import": "^2.14.0"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-15.07-non-proxy-mode/api/sample.env:
--------------------------------------------------------------------------------
1 | ## DB
2 | # Local
3 | DB_URL=mongodb://localhost/issuetracker
4 |
5 | # Atlas - replace UUU: user, PPP: password, XXX: hostname
6 | # DB_URL=mongodb+srv://UUU:PPP@XXX.mongodb.net/issuetracker?retryWrites=true
7 |
8 | # mLab - replace UUU: user, PPP: password, XXX: hostname, YYY: port
9 | # DB_URL=mongodb://UUU:PPP@XXX.mlab.com:YYY/issuetracker
10 |
11 |
12 | ## Server Port
13 | PORT=3000
14 |
15 | ## Enable CORS (default: true)
16 | # ENABLE_CORS=false
17 |
18 | UI_SERVER_ORIGIN=http://ui.promernstack.com:8000
19 |
20 | COOKIE_DOMAIN=promernstack.com
21 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-15.07-non-proxy-mode/api/scripts/generate_data.mongo.js:
--------------------------------------------------------------------------------
1 | /* global db print */
2 | /* eslint no-restricted-globals: "off" */
3 |
4 | const owners = ['Ravan', 'Eddie', 'Pieta', 'Parvati', 'Victor'];
5 | const statuses = ['New', 'Assigned', 'Fixed', 'Closed'];
6 |
7 | const initialCount = db.issues.count();
8 |
9 | for (let i = 0; i < 100; i += 1) {
10 | const randomCreatedDate = (new Date())
11 | - Math.floor(Math.random() * 60) * 1000 * 60 * 60 * 24;
12 | const created = new Date(randomCreatedDate);
13 | const randomDueDate = (new Date())
14 | - Math.floor(Math.random() * 60) * 1000 * 60 * 60 * 24;
15 | const due = new Date(randomDueDate);
16 |
17 | const owner = owners[Math.floor(Math.random() * 5)];
18 | const status = statuses[Math.floor(Math.random() * 4)];
19 | const effort = Math.ceil(Math.random() * 20);
20 | const title = `Lorem ipsum dolor sit amet, ${i}`;
21 | const id = initialCount + i + 1;
22 |
23 | const issue = {
24 | id, title, created, due, owner, status, effort,
25 | };
26 |
27 | db.issues.insertOne(issue);
28 | }
29 |
30 | const count = db.issues.count();
31 | db.counters.update({ _id: 'issues' }, { $set: { current: count } });
32 |
33 | print('New issue count:', count);
34 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-15.07-non-proxy-mode/api/server.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config();
2 | const express = require('express');
3 | const cookieParser = require('cookie-parser');
4 |
5 | const { connectToDb } = require('./db.js');
6 | const { installHandler } = require('./api_handler.js');
7 | const auth = require('./auth.js');
8 |
9 | const app = express();
10 |
11 | app.use(cookieParser());
12 | app.use('/auth', auth.routes);
13 |
14 | installHandler(app);
15 |
16 | const port = process.env.PORT || 3000;
17 |
18 | (async function start() {
19 | try {
20 | await connectToDb();
21 | app.listen(port, () => {
22 | console.log(`API server started on port ${port}`);
23 | });
24 | } catch (err) {
25 | console.log('ERROR:', err);
26 | }
27 | }());
28 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-15.07-non-proxy-mode/ui/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb",
3 | }
4 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-15.07-non-proxy-mode/ui/.gitignore:
--------------------------------------------------------------------------------
1 | dist
2 | node_modules
3 | .env
4 | public/*.js
5 | public/*.js.map
6 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-15.07-non-proxy-mode/ui/browser/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true
4 | },
5 | rules: {
6 | "import/extensions": [ 'error', 'always', { ignorePackages: true } ],
7 | "react/prop-types": "off"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-15.07-non-proxy-mode/ui/browser/App.jsx:
--------------------------------------------------------------------------------
1 | import 'babel-polyfill';
2 | import React from 'react';
3 | import ReactDOM from 'react-dom';
4 | import { BrowserRouter as Router } from 'react-router-dom';
5 |
6 | import Page from '../src/Page.jsx';
7 | import store from '../src/store.js';
8 |
9 | /* eslint-disable no-underscore-dangle */
10 | store.initialData = window.__INITIAL_DATA__;
11 | store.userData = window.__USER_DATA__;
12 |
13 | const element = (
14 |
15 |
16 |
17 | );
18 |
19 | ReactDOM.hydrate(element, document.getElementById('contents'));
20 |
21 | if (module.hot) {
22 | module.hot.accept();
23 | }
24 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-15.07-non-proxy-mode/ui/public/bootstrap:
--------------------------------------------------------------------------------
1 | ../node_modules/bootstrap/dist
--------------------------------------------------------------------------------
/pro-mern-stack-2-15.07-non-proxy-mode/ui/sample.env:
--------------------------------------------------------------------------------
1 | PORT=8000
2 | # ENABLE_HMR=true
3 | GOOGLE_CLIENT_ID=YOUR_CLIENT_ID.apps.googleusercontent.com
4 |
5 | # Regular config
6 | # UI_API_ENDPOINT=http://localhost:3000/graphql
7 | # UI_AUTH_ENDPOINT=http://localhost:3000/auth
8 |
9 | # Regular config with domains
10 | UI_API_ENDPOINT=http://api.promernstack.com:3000/graphql
11 | UI_AUTH_ENDPOINT=http://api.promernstack.com:3000/auth
12 |
13 | # Proxy Config
14 | # UI_API_ENDPOINT=http://localhost:8000/graphql
15 | # UI_AUTH_ENDPOINT=http://localhost:8000/auth
16 | # API_PROXY_TARGET=http://localhost:3000
17 | # UI_SERVER_API_ENDPOINT=http://localhost:3000/graphql
18 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-15.07-non-proxy-mode/ui/server/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "node": true
4 | },
5 | "rules": {
6 | "no-console": "off",
7 | "import/extensions": "off"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-15.07-non-proxy-mode/ui/server/render.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOMServer from 'react-dom/server';
3 | import { StaticRouter, matchPath } from 'react-router-dom';
4 |
5 | import Page from '../src/Page.jsx';
6 | import template from './template.js';
7 | import store from '../src/store.js';
8 | import routes from '../src/routes.js';
9 |
10 | async function render(req, res) {
11 | const activeRoute = routes.find(
12 | route => matchPath(req.path, route),
13 | );
14 |
15 | let initialData;
16 | if (activeRoute && activeRoute.component.fetchData) {
17 | const match = matchPath(req.path, activeRoute);
18 | const index = req.url.indexOf('?');
19 | const search = index !== -1 ? req.url.substr(index) : null;
20 | initialData = await activeRoute.component
21 | .fetchData(match, search, req.headers.cookie);
22 | }
23 |
24 | const userData = await Page.fetchData(req.headers.cookie);
25 |
26 | store.initialData = initialData;
27 | store.userData = userData;
28 |
29 | const context = {};
30 | const element = (
31 |
32 |
33 |
34 | );
35 | const body = ReactDOMServer.renderToString(element);
36 |
37 | if (context.url) {
38 | res.redirect(301, context.url);
39 | } else {
40 | res.send(template(body, initialData, userData));
41 | }
42 | }
43 |
44 | export default render;
45 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-15.07-non-proxy-mode/ui/server/template.js:
--------------------------------------------------------------------------------
1 | import serialize from 'serialize-javascript';
2 |
3 | export default function template(body, initialData, userData) {
4 | return `
5 |
6 |
7 |
8 |
9 | Pro MERN Stack
10 |
11 |
12 |
13 |
14 |
15 |
19 |
20 |
21 |
22 |
23 | ${body}
24 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | `;
36 | }
37 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-15.07-non-proxy-mode/ui/src/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "node": true
5 | },
6 | "rules": {
7 | "import/extensions": [ "error", "always", { "ignorePackages": true } ],
8 | "react/prop-types": "off"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-15.07-non-proxy-mode/ui/src/About.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import store from './store.js';
3 | import graphQLFetch from './graphQLFetch.js';
4 |
5 | export default class About extends React.Component {
6 | static async fetchData() {
7 | const data = await graphQLFetch('query {about}');
8 | return data;
9 | }
10 |
11 | constructor(props) {
12 | super(props);
13 | const apiAbout = store.initialData ? store.initialData.about : null;
14 | delete store.initialData;
15 | this.state = { apiAbout };
16 | }
17 |
18 | async componentDidMount() {
19 | const { apiAbout } = this.state;
20 | if (apiAbout == null) {
21 | const data = await About.fetchData();
22 | this.setState({ apiAbout: data.about });
23 | }
24 | }
25 |
26 | render() {
27 | const { apiAbout } = this.state;
28 | return (
29 |
30 |
Issue Tracker version 0.9
31 |
32 | {apiAbout}
33 |
34 |
35 | );
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-15.07-non-proxy-mode/ui/src/Contents.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Switch, Route, Redirect } from 'react-router-dom';
3 |
4 | import routes from './routes.js';
5 |
6 | export default function Contents() {
7 | return (
8 |
9 |
10 | {routes.map(attrs => )}
11 |
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-15.07-non-proxy-mode/ui/src/IssueDetail.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default function IssueDetail({ issue }) {
4 | if (issue) {
5 | return (
6 |
7 |
Description
8 |
{issue.description}
9 |
10 | );
11 | }
12 | return null;
13 | }
14 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-15.07-non-proxy-mode/ui/src/NotFound.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function NotFound() {
4 | return Page Not Found
;
5 | }
6 |
7 | export default NotFound;
8 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-15.07-non-proxy-mode/ui/src/NumInput.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function format(num) {
4 | return num != null ? num.toString() : '';
5 | }
6 |
7 | function unformat(str) {
8 | const val = parseInt(str, 10);
9 | return Number.isNaN(val) ? null : val;
10 | }
11 |
12 | export default class NumInput extends React.Component {
13 | constructor(props) {
14 | super(props);
15 | this.state = { value: format(props.value) };
16 | this.onBlur = this.onBlur.bind(this);
17 | this.onChange = this.onChange.bind(this);
18 | }
19 |
20 | onChange(e) {
21 | if (e.target.value.match(/^\d*$/)) {
22 | this.setState({ value: e.target.value });
23 | }
24 | }
25 |
26 | onBlur(e) {
27 | const { onChange } = this.props;
28 | const { value } = this.state;
29 | onChange(e, unformat(value));
30 | }
31 |
32 | render() {
33 | const { value } = this.state;
34 | return (
35 |
42 | );
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-15.07-non-proxy-mode/ui/src/Search.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import SelectAsync from 'react-select/lib/Async'; // eslint-disable-line
3 | import { withRouter } from 'react-router-dom';
4 |
5 | import graphQLFetch from './graphQLFetch.js';
6 | import withToast from './withToast.jsx';
7 |
8 | class Search extends React.Component {
9 | constructor(props) {
10 | super(props);
11 |
12 | this.onChangeSelection = this.onChangeSelection.bind(this);
13 | this.loadOptions = this.loadOptions.bind(this);
14 | }
15 |
16 | onChangeSelection({ value }) {
17 | const { history } = this.props;
18 | history.push(`/edit/${value}`);
19 | }
20 |
21 | async loadOptions(term) {
22 | if (term.length < 3) return [];
23 | const query = `query issueList($search: String) {
24 | issueList(search: $search) {
25 | issues {id title}
26 | }
27 | }`;
28 |
29 | const { showError } = this.props;
30 | const data = await graphQLFetch(query, { search: term }, showError);
31 | return data.issueList.issues.map(issue => ({
32 | label: `#${issue.id}: ${issue.title}`, value: issue.id,
33 | }));
34 | }
35 |
36 | render() {
37 | return (
38 | true}
43 | onChange={this.onChangeSelection}
44 | components={{ DropdownIndicator: null }}
45 | />
46 | );
47 | }
48 | }
49 |
50 | export default withRouter(withToast(Search));
51 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-15.07-non-proxy-mode/ui/src/TextInput.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function format(text) {
4 | return text != null ? text : '';
5 | }
6 |
7 | function unformat(text) {
8 | return text.trim().length === 0 ? null : text;
9 | }
10 |
11 | export default class TextInput extends React.Component {
12 | constructor(props) {
13 | super(props);
14 | this.state = { value: format(props.value) };
15 | this.onBlur = this.onBlur.bind(this);
16 | this.onChange = this.onChange.bind(this);
17 | }
18 |
19 | onChange(e) {
20 | this.setState({ value: e.target.value });
21 | }
22 |
23 | onBlur(e) {
24 | const { onChange } = this.props;
25 | const { value } = this.state;
26 | onChange(e, unformat(value));
27 | }
28 |
29 | render() {
30 | const { value } = this.state;
31 | const { tag = 'input', ...props } = this.props;
32 | return React.createElement(tag, {
33 | ...props,
34 | value,
35 | onBlur: this.onBlur,
36 | onChange: this.onChange,
37 | });
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-15.07-non-proxy-mode/ui/src/Toast.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Alert, Collapse } from 'react-bootstrap';
3 |
4 | export default class Toast extends React.Component {
5 | componentDidUpdate() {
6 | const { showing, onDismiss } = this.props;
7 | if (showing) {
8 | clearTimeout(this.dismissTimer);
9 | this.dismissTimer = setTimeout(onDismiss, 5000);
10 | }
11 | }
12 |
13 | componentWillUnmount() {
14 | clearTimeout(this.dismissTimer);
15 | }
16 |
17 | render() {
18 | const {
19 | showing, bsStyle, onDismiss, children,
20 | } = this.props;
21 | return (
22 |
23 |
27 |
28 | {children}
29 |
30 |
31 |
32 | );
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-15.07-non-proxy-mode/ui/src/UserContext.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const UserContext = React.createContext({
4 | signedIn: false,
5 | });
6 |
7 | export default UserContext;
8 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-15.07-non-proxy-mode/ui/src/graphQLFetch.js:
--------------------------------------------------------------------------------
1 | import fetch from 'isomorphic-fetch';
2 |
3 | const dateRegex = new RegExp('^\\d\\d\\d\\d-\\d\\d-\\d\\d');
4 |
5 | function jsonDateReviver(key, value) {
6 | if (dateRegex.test(value)) return new Date(value);
7 | return value;
8 | }
9 |
10 | export default async function
11 | graphQLFetch(query, variables = {}, showError = null, cookie = null) {
12 | const apiEndpoint = (__isBrowser__) // eslint-disable-line no-undef
13 | ? window.ENV.UI_API_ENDPOINT
14 | : process.env.UI_SERVER_API_ENDPOINT;
15 | try {
16 | const headers = { 'Content-Type': 'application/json' };
17 | if (cookie) headers.Cookie = cookie;
18 | const response = await fetch(apiEndpoint, {
19 | method: 'POST',
20 | credentials: 'include',
21 | headers,
22 | body: JSON.stringify({ query, variables }),
23 | });
24 | const body = await response.text();
25 | const result = JSON.parse(body, jsonDateReviver);
26 |
27 | if (result.errors) {
28 | const error = result.errors[0];
29 | if (error.extensions.code === 'BAD_USER_INPUT') {
30 | const details = error.extensions.exception.errors.join('\n ');
31 | if (showError) showError(`${error.message}:\n ${details}`);
32 | } else if (showError) {
33 | showError(`${error.extensions.code}: ${error.message}`);
34 | }
35 | }
36 | return result.data;
37 | } catch (e) {
38 | if (showError) showError(`Error in sending data to server: ${e.message}`);
39 | return null;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-15.07-non-proxy-mode/ui/src/routes.js:
--------------------------------------------------------------------------------
1 | import IssueList from './IssueList.jsx';
2 | import IssueReport from './IssueReport.jsx';
3 | import IssueEdit from './IssueEdit.jsx';
4 | import About from './About.jsx';
5 | import NotFound from './NotFound.jsx';
6 |
7 | const routes = [
8 | { path: '/issues/:id?', component: IssueList },
9 | { path: '/edit/:id', component: IssueEdit },
10 | { path: '/report', component: IssueReport },
11 | { path: '/about', component: About },
12 | { path: '*', component: NotFound },
13 | ];
14 |
15 | export default routes;
16 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-15.07-non-proxy-mode/ui/src/store.js:
--------------------------------------------------------------------------------
1 | const store = {};
2 |
3 | export default store;
4 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-15.07-non-proxy-mode/ui/src/withToast.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Toast from './Toast.jsx';
3 |
4 | export default function withToast(OriginalComponent) {
5 | return class ToastWrapper extends React.Component {
6 | constructor(props) {
7 | super(props);
8 | this.state = {
9 | toastVisible: false, toastMessage: '', toastType: 'success',
10 | };
11 | this.showSuccess = this.showSuccess.bind(this);
12 | this.showError = this.showError.bind(this);
13 | this.dismissToast = this.dismissToast.bind(this);
14 | }
15 |
16 | showSuccess(message) {
17 | this.setState({ toastVisible: true, toastMessage: message, toastType: 'success' });
18 | }
19 |
20 | showError(message) {
21 | this.setState({ toastVisible: true, toastMessage: message, toastType: 'danger' });
22 | }
23 |
24 | dismissToast() {
25 | this.setState({ toastVisible: false });
26 | }
27 |
28 | render() {
29 | const { toastType, toastVisible, toastMessage } = this.state;
30 | return (
31 |
32 |
38 |
43 | {toastMessage}
44 |
45 |
46 | );
47 | }
48 | };
49 | }
50 |
--------------------------------------------------------------------------------
/pro-mern-stack-2-15.07-non-proxy-mode/ui/webpack.serverHMR.js:
--------------------------------------------------------------------------------
1 | /*
2 | eslint-disable import/no-extraneous-dependencies
3 | */
4 | const webpack = require('webpack');
5 | const merge = require('webpack-merge');
6 | const serverConfig = require('./webpack.config.js')[1];
7 |
8 | module.exports = merge(serverConfig, {
9 | entry: { server: ['./node_modules/webpack/hot/poll?1000'] },
10 | plugins: [
11 | new webpack.HotModuleReplacementPlugin(),
12 | ],
13 | });
14 |
--------------------------------------------------------------------------------