├── README.md ├── client ├── .babelrc ├── .gitignore ├── package-lock.json ├── package.json ├── resources │ ├── index.html │ └── scss │ │ └── style.scss ├── src │ ├── components │ │ ├── App │ │ │ └── index.jsx │ │ ├── Article │ │ │ ├── Form │ │ │ │ └── index.jsx │ │ │ └── index.js │ │ ├── Home │ │ │ └── index.jsx │ │ └── index.js │ ├── index.js │ ├── reducers │ │ ├── home.js │ │ └── index.js │ └── store.js └── webpack.config.js ├── package-lock.json └── server ├── .gitignore ├── app.js ├── models └── Articles.js ├── package-lock.json └── routes ├── api ├── articles.js └── index.js └── index.js /README.md: -------------------------------------------------------------------------------- 1 | # Blog-Tutorial 2 | 3 | Simple blog created using React.js & Node.js 4 | 5 | ### Prerequisites 6 | 7 | Make sure you have these installed on your machine 8 | 9 | * [Node.js](https://nodejs.org/en/download/) 10 | * [MongoDB](https://www.mongodb.com) 11 | * **npm** This comes with Node.js, but make sure you check if you have it anyway 12 | 13 | ### Installing packages 14 | 15 | Install backend packages 16 | 17 | ``` 18 | cd server/ 19 | npm i 20 | ``` 21 | 22 | Install frontend packages 23 | 24 | ``` 25 | cd client/ 26 | npm i 27 | ``` 28 | 29 | ### Running the app 30 | 31 | To run the app (dev. mode) 32 | 33 | ``` 34 | cd server 35 | node app.js 36 | 37 | cd client 38 | npm start 39 | ``` 40 | 41 | ## Built With 42 | 43 | * [Node.js](https://nodejs.org) - The backend framework used 44 | * [Express.js](https://github.com/expressjs/express) - Node.js framework used 45 | * [React.js](https://github.com/facebook/react) - The frontend framework used 46 | * [MongoDB](https://www.mongodb.com/) - Database platform used 47 | 48 | 49 | ## Authors 50 | 51 | * **Antonio Erdeljac** - *Initial work* - [Blog-Tutorial](https://github.com/AntonioErdeljac/Blog-Tutorial) 52 | 53 | ## Acknowledgments 54 | 55 | * This was a tutorial for my [Medium article](https://medium.com/@_aerdeljac/learn-how-to-create-a-simple-blog-with-react-node-c05fa6889de3) 56 | -------------------------------------------------------------------------------- /client/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env", "react"], 3 | "plugins": ["transform-object-rest-spread"] 4 | } 5 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "webpack.config.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "webpack --mode production", 9 | "start": "webpack-dev-server --mode development --open" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "babel": "^6.23.0", 15 | "babel-loader": "^7.1.4", 16 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 17 | "babel-preset-env": "^1.7.0", 18 | "babel-preset-react": "^6.24.1", 19 | "css-loader": "^0.28.11", 20 | "extract-text-webpack-plugin": "^4.0.0-beta.0", 21 | "file-loader": "^1.1.11", 22 | "html-loader": "^0.5.5", 23 | "html-webpack-plugin": "^3.2.0", 24 | "node-sass": "^4.9.0", 25 | "sass-loader": "^7.0.1", 26 | "style-loader": "^0.21.0", 27 | "webpack": "^4.8.3", 28 | "webpack-cli": "^2.1.4", 29 | "webpack-dev-server": "^3.1.4" 30 | }, 31 | "dependencies": { 32 | "axios": "^0.18.0", 33 | "babel-polyfill": "^6.26.0", 34 | "bootstrap": "^4.1.1", 35 | "history": "^4.7.2", 36 | "moment": "^2.22.1", 37 | "prop-types": "^15.6.1", 38 | "react": "^16.4.0", 39 | "react-dom": "^16.4.0", 40 | "react-redux": "^5.0.7", 41 | "react-router-dom": "^4.2.2", 42 | "react-scripts": "^1.1.4", 43 | "redux": "^4.0.0", 44 | "superagent": "^3.8.3", 45 | "superagent-promise": "^1.1.0" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /client/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | LightBlog 8 | 9 | 10 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /client/resources/scss/style.scss: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=Nunito|Poppins:500|Open+Sans'); 2 | @import "~bootstrap/scss/bootstrap"; 3 | 4 | body, html { 5 | height: 100%; 6 | background-color: rgba(0,0,0,.018); 7 | color: rgba(0,0,0,.75); 8 | font-family: 'Nunito'; 9 | } -------------------------------------------------------------------------------- /client/src/components/App/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { withRouter, Switch, Route } from 'react-router-dom'; 3 | 4 | import { Home } from '../../components'; 5 | 6 | const App = (props) => { 7 | return ( 8 | 9 | 10 | 11 | ) 12 | } 13 | 14 | export default withRouter(App); -------------------------------------------------------------------------------- /client/src/components/Article/Form/index.jsx: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import React from 'react'; 3 | import { connect } from 'react-redux'; 4 | 5 | class Form extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | 9 | this.state = { 10 | title: '', 11 | body: '', 12 | author: '', 13 | } 14 | 15 | this.handleChangeField = this.handleChangeField.bind(this); 16 | this.handleSubmit = this.handleSubmit.bind(this); 17 | } 18 | 19 | componentWillReceiveProps(nextProps) { 20 | if(nextProps.articleToEdit) { 21 | this.setState({ 22 | title: nextProps.articleToEdit.title, 23 | body: nextProps.articleToEdit.body, 24 | author: nextProps.articleToEdit.author, 25 | }); 26 | } 27 | } 28 | 29 | handleSubmit(){ 30 | const { onSubmit, articleToEdit, onEdit } = this.props; 31 | const { title, body, author } = this.state; 32 | 33 | if(!articleToEdit) { 34 | return axios.post('http://localhost:8000/api/articles', { 35 | title, 36 | body, 37 | author, 38 | }) 39 | .then((res) => onSubmit(res.data)) 40 | .then(() => this.setState({ title: '', body: '', author: '' })); 41 | } else { 42 | return axios.patch(`http://localhost:8000/api/articles/${articleToEdit._id}`, { 43 | title, 44 | body, 45 | author, 46 | }) 47 | .then((res) => onEdit(res.data)) 48 | .then(() => this.setState({ title: '', body: '', author: '' })); 49 | } 50 | } 51 | 52 | handleChangeField(key, event) { 53 | this.setState({ 54 | [key]: event.target.value, 55 | }); 56 | } 57 | 58 | render() { 59 | const { articleToEdit } = this.props; 60 | const { title, body, author } = this.state; 61 | 62 | return ( 63 |
64 | this.handleChangeField('title', ev)} 66 | value={title} 67 | className="form-control my-3" 68 | placeholder="Article Title" 69 | /> 70 | 76 | this.handleChangeField('author', ev)} 78 | value={author} 79 | className="form-control my-3" 80 | placeholder="Article Author" 81 | /> 82 | 83 |
84 | ) 85 | } 86 | } 87 | 88 | const mapDispatchToProps = dispatch => ({ 89 | onSubmit: data => dispatch({ type: 'SUBMIT_ARTICLE', data }), 90 | onEdit: data => dispatch({ type: 'EDIT_ARTICLE', data }), 91 | }); 92 | 93 | const mapStateToProps = state => ({ 94 | articleToEdit: state.home.articleToEdit, 95 | }); 96 | 97 | export default connect(mapStateToProps, mapDispatchToProps)(Form); -------------------------------------------------------------------------------- /client/src/components/Article/index.js: -------------------------------------------------------------------------------- 1 | export { default as Form } from './Form'; -------------------------------------------------------------------------------- /client/src/components/Home/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import axios from 'axios'; 3 | import moment from 'moment'; 4 | import { connect } from 'react-redux'; 5 | 6 | import { Form } from '../../components/Article'; 7 | 8 | class Home extends React.Component { 9 | constructor(props) { 10 | super(props); 11 | 12 | this.handleDelete = this.handleDelete.bind(this); 13 | this.handleEdit = this.handleEdit.bind(this); 14 | } 15 | 16 | componentDidMount() { 17 | const { onLoad } = this.props; 18 | 19 | axios('http://localhost:8000/api/articles') 20 | .then((res) => onLoad(res.data)); 21 | } 22 | 23 | handleDelete(id) { 24 | const { onDelete } = this.props; 25 | 26 | return axios.delete(`http://localhost:8000/api/articles/${id}`) 27 | .then(() => onDelete(id)); 28 | } 29 | 30 | handleEdit(article) { 31 | const { setEdit } = this.props; 32 | 33 | setEdit(article); 34 | } 35 | 36 | render() { 37 | const { articles } = this.props; 38 | 39 | return ( 40 |
41 |
42 |
43 |

LightBlog

44 |
45 |
46 |
47 |
48 |
49 | {articles.map((article) => { 50 | return ( 51 |
52 |
53 | {article.title} 54 |
55 |
56 | {article.body} 57 |

{article.author} {moment(new Date(article.createdAt)).fromNow()}

58 |
59 |
60 |
61 | 64 | 67 |
68 |
69 |
70 | ) 71 | })} 72 |
73 |
74 |
75 | ); 76 | } 77 | } 78 | 79 | const mapStateToProps = state => ({ 80 | articles: state.home.articles, 81 | }); 82 | 83 | const mapDispatchToProps = dispatch => ({ 84 | onLoad: data => dispatch({ type: 'HOME_PAGE_LOADED', data }), 85 | onDelete: id => dispatch({ type: 'DELETE_ARTICLE', id }), 86 | setEdit: article => dispatch({ type: 'SET_EDIT', article }), 87 | }); 88 | 89 | export default connect(mapStateToProps, mapDispatchToProps)(Home); -------------------------------------------------------------------------------- /client/src/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as App } from './App'; 2 | export { default as Home } from './Home'; 3 | export { default as Article } from './Article'; -------------------------------------------------------------------------------- /client/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import createHistory from 'history/createBrowserHistory'; 3 | import ReactDOM from 'react-dom'; 4 | import { Provider } from 'react-redux'; 5 | import { Route, Switch, Router } from 'react-router-dom'; 6 | 7 | import store from './store'; 8 | import { App } from './components'; 9 | 10 | import '../resources/scss/style.scss'; 11 | 12 | 13 | ReactDOM.render( 14 | 15 | 16 | 17 | 18 | 19 | 20 | , 21 | document.getElementById('root'), 22 | ); 23 | -------------------------------------------------------------------------------- /client/src/reducers/home.js: -------------------------------------------------------------------------------- 1 | export default (state={articles: []}, action) => { 2 | switch(action.type) { 3 | case 'HOME_PAGE_LOADED': 4 | return { 5 | ...state, 6 | articles: action.data.articles, 7 | }; 8 | case 'SUBMIT_ARTICLE': 9 | return { 10 | ...state, 11 | articles: ([action.data.article]).concat(state.articles), 12 | }; 13 | case 'DELETE_ARTICLE': 14 | return { 15 | ...state, 16 | articles: state.articles.filter((article) => article._id !== action.id), 17 | }; 18 | case 'SET_EDIT': 19 | return { 20 | ...state, 21 | articleToEdit: action.article, 22 | }; 23 | case 'EDIT_ARTICLE': 24 | return { 25 | ...state, 26 | articles: state.articles.map((article) => { 27 | if(article._id === action.data.article._id) { 28 | return { 29 | ...action.data.article, 30 | } 31 | } 32 | return article; 33 | }), 34 | articleToEdit: undefined, 35 | } 36 | default: 37 | return state; 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /client/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | export { default as home } from './home'; -------------------------------------------------------------------------------- /client/src/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, combineReducers } from 'redux'; 2 | 3 | import { home } from './reducers'; 4 | 5 | const reducers = combineReducers({ 6 | home, 7 | }); 8 | 9 | const store = createStore(reducers); 10 | 11 | export default store; -------------------------------------------------------------------------------- /client/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 2 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 3 | 4 | module.exports = { 5 | entry: [ 6 | 'babel-polyfill', 7 | './src/index.js', 8 | ], 9 | 10 | output: { 11 | publicPath: '/', 12 | filename: './main.js', 13 | }, 14 | 15 | resolve: { 16 | extensions: ['.js', '.jsx'], 17 | }, 18 | 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.(js|jsx)$/, 23 | exclude: /node_modules/, 24 | use: ['babel-loader'], 25 | }, 26 | 27 | { 28 | test: /\.(jpe?g|png|gif|svg)$/i, 29 | use: { 30 | loader: 'file-loader', 31 | options: { 32 | name: 'public/img/[name].[ext]', 33 | outputPath: 'dist/img/', 34 | }, 35 | }, 36 | }, 37 | 38 | { 39 | test: /\.scss$/, 40 | use: ExtractTextPlugin.extract({ 41 | fallback: 'style-loader', 42 | use: [{ loader: 'css-loader', options: { minimize: true } }, 'sass-loader'], 43 | }), 44 | }, 45 | { 46 | test: /\.html$/, 47 | use: { 48 | loader: 'html-loader', 49 | options: { 50 | minimize: true, 51 | }, 52 | }, 53 | }, 54 | { 55 | test: /\.(otf|ttf|eot|woff|woff2)$/, 56 | loader: 'file-loader', 57 | options: { 58 | name: 'public/fonts/[name].[ext]', 59 | outputPath: 'dist/fonts', 60 | }, 61 | }, 62 | ], 63 | }, 64 | 65 | plugins: [ 66 | new ExtractTextPlugin({ filename: 'style.css' }), 67 | new HtmlWebpackPlugin({ 68 | template: './resources/index.html', 69 | filename: './index.html', 70 | hash: true, 71 | }), 72 | ], 73 | 74 | devServer: { 75 | historyApiFallback: true, 76 | publicPath: '/', 77 | contentBase: './dist', 78 | }, 79 | }; -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "requires": true, 3 | "lockfileVersion": 1, 4 | "dependencies": { 5 | "axios": { 6 | "version": "0.18.0", 7 | "resolved": "https://registry.npmjs.org/axios/-/axios-0.18.0.tgz", 8 | "integrity": "sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI=", 9 | "requires": { 10 | "follow-redirects": "^1.3.0", 11 | "is-buffer": "^1.1.5" 12 | } 13 | }, 14 | "debug": { 15 | "version": "3.1.0", 16 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 17 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 18 | "requires": { 19 | "ms": "2.0.0" 20 | } 21 | }, 22 | "follow-redirects": { 23 | "version": "1.5.0", 24 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.0.tgz", 25 | "integrity": "sha512-fdrt472/9qQ6Kgjvb935ig6vJCuofpBUD14f9Vb+SLlm7xIe4Qva5gey8EKtv8lp7ahE1wilg3xL1znpVGtZIA==", 26 | "requires": { 27 | "debug": "^3.1.0" 28 | } 29 | }, 30 | "is-buffer": { 31 | "version": "1.1.6", 32 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", 33 | "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" 34 | }, 35 | "ms": { 36 | "version": "2.0.0", 37 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 38 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /server/app.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const express = require('express'); 3 | const bodyParser = require('body-parser'); 4 | const session = require('express-session'); 5 | const cors = require('cors'); 6 | const errorHandler = require('errorhandler'); 7 | const mongoose = require('mongoose'); 8 | 9 | mongoose.promise = global.Promise; 10 | 11 | const isProduction = process.env.NODE_ENV === 'production'; 12 | 13 | const app = express(); 14 | 15 | app.use(cors()); 16 | app.use(require('morgan')('dev')); 17 | app.use(bodyParser.urlencoded({ extended: false })); 18 | app.use(bodyParser.json()); 19 | app.use(express.static(path.join(__dirname, 'public'))); 20 | app.use(session({ secret: 'LightBlog', cookie: { maxAge: 60000 }, resave: false, saveUninitialized: false })); 21 | 22 | if(!isProduction) { 23 | app.use(errorHandler()); 24 | } 25 | 26 | mongoose.connect('mongodb://localhost/lightblog'); 27 | mongoose.set('debug', true); 28 | 29 | // Add models 30 | require('./models/Articles'); 31 | // Add routes 32 | app.use(require('./routes')); 33 | 34 | app.use((req, res, next) => { 35 | const err = new Error('Not Found'); 36 | err.status = 404; 37 | next(err); 38 | }); 39 | 40 | if (!isProduction) { 41 | app.use((err, req, res) => { 42 | res.status(err.status || 500); 43 | 44 | res.json({ 45 | errors: { 46 | message: err.message, 47 | error: err, 48 | }, 49 | }); 50 | }); 51 | } 52 | 53 | app.use((err, req, res) => { 54 | res.status(err.status || 500); 55 | 56 | res.json({ 57 | errors: { 58 | message: err.message, 59 | error: {}, 60 | }, 61 | }); 62 | }); 63 | 64 | app.listen(8000, () => console.log('Server started on http://localhost:8000')); -------------------------------------------------------------------------------- /server/models/Articles.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const { Schema } = mongoose; 4 | 5 | const ArticlesSchema = new Schema({ 6 | title: String, 7 | body: String, 8 | author: String, 9 | }, { timestamps: true }); 10 | 11 | ArticlesSchema.methods.toJSON = function() { 12 | return { 13 | _id: this._id, 14 | title: this.title, 15 | body: this.body, 16 | author: this.author, 17 | createdAt: this.createdAt, 18 | updatedAt: this.updatedAt, 19 | }; 20 | }; 21 | 22 | mongoose.model('Articles', ArticlesSchema); -------------------------------------------------------------------------------- /server/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "requires": true, 3 | "lockfileVersion": 1, 4 | "dependencies": { 5 | "accepts": { 6 | "version": "1.3.5", 7 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", 8 | "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", 9 | "requires": { 10 | "mime-types": "~2.1.18", 11 | "negotiator": "0.6.1" 12 | } 13 | }, 14 | "array-flatten": { 15 | "version": "1.1.1", 16 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 17 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 18 | }, 19 | "async": { 20 | "version": "2.1.4", 21 | "resolved": "https://registry.npmjs.org/async/-/async-2.1.4.tgz", 22 | "integrity": "sha1-LSFgx3iAMuTdbL4lAvH5osj2zeQ=", 23 | "requires": { 24 | "lodash": "^4.14.0" 25 | } 26 | }, 27 | "basic-auth": { 28 | "version": "2.0.0", 29 | "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.0.tgz", 30 | "integrity": "sha1-AV2z81PgLlY3d1X5YnQuiYHnu7o=", 31 | "requires": { 32 | "safe-buffer": "5.1.1" 33 | } 34 | }, 35 | "bluebird": { 36 | "version": "3.5.0", 37 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.0.tgz", 38 | "integrity": "sha1-eRQg1/VR7qKJdFOop3ZT+WYG1nw=" 39 | }, 40 | "body-parser": { 41 | "version": "1.18.3", 42 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", 43 | "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", 44 | "requires": { 45 | "bytes": "3.0.0", 46 | "content-type": "~1.0.4", 47 | "debug": "2.6.9", 48 | "depd": "~1.1.2", 49 | "http-errors": "~1.6.3", 50 | "iconv-lite": "0.4.23", 51 | "on-finished": "~2.3.0", 52 | "qs": "6.5.2", 53 | "raw-body": "2.3.3", 54 | "type-is": "~1.6.16" 55 | } 56 | }, 57 | "bson": { 58 | "version": "1.0.6", 59 | "resolved": "https://registry.npmjs.org/bson/-/bson-1.0.6.tgz", 60 | "integrity": "sha512-D8zmlb46xfuK2gGvKmUjIklQEouN2nQ0LEHHeZ/NoHM2LDiMk2EYzZ5Ntw/Urk+bgMDosOZxaRzXxvhI5TcAVQ==" 61 | }, 62 | "bytes": { 63 | "version": "3.0.0", 64 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", 65 | "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" 66 | }, 67 | "content-disposition": { 68 | "version": "0.5.2", 69 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", 70 | "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" 71 | }, 72 | "content-type": { 73 | "version": "1.0.4", 74 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 75 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 76 | }, 77 | "cookie": { 78 | "version": "0.3.1", 79 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", 80 | "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" 81 | }, 82 | "cookie-signature": { 83 | "version": "1.0.6", 84 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 85 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 86 | }, 87 | "cors": { 88 | "version": "2.8.4", 89 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.4.tgz", 90 | "integrity": "sha1-K9OB8usgECAQXNUOpZ2mMJBpRoY=", 91 | "requires": { 92 | "object-assign": "^4", 93 | "vary": "^1" 94 | } 95 | }, 96 | "crc": { 97 | "version": "3.4.4", 98 | "resolved": "https://registry.npmjs.org/crc/-/crc-3.4.4.tgz", 99 | "integrity": "sha1-naHpgOO9RPxck79as9ozeNheRms=" 100 | }, 101 | "debug": { 102 | "version": "2.6.9", 103 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 104 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 105 | "requires": { 106 | "ms": "2.0.0" 107 | } 108 | }, 109 | "depd": { 110 | "version": "1.1.2", 111 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 112 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 113 | }, 114 | "destroy": { 115 | "version": "1.0.4", 116 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 117 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 118 | }, 119 | "ee-first": { 120 | "version": "1.1.1", 121 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 122 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 123 | }, 124 | "encodeurl": { 125 | "version": "1.0.2", 126 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 127 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 128 | }, 129 | "errorhandler": { 130 | "version": "1.5.0", 131 | "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.5.0.tgz", 132 | "integrity": "sha1-6rpkyl1UKjEayUX1gt78M2Fl2fQ=", 133 | "requires": { 134 | "accepts": "~1.3.3", 135 | "escape-html": "~1.0.3" 136 | } 137 | }, 138 | "escape-html": { 139 | "version": "1.0.3", 140 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 141 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 142 | }, 143 | "etag": { 144 | "version": "1.8.1", 145 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 146 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 147 | }, 148 | "express": { 149 | "version": "4.16.3", 150 | "resolved": "https://registry.npmjs.org/express/-/express-4.16.3.tgz", 151 | "integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=", 152 | "requires": { 153 | "accepts": "~1.3.5", 154 | "array-flatten": "1.1.1", 155 | "body-parser": "1.18.2", 156 | "content-disposition": "0.5.2", 157 | "content-type": "~1.0.4", 158 | "cookie": "0.3.1", 159 | "cookie-signature": "1.0.6", 160 | "debug": "2.6.9", 161 | "depd": "~1.1.2", 162 | "encodeurl": "~1.0.2", 163 | "escape-html": "~1.0.3", 164 | "etag": "~1.8.1", 165 | "finalhandler": "1.1.1", 166 | "fresh": "0.5.2", 167 | "merge-descriptors": "1.0.1", 168 | "methods": "~1.1.2", 169 | "on-finished": "~2.3.0", 170 | "parseurl": "~1.3.2", 171 | "path-to-regexp": "0.1.7", 172 | "proxy-addr": "~2.0.3", 173 | "qs": "6.5.1", 174 | "range-parser": "~1.2.0", 175 | "safe-buffer": "5.1.1", 176 | "send": "0.16.2", 177 | "serve-static": "1.13.2", 178 | "setprototypeof": "1.1.0", 179 | "statuses": "~1.4.0", 180 | "type-is": "~1.6.16", 181 | "utils-merge": "1.0.1", 182 | "vary": "~1.1.2" 183 | }, 184 | "dependencies": { 185 | "body-parser": { 186 | "version": "1.18.2", 187 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", 188 | "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", 189 | "requires": { 190 | "bytes": "3.0.0", 191 | "content-type": "~1.0.4", 192 | "debug": "2.6.9", 193 | "depd": "~1.1.1", 194 | "http-errors": "~1.6.2", 195 | "iconv-lite": "0.4.19", 196 | "on-finished": "~2.3.0", 197 | "qs": "6.5.1", 198 | "raw-body": "2.3.2", 199 | "type-is": "~1.6.15" 200 | } 201 | }, 202 | "iconv-lite": { 203 | "version": "0.4.19", 204 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", 205 | "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" 206 | }, 207 | "qs": { 208 | "version": "6.5.1", 209 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", 210 | "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" 211 | }, 212 | "raw-body": { 213 | "version": "2.3.2", 214 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", 215 | "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", 216 | "requires": { 217 | "bytes": "3.0.0", 218 | "http-errors": "1.6.2", 219 | "iconv-lite": "0.4.19", 220 | "unpipe": "1.0.0" 221 | }, 222 | "dependencies": { 223 | "depd": { 224 | "version": "1.1.1", 225 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", 226 | "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" 227 | }, 228 | "http-errors": { 229 | "version": "1.6.2", 230 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", 231 | "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", 232 | "requires": { 233 | "depd": "1.1.1", 234 | "inherits": "2.0.3", 235 | "setprototypeof": "1.0.3", 236 | "statuses": ">= 1.3.1 < 2" 237 | } 238 | }, 239 | "setprototypeof": { 240 | "version": "1.0.3", 241 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", 242 | "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" 243 | } 244 | } 245 | }, 246 | "statuses": { 247 | "version": "1.4.0", 248 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", 249 | "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" 250 | } 251 | } 252 | }, 253 | "express-session": { 254 | "version": "1.15.6", 255 | "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.15.6.tgz", 256 | "integrity": "sha512-r0nrHTCYtAMrFwZ0kBzZEXa1vtPVrw0dKvGSrKP4dahwBQ1BJpF2/y1Pp4sCD/0kvxV4zZeclyvfmw0B4RMJQA==", 257 | "requires": { 258 | "cookie": "0.3.1", 259 | "cookie-signature": "1.0.6", 260 | "crc": "3.4.4", 261 | "debug": "2.6.9", 262 | "depd": "~1.1.1", 263 | "on-headers": "~1.0.1", 264 | "parseurl": "~1.3.2", 265 | "uid-safe": "~2.1.5", 266 | "utils-merge": "1.0.1" 267 | } 268 | }, 269 | "finalhandler": { 270 | "version": "1.1.1", 271 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", 272 | "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", 273 | "requires": { 274 | "debug": "2.6.9", 275 | "encodeurl": "~1.0.2", 276 | "escape-html": "~1.0.3", 277 | "on-finished": "~2.3.0", 278 | "parseurl": "~1.3.2", 279 | "statuses": "~1.4.0", 280 | "unpipe": "~1.0.0" 281 | }, 282 | "dependencies": { 283 | "statuses": { 284 | "version": "1.4.0", 285 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", 286 | "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" 287 | } 288 | } 289 | }, 290 | "forwarded": { 291 | "version": "0.1.2", 292 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 293 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" 294 | }, 295 | "fresh": { 296 | "version": "0.5.2", 297 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 298 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 299 | }, 300 | "http-errors": { 301 | "version": "1.6.3", 302 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", 303 | "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", 304 | "requires": { 305 | "depd": "~1.1.2", 306 | "inherits": "2.0.3", 307 | "setprototypeof": "1.1.0", 308 | "statuses": ">= 1.4.0 < 2" 309 | } 310 | }, 311 | "iconv-lite": { 312 | "version": "0.4.23", 313 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", 314 | "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", 315 | "requires": { 316 | "safer-buffer": ">= 2.1.2 < 3" 317 | } 318 | }, 319 | "inherits": { 320 | "version": "2.0.3", 321 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 322 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 323 | }, 324 | "ipaddr.js": { 325 | "version": "1.6.0", 326 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.6.0.tgz", 327 | "integrity": "sha1-4/o1e3c9phnybpXwSdBVxyeW+Gs=" 328 | }, 329 | "kareem": { 330 | "version": "2.1.0", 331 | "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.1.0.tgz", 332 | "integrity": "sha512-ycoMY1tVkcH1/NaxGn2erZaUC3CodmX7Fl6DUVXjN73+uecWYTaaldRkxNY3HeSKQnQTWnoxRKnZfVHcB8tIWg==" 333 | }, 334 | "lodash": { 335 | "version": "4.17.10", 336 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", 337 | "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" 338 | }, 339 | "lodash.get": { 340 | "version": "4.4.2", 341 | "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", 342 | "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" 343 | }, 344 | "media-typer": { 345 | "version": "0.3.0", 346 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 347 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 348 | }, 349 | "merge-descriptors": { 350 | "version": "1.0.1", 351 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 352 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 353 | }, 354 | "methods": { 355 | "version": "1.1.2", 356 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 357 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 358 | }, 359 | "mime": { 360 | "version": "1.4.1", 361 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", 362 | "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" 363 | }, 364 | "mime-db": { 365 | "version": "1.33.0", 366 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", 367 | "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" 368 | }, 369 | "mime-types": { 370 | "version": "2.1.18", 371 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", 372 | "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", 373 | "requires": { 374 | "mime-db": "~1.33.0" 375 | } 376 | }, 377 | "mongodb": { 378 | "version": "3.0.8", 379 | "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.0.8.tgz", 380 | "integrity": "sha512-mj7yIUyAr9xnO2ev8pcVJ9uX7gSum5LLs1qIFoWLxA5Il50+jcojKtaO1/TbexsScZ9Poz00Pc3b86GiSqJ7WA==", 381 | "requires": { 382 | "mongodb-core": "3.0.8" 383 | } 384 | }, 385 | "mongodb-core": { 386 | "version": "3.0.8", 387 | "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-3.0.8.tgz", 388 | "integrity": "sha512-dFxfhH9N7ohuQnINyIl6dqEF8sYOE0WKuymrFf3L3cipJNrx+S8rAbNOTwa00/fuJCjBMJNFsaA+R2N16//UIw==", 389 | "requires": { 390 | "bson": "~1.0.4", 391 | "require_optional": "^1.0.1" 392 | } 393 | }, 394 | "mongoose": { 395 | "version": "5.1.2", 396 | "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.1.2.tgz", 397 | "integrity": "sha512-k9hssPMgBnUYG5e9NoUbx/2ERDyelDY0Vf6BwjtmoETUhVT7pQUe1o+oelLLuHF3ZVY2qgienK8pnrI5pdvlxA==", 398 | "requires": { 399 | "async": "2.1.4", 400 | "bson": "~1.0.5", 401 | "kareem": "2.1.0", 402 | "lodash.get": "4.4.2", 403 | "mongodb": "3.0.8", 404 | "mongoose-legacy-pluralize": "1.0.2", 405 | "mpath": "0.4.1", 406 | "mquery": "3.0.0", 407 | "ms": "2.0.0", 408 | "regexp-clone": "0.0.1", 409 | "sliced": "1.0.1" 410 | } 411 | }, 412 | "mongoose-legacy-pluralize": { 413 | "version": "1.0.2", 414 | "resolved": "https://registry.npmjs.org/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz", 415 | "integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==" 416 | }, 417 | "morgan": { 418 | "version": "1.9.0", 419 | "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.0.tgz", 420 | "integrity": "sha1-0B+mxlhZt2/PMbPLU6OCGjEdgFE=", 421 | "requires": { 422 | "basic-auth": "~2.0.0", 423 | "debug": "2.6.9", 424 | "depd": "~1.1.1", 425 | "on-finished": "~2.3.0", 426 | "on-headers": "~1.0.1" 427 | } 428 | }, 429 | "mpath": { 430 | "version": "0.4.1", 431 | "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.4.1.tgz", 432 | "integrity": "sha512-NNY/MpBkALb9jJmjpBlIi6GRoLveLUM0pJzgbp9vY9F7IQEb/HREC/nxrixechcQwd1NevOhJnWWV8QQQRE+OA==" 433 | }, 434 | "mquery": { 435 | "version": "3.0.0", 436 | "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.0.0.tgz", 437 | "integrity": "sha512-WL1Lk8v4l8VFSSwN3yCzY9TXw+fKVYKn6f+w86TRzOLSE8k1yTgGaLBPUByJQi8VcLbOdnUneFV/y3Kv874pnQ==", 438 | "requires": { 439 | "bluebird": "3.5.0", 440 | "debug": "2.6.9", 441 | "regexp-clone": "0.0.1", 442 | "sliced": "0.0.5" 443 | }, 444 | "dependencies": { 445 | "sliced": { 446 | "version": "0.0.5", 447 | "resolved": "https://registry.npmjs.org/sliced/-/sliced-0.0.5.tgz", 448 | "integrity": "sha1-XtwETKTrb3gW1Qui/GPiXY/kcH8=" 449 | } 450 | } 451 | }, 452 | "ms": { 453 | "version": "2.0.0", 454 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 455 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 456 | }, 457 | "negotiator": { 458 | "version": "0.6.1", 459 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", 460 | "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" 461 | }, 462 | "object-assign": { 463 | "version": "4.1.1", 464 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 465 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 466 | }, 467 | "on-finished": { 468 | "version": "2.3.0", 469 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 470 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 471 | "requires": { 472 | "ee-first": "1.1.1" 473 | } 474 | }, 475 | "on-headers": { 476 | "version": "1.0.1", 477 | "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz", 478 | "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=" 479 | }, 480 | "parseurl": { 481 | "version": "1.3.2", 482 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", 483 | "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" 484 | }, 485 | "path": { 486 | "version": "0.12.7", 487 | "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", 488 | "integrity": "sha1-1NwqUGxM4hl+tIHr/NWzbAFAsQ8=", 489 | "requires": { 490 | "process": "^0.11.1", 491 | "util": "^0.10.3" 492 | } 493 | }, 494 | "path-to-regexp": { 495 | "version": "0.1.7", 496 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 497 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 498 | }, 499 | "process": { 500 | "version": "0.11.10", 501 | "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", 502 | "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" 503 | }, 504 | "proxy-addr": { 505 | "version": "2.0.3", 506 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.3.tgz", 507 | "integrity": "sha512-jQTChiCJteusULxjBp8+jftSQE5Obdl3k4cnmLA6WXtK6XFuWRnvVL7aCiBqaLPM8c4ph0S4tKna8XvmIwEnXQ==", 508 | "requires": { 509 | "forwarded": "~0.1.2", 510 | "ipaddr.js": "1.6.0" 511 | } 512 | }, 513 | "qs": { 514 | "version": "6.5.2", 515 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", 516 | "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" 517 | }, 518 | "random-bytes": { 519 | "version": "1.0.0", 520 | "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", 521 | "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" 522 | }, 523 | "range-parser": { 524 | "version": "1.2.0", 525 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", 526 | "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" 527 | }, 528 | "raw-body": { 529 | "version": "2.3.3", 530 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", 531 | "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", 532 | "requires": { 533 | "bytes": "3.0.0", 534 | "http-errors": "1.6.3", 535 | "iconv-lite": "0.4.23", 536 | "unpipe": "1.0.0" 537 | } 538 | }, 539 | "regexp-clone": { 540 | "version": "0.0.1", 541 | "resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-0.0.1.tgz", 542 | "integrity": "sha1-p8LgmJH9vzj7sQ03b7cwA+aKxYk=" 543 | }, 544 | "require_optional": { 545 | "version": "1.0.1", 546 | "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", 547 | "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==", 548 | "requires": { 549 | "resolve-from": "^2.0.0", 550 | "semver": "^5.1.0" 551 | } 552 | }, 553 | "resolve-from": { 554 | "version": "2.0.0", 555 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", 556 | "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=" 557 | }, 558 | "safe-buffer": { 559 | "version": "5.1.1", 560 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", 561 | "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" 562 | }, 563 | "safer-buffer": { 564 | "version": "2.1.2", 565 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 566 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 567 | }, 568 | "semver": { 569 | "version": "5.5.0", 570 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", 571 | "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" 572 | }, 573 | "send": { 574 | "version": "0.16.2", 575 | "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", 576 | "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", 577 | "requires": { 578 | "debug": "2.6.9", 579 | "depd": "~1.1.2", 580 | "destroy": "~1.0.4", 581 | "encodeurl": "~1.0.2", 582 | "escape-html": "~1.0.3", 583 | "etag": "~1.8.1", 584 | "fresh": "0.5.2", 585 | "http-errors": "~1.6.2", 586 | "mime": "1.4.1", 587 | "ms": "2.0.0", 588 | "on-finished": "~2.3.0", 589 | "range-parser": "~1.2.0", 590 | "statuses": "~1.4.0" 591 | }, 592 | "dependencies": { 593 | "statuses": { 594 | "version": "1.4.0", 595 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", 596 | "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" 597 | } 598 | } 599 | }, 600 | "serve-static": { 601 | "version": "1.13.2", 602 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", 603 | "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", 604 | "requires": { 605 | "encodeurl": "~1.0.2", 606 | "escape-html": "~1.0.3", 607 | "parseurl": "~1.3.2", 608 | "send": "0.16.2" 609 | } 610 | }, 611 | "setprototypeof": { 612 | "version": "1.1.0", 613 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", 614 | "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" 615 | }, 616 | "sliced": { 617 | "version": "1.0.1", 618 | "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", 619 | "integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=" 620 | }, 621 | "statuses": { 622 | "version": "1.5.0", 623 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 624 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 625 | }, 626 | "type-is": { 627 | "version": "1.6.16", 628 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", 629 | "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", 630 | "requires": { 631 | "media-typer": "0.3.0", 632 | "mime-types": "~2.1.18" 633 | } 634 | }, 635 | "uid-safe": { 636 | "version": "2.1.5", 637 | "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", 638 | "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", 639 | "requires": { 640 | "random-bytes": "~1.0.0" 641 | } 642 | }, 643 | "unpipe": { 644 | "version": "1.0.0", 645 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 646 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 647 | }, 648 | "util": { 649 | "version": "0.10.3", 650 | "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", 651 | "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", 652 | "requires": { 653 | "inherits": "2.0.1" 654 | }, 655 | "dependencies": { 656 | "inherits": { 657 | "version": "2.0.1", 658 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", 659 | "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=" 660 | } 661 | } 662 | }, 663 | "utils-merge": { 664 | "version": "1.0.1", 665 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 666 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 667 | }, 668 | "vary": { 669 | "version": "1.1.2", 670 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 671 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 672 | } 673 | } 674 | } 675 | -------------------------------------------------------------------------------- /server/routes/api/articles.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const router = require('express').Router(); 3 | const Articles = mongoose.model('Articles'); 4 | 5 | router.post('/', (req, res, next) => { 6 | const { body } = req; 7 | 8 | if(!body.title) { 9 | return res.status(422).json({ 10 | errors: { 11 | title: 'is required', 12 | }, 13 | }); 14 | } 15 | 16 | if(!body.author) { 17 | return res.status(422).json({ 18 | errors: { 19 | author: 'is required', 20 | }, 21 | }); 22 | } 23 | 24 | if(!body.body) { 25 | return res.status(422).json({ 26 | errors: { 27 | body: 'is required', 28 | }, 29 | }); 30 | } 31 | 32 | const finalArticle = new Articles(body); 33 | return finalArticle.save() 34 | .then(() => res.json({ article: finalArticle.toJSON() })) 35 | .catch(next); 36 | }); 37 | 38 | router.get('/', (req, res, next) => { 39 | return Articles.find() 40 | .sort({ createdAt: 'descending' }) 41 | .then((articles) => res.json({ articles: articles.map(article => article.toJSON()) })) 42 | .catch(next); 43 | }); 44 | 45 | router.param('id', (req, res, next, id) => { 46 | return Articles.findById(id, (err, article) => { 47 | if(err) { 48 | return res.sendStatus(404); 49 | } else if(article) { 50 | req.article = article; 51 | return next(); 52 | } 53 | }).catch(next); 54 | }); 55 | 56 | router.get('/:id', (req, res, next) => { 57 | return res.json({ 58 | article: req.article.toJSON(), 59 | }); 60 | }); 61 | 62 | router.patch('/:id', (req, res, next) => { 63 | const { body } = req; 64 | 65 | if(typeof body.title !== 'undefined') { 66 | req.article.title = body.title; 67 | } 68 | 69 | if(typeof body.author !== 'undefined') { 70 | req.article.author = body.author; 71 | } 72 | 73 | if(typeof body.body !== 'undefined') { 74 | req.article.body = body.body; 75 | } 76 | 77 | return req.article.save() 78 | .then(() => res.json({ article: req.article.toJSON() })) 79 | .catch(next); 80 | }); 81 | 82 | router.delete('/:id', (req, res, next) => { 83 | return Articles.findByIdAndRemove(req.article._id) 84 | .then(() => res.sendStatus(200)) 85 | .catch(next); 86 | }); 87 | 88 | module.exports = router; -------------------------------------------------------------------------------- /server/routes/api/index.js: -------------------------------------------------------------------------------- 1 | const router = require('express').Router(); 2 | 3 | router.use('/articles', require('./articles')); 4 | 5 | module.exports = router; -------------------------------------------------------------------------------- /server/routes/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | 4 | router.use('/api', require('./api')); 5 | 6 | module.exports = router; 7 | --------------------------------------------------------------------------------