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