From 52a50677bc28864ee3e3aafda0d6892a47e1ac9c Mon Sep 17 00:00:00 2001 From: ssfiero Date: Sun, 5 Mar 2017 13:55:20 -0600 Subject: [PATCH 1/7] Initial Commit --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 21f5dd1..413c413 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ # Final Project Starter A starter repository for the Final Project. + +Hi, my name is Steven Fiero and this is the repo for my final project for the ACA Advanced class. From 9baf001e74904e8e623227c376f7f2413a88a7ed Mon Sep 17 00:00:00 2001 From: ssfiero Date: Sun, 5 Mar 2017 20:28:13 -0600 Subject: [PATCH 2/7] Final Project - Authentication v1 complete --- client/src/App.js | 1 + src/index.js | 36 ++++++++++++----- src/routes/AuthenticationRoutes.js | 62 ++++++++++++++++++++++++++++++ src/services/passport.js | 61 +++++++++++++++++++++++++++++ 4 files changed, 150 insertions(+), 10 deletions(-) diff --git a/client/src/App.js b/client/src/App.js index fa355ef..b979cfa 100644 --- a/client/src/App.js +++ b/client/src/App.js @@ -6,6 +6,7 @@ import TopNavbar from './TopNavbar'; import Secret from './Secret'; import axios from 'axios'; + class App extends Component { } diff --git a/src/index.js b/src/index.js index 96dee02..e78f232 100644 --- a/src/index.js +++ b/src/index.js @@ -1,24 +1,40 @@ // dotenv allows us to declare environment variables in a .env file, \ // find out more here https://github.com/motdotla/dotenv -require("dotenv").config(); -const express = require("express"); -const bodyParser = require("body-parser"); -const mongoose = require("mongoose"); -const passport = require("passport"); +require('dotenv').config(); +const express = require('express'); +const bodyParser = require('body-parser'); +const mongoose = require('mongoose'); +const passport = require('passport'); + +// Require our custom strategies +require('./services/passport'); + + +/* eslint no-console: 0 */ mongoose.Promise = global.Promise; mongoose - .connect("mongodb://localhost/todo-list-app") - .then(() => console.log("[mongoose] Connected to MongoDB")) - .catch(() => console.log("[mongoose] Error connecting to MongoDB")); + .connect('mongodb://localhost/advanced-final-project-starter') + .then(() => console.log('[mongoose] Connected to MongoDB')) + .catch(() => console.log('[mongoose] Error connecting to MongoDB')); const app = express(); -const authenticationRoutes = require("./routes/AuthenticationRoutes"); +const authenticationRoutes = require('./routes/AuthenticationRoutes'); app.use(bodyParser.json()); -// app.use(authenticationRoutes); +app.use(authenticationRoutes); + +const authStrategy = passport.authenticate('authStrategy', { session: false }); + +/* eslint no-unused-vars: 0 */ +app.get('/api/secret', authStrategy, function (req, res, next) { + res.send(`The current user is ${req.user.username}`); +}); + + const port = process.env.PORT || 3001; + app.listen(port, () => { console.log(`Listening on port:${port}`); }); diff --git a/src/routes/AuthenticationRoutes.js b/src/routes/AuthenticationRoutes.js index 611413a..ef26db2 100644 --- a/src/routes/AuthenticationRoutes.js +++ b/src/routes/AuthenticationRoutes.js @@ -3,3 +3,65 @@ const router = express.Router(); const jwt = require('jwt-simple'); const User = require('../models/UserModel'); const bcrypt = require('bcrypt'); +const passport = require('passport'); + + +// Require our custom strategies +require('../services/passport'); + + +// Authentication +const signinStrategy = passport.authenticate('signinStrategy', { session: false}); + + +// Helper method to create token for a user +function tokenForUser(user) { + const timestamp = new Date().getTime(); + return jwt.encode({ userId: user.id, iat: timestamp }, process.env.SECRET); +} + + +/* eslint no-unused-vars: 0 */ +router.post('/api/signin', signinStrategy, function (req, res, next) { + res.json({ token: tokenForUser(req.user)}); +}); + + +router.post('/api/signup', function (req, res, next) { + // Grab the username and password from the request body + const { username, password } = req.body; + + // If no username or password was supplied return an error + if (!username || !password) { + return res.status(422) + .json({ error: 'You must provide a username and password' }); + } + + // Look for a user with the current user name + User.findOne({ username }).exec() + .then((existingUser) => { + // If the user exists, return an error on sign up + if (existingUser) { + return res.status(422).json({ error: 'Username is in use' }); + } + + // If the user does not exist, create the user + // Use bcrypt to hash their password (Never save plain text passwords!) + bcrypt.hash(password, 10, function (err, hashedPassword) { + if (err) { + return next(err); + } + + // Create a new user with the supplied username and the hashed password + const user = new User({ username, password: hashedPassword }); + + // Save and return the user + user.save() + .then(newUser => res.json({ token: tokenForUser(newUser) })); + }); + }) + .catch(err => next(err)); +}); + + +module.exports = router; diff --git a/src/services/passport.js b/src/services/passport.js index 5680076..f55105c 100644 --- a/src/services/passport.js +++ b/src/services/passport.js @@ -3,3 +3,64 @@ const passport = require('passport'); const User = require('../models/UserModel'); const { Strategy: JwtStrategy, ExtractJwt } = require('passport-jwt'); const LocalStrategy = require('passport-local'); + + +const signinStrategy = new LocalStrategy(function (username, password, done) { + User.findOne({ username }).exec() + .then(user => { + // If there is no user found, call done with a `null` argument, signifying + // no error and `false` signifying that the signin failed + if (!user) { + return done(null, false); + } + + bcrypt.compare(password, user.password, function (err, isMatch) { + // If there is an error, call done with the error + if (err) { + return done(err, false); + } + + // If the passwords do not match, call done with a `null` argument, signifying + // no error and `false` signifying that the signin failed + if (!isMatch) { + return done(null, false); + } + + // If there are no errors and the passwords match, + // call done with a `null` argument, signifying no error + // and with the now signed in user + return done(null, user); + }); + }) + .catch(err => done(err, false)); +}); + + +// Setup options for JwtStrategy +const jwtOptions = { + // Get the secret from the environment + secretOrKey: process.env.SECRET, + // Tell the strategy where to find the token in the request + jwtFromRequest: ExtractJwt.fromHeader('authorization') +}; + + +// Create JWT strategy +// This will take the token and decode it to +// extract the information we have stored in it +const authStrategy = new JwtStrategy(jwtOptions, function (payload, done) { + User.findById(payload.userId, function (err, user) { + if (err) { return done(err, false); } + + if (user) { + done(null, user); + } else { + done(null, false); + } + }); +}); + + +// Tell passport to use this strategy +passport.use('authStrategy', authStrategy); +passport.use('signinStrategy', signinStrategy); From c24382469a99fa7fe94dcc23f252e3ffbbfd406f Mon Sep 17 00:00:00 2001 From: ssfiero Date: Wed, 8 Mar 2017 23:39:21 -0600 Subject: [PATCH 3/7] Final Project - Authentication v2 --- client/src/App.js | 88 +++++++++++++++++++++++++++++- client/src/Secret.js | 6 ++ client/src/SignUp.js | 11 +++- client/src/SignUpSignIn.js | 5 +- client/src/TopNavbar.js | 8 ++- src/models/UserModel.js | 2 +- src/routes/AuthenticationRoutes.js | 2 +- 7 files changed, 113 insertions(+), 9 deletions(-) diff --git a/client/src/App.js b/client/src/App.js index b979cfa..7fc2ac4 100644 --- a/client/src/App.js +++ b/client/src/App.js @@ -1,14 +1,100 @@ import React, { Component } from 'react'; import { BrowserRouter, Match, Miss } from 'react-router'; +import axios from 'axios'; import './App.css'; import SignUpSignIn from './SignUpSignIn'; import TopNavbar from './TopNavbar'; import Secret from './Secret'; -import axios from 'axios'; class App extends Component { + constructor() { + super(); + + this.state = { + signUpSignInError: '', + authenticated: localStorage.getItem('token') + }; + } + + + handleSignUp(credentials) { + // Handler is responsible for taking the credentials, verifying the information + // and submitting the request to the API to signup a new user. + const { username, password, confirmPassword } = credentials; + if (!username.trim() || !password.trim() || password.trim() !== confirmPassword.trim()) { + this.setState({ + ...this.state, + signUpSignInError: 'Must Provide All Fields' + }); + } else { + axios.post('/api/signup', credentials) + .then(resp => { + const { token } = resp.data; + localStorage.setItem('token', token); + + this.setState({ + ...this.state, + signUpSignInError: '', + authenticated: token + }); + }); + } + } + + + handleSignIn(credentials) { + // Handle Sign Up + } + + handleSignOut() { + localStorage.removeItem('token'); + this.setState({ + authenticated: false + }); + } + + + renderSignUpSignIn() { + return ; + } + + + renderApp() { + // Returning routing logic to be rendered + // When user successfully signsup / signsin, the token is updated in state, + // which causes a re-render and then the renderApp method is called, + // which allows the user access to the application. + return ( +
+

I am protected!

} /> + +

NOT FOUND!

} /> +
+ ); + } + + + render() { + return ( + +
+ + // Looking into state for authenticated value to be set, if so render App. + // If state does not have authenticated value, render SignUpSignIn. + {this.state.authenticated ? this.renderApp() : this.renderSignUpSignIn()} +
+
+ ); + } } + export default App; diff --git a/client/src/Secret.js b/client/src/Secret.js index dc30585..a3df4cb 100644 --- a/client/src/Secret.js +++ b/client/src/Secret.js @@ -1,6 +1,7 @@ import React, { Component } from 'react'; import axios from 'axios'; + class Secret extends Component { constructor() { super(); @@ -10,8 +11,10 @@ class Secret extends Component { }; } + componentDidMount() { axios.get('/api/secret', { + // Passing token via authorization header headers: { authorization: localStorage.getItem('token') } @@ -22,9 +25,11 @@ class Secret extends Component { message: resp.data }); }) + /* eslint no-console: 0 */ .catch(err => console.log(err)); } + render() { return (

{this.state.message}

@@ -32,4 +37,5 @@ class Secret extends Component { } } + export default Secret; diff --git a/client/src/SignUp.js b/client/src/SignUp.js index 7b533c2..71eb190 100644 --- a/client/src/SignUp.js +++ b/client/src/SignUp.js @@ -1,6 +1,7 @@ import React, { Component, PropTypes } from 'react'; import { FormGroup, ControlLabel, FormControl, Button } from 'react-bootstrap'; + class SignUp extends Component { constructor() { super(); @@ -9,10 +10,12 @@ class SignUp extends Component { username: '', password: '', confirmPassword: '', - } + }; } + handleSubmit(event) { + // handleSubmit method is invoked when the form is submitted event.preventDefault(); this.props.onSignUp({ @@ -22,6 +25,7 @@ class SignUp extends Component { }); } + handleChange(event) { const { name, value } = event.target; @@ -31,6 +35,7 @@ class SignUp extends Component { })); } + render() { return (
@@ -69,14 +74,16 @@ class SignUp extends Component { +
); } } + SignUp.propTypes = { onSignUp: PropTypes.func.isRequired }; + export default SignUp; diff --git a/client/src/SignUpSignIn.js b/client/src/SignUpSignIn.js index 8bbfc4f..8fb0d29 100644 --- a/client/src/SignUpSignIn.js +++ b/client/src/SignUpSignIn.js @@ -2,6 +2,7 @@ import React, { Component, PropTypes } from 'react'; import { Tabs, Tab, Row, Col, Alert } from 'react-bootstrap'; import SignUp from './SignUp'; + class SignUpSignIn extends Component { renderError() { @@ -27,13 +28,15 @@ class SignUpSignIn extends Component { - ) + ); } } + SignUpSignIn.propTypes = { error: PropTypes.string, onSignUp: PropTypes.func.isRequired }; + export default SignUpSignIn; diff --git a/client/src/TopNavbar.js b/client/src/TopNavbar.js index e5d36b9..7544ac5 100644 --- a/client/src/TopNavbar.js +++ b/client/src/TopNavbar.js @@ -2,6 +2,7 @@ import React, { PropTypes } from 'react'; import { Navbar, Nav, NavItem } from 'react-bootstrap'; import { Link } from 'react-router'; + const TopNavbar = (props) => { return ( @@ -20,16 +21,17 @@ const TopNavbar = (props) => { - - : null + : null } ); -} +}; + TopNavbar.propTypes = { onSignOut: PropTypes.func.isRequired, showNavItems: PropTypes.bool.isRequired }; + export default TopNavbar; diff --git a/src/models/UserModel.js b/src/models/UserModel.js index 47c5e39..48107ef 100644 --- a/src/models/UserModel.js +++ b/src/models/UserModel.js @@ -1,6 +1,6 @@ const mongoose = require('mongoose'); const Schema = mongoose.Schema; -const bcrypt = require('bcrypt'); + const userSchema = new Schema({ username: { diff --git a/src/routes/AuthenticationRoutes.js b/src/routes/AuthenticationRoutes.js index ef26db2..3479dd9 100644 --- a/src/routes/AuthenticationRoutes.js +++ b/src/routes/AuthenticationRoutes.js @@ -11,7 +11,7 @@ require('../services/passport'); // Authentication -const signinStrategy = passport.authenticate('signinStrategy', { session: false}); +const signinStrategy = passport.authenticate('signinStrategy', { session: false }); // Helper method to create token for a user From 7e64ab864e132a47f1ba79f5fd915a05824a77ed Mon Sep 17 00:00:00 2001 From: ssfiero Date: Tue, 14 Mar 2017 23:24:09 -0500 Subject: [PATCH 4/7] Final Project - Authentication v2 complete --- client/src/App.js | 21 ++++++++++- client/src/SignIn.js | 75 ++++++++++++++++++++++++++++++++++++++ client/src/SignUpSignIn.js | 6 ++- 3 files changed, 99 insertions(+), 3 deletions(-) create mode 100644 client/src/SignIn.js diff --git a/client/src/App.js b/client/src/App.js index f572175..c178e0a 100644 --- a/client/src/App.js +++ b/client/src/App.js @@ -42,7 +42,25 @@ class App extends Component { handleSignIn(credentials) { - // Handle Sign Up + // Handler is responsible for taking the credentials, verifying the information + // and submitting the request to the API to signin an existing user. + const { username, password } = credentials; + if (!username.trim() || !password.trim()) { + this.setState({ + signUpSignInError: 'Must Provide All Fields' + }); + } else { + axios.post('/api/signin', credentials) + .then(resp => { + const { token } = resp.data; + localStorage.setItem('token', token); + + this.setState({ + signUpSignInError: '', + authenticated: token + }); + }); + } } @@ -58,6 +76,7 @@ class App extends Component { return ; } diff --git a/client/src/SignIn.js b/client/src/SignIn.js new file mode 100644 index 0000000..88782a7 --- /dev/null +++ b/client/src/SignIn.js @@ -0,0 +1,75 @@ +import React, { Component, PropTypes } from 'react'; +import { FormGroup, ControlLabel, FormControl, Button } from 'react-bootstrap'; + + +class SignIn extends Component { + constructor() { + super(); + + this.state = { + username: '', + password: '', + }; + } + + + handleSubmit(event) { + // handleSubmit method is invoked when the form is submitted + event.preventDefault(); + + this.props.onSignIn({ + username: this.state.username, + password: this.state.password + }); + } + + + handleChange(event) { + const { name, value } = event.target; + + this.setState({ + [name]: value + }); + } + + + render() { + return ( +
+ + Username + this.handleChange(event)} + placeholder="Enter Username" + value={this.state.username} + /> + + + + Password + this.handleChange(event)} + placeholder="Enter Password" + value={this.state.password} + /> + + + +
+ ); + } +} + + +SignIn.propTypes = { + onSignIn: PropTypes.func.isRequired +}; + + +export default SignIn; diff --git a/client/src/SignUpSignIn.js b/client/src/SignUpSignIn.js index 8fb0d29..5541fd8 100644 --- a/client/src/SignUpSignIn.js +++ b/client/src/SignUpSignIn.js @@ -1,6 +1,7 @@ import React, { Component, PropTypes } from 'react'; import { Tabs, Tab, Row, Col, Alert } from 'react-bootstrap'; import SignUp from './SignUp'; +import SignIn from './SignIn'; class SignUpSignIn extends Component { @@ -23,7 +24,7 @@ class SignUpSignIn extends Component { - Sign In + @@ -35,7 +36,8 @@ class SignUpSignIn extends Component { SignUpSignIn.propTypes = { error: PropTypes.string, - onSignUp: PropTypes.func.isRequired + onSignUp: PropTypes.func.isRequired, + onSignIn: PropTypes.func.isRequired }; From 51fa8ce61cb3375793fcf1a6655fef5e6095b397 Mon Sep 17 00:00:00 2001 From: ssfiero Date: Thu, 16 Mar 2017 23:42:27 -0500 Subject: [PATCH 5/7] Final Project - Related Data WIP --- src/controllers/ListsController.js | 36 ++++++++++++++++++++++++++++++ src/index.js | 5 +++++ src/models/ListModel.js | 33 +++++++++++++++++++++++++++ src/routes/ListRoutes.js | 14 ++++++++++++ 4 files changed, 88 insertions(+) create mode 100644 src/controllers/ListsController.js create mode 100644 src/models/ListModel.js create mode 100644 src/routes/ListRoutes.js diff --git a/src/controllers/ListsController.js b/src/controllers/ListsController.js new file mode 100644 index 0000000..b4e3043 --- /dev/null +++ b/src/controllers/ListsController.js @@ -0,0 +1,36 @@ +import ListModel from '../models/ListModel'; + + +const ListController = { + create(req, res, next ) { + // In create method, assigning the title based on what is received + // from the request body, and user_id from the req.user. + const list = new List({ title: req.body.title, user_id: req.user._id }); + list + .save() + .then(list => res.json(list)) + .catch(err => next(err)); + }, + + update(req, res, next) { + // In update method, finding the List based on the _id and user_id. + // This means a user will not be able to update a list they do not own. + List.find({ _id: req.params.id, user_id: req.user._id }) + exec() + .then(list => { + if (!list) { + return next('No List Found'); + } + + list.title = req.params.title; + return list.save(); + }) + .then(list => { + return req.json(list); + }) + .catch(err => next(err)); + } +}; + + +export default ListController; diff --git a/src/index.js b/src/index.js index e78f232..0ab469c 100644 --- a/src/index.js +++ b/src/index.js @@ -5,6 +5,7 @@ const express = require('express'); const bodyParser = require('body-parser'); const mongoose = require('mongoose'); const passport = require('passport'); +import ListRoutes from '../routes/ListRoutes'; // Require our custom strategies @@ -27,6 +28,10 @@ app.use(authenticationRoutes); const authStrategy = passport.authenticate('authStrategy', { session: false }); +app.use(authStrategy, ListRoutes); +// Applied middleware to routes, and all ListRoutes will be protected by authStrategy. +// This means a user will not have access unless they are logged into the application. + /* eslint no-unused-vars: 0 */ app.get('/api/secret', authStrategy, function (req, res, next) { res.send(`The current user is ${req.user.username}`); diff --git a/src/models/ListModel.js b/src/models/ListModel.js new file mode 100644 index 0000000..ddbd9c4 --- /dev/null +++ b/src/models/ListModel.js @@ -0,0 +1,33 @@ +import mongoose, { Schema } from 'mongoose'; + + +const listSchema = new Schema({ + title: { + type: String, + required: true + }, + + user_id: { + type: mongoose.ObjectId, + required: true + }, + + // Items do not have their own ItemModel.js file, but will instead exist + // inside of a List document in the Lists collection. + items: [{ + // Declare a new property items, and set its value to an array. + // Mongoose will store an array of items. + text: { + type: String, + required: true + }, + + completed: { + type: Boolean, + required: true + } + }] +}); + + +export default mongoose.model('List', listSchema); diff --git a/src/routes/ListRoutes.js b/src/routes/ListRoutes.js new file mode 100644 index 0000000..2426c6d --- /dev/null +++ b/src/routes/ListRoutes.js @@ -0,0 +1,14 @@ +import express from 'express'; +import { create, update } from '../controllers/ListsController'; + + +const router = express.Router(); + + +router.post('/lists', create); + + +router.put('/lists/:id', update); + + +export default router; From f9b1d9ecf4b8df1ece8433b76ee46ea30d8b0291 Mon Sep 17 00:00:00 2001 From: ssfiero Date: Sat, 18 Mar 2017 18:02:45 -0500 Subject: [PATCH 6/7] Final Project - Related Data WIP --- src/controllers/ListsItemsController.js | 56 +++++++++++++++++++++++++ src/index.js | 2 + src/routes/ListItemRoutes.js | 14 +++++++ 3 files changed, 72 insertions(+) create mode 100644 src/controllers/ListsItemsController.js create mode 100644 src/routes/ListItemRoutes.js diff --git a/src/controllers/ListsItemsController.js b/src/controllers/ListsItemsController.js new file mode 100644 index 0000000..bcbc93c --- /dev/null +++ b/src/controllers/ListsItemsController.js @@ -0,0 +1,56 @@ +import ListModel from '../models/ListModel'; + + +const ListsItemsController = { + create(req, res, next) { + // Find the list by it's id, and ensure that the current user owns the list + ListModel.find({ _id: req.params.list_id, user_id: req.user._id }) + .then(list => { + // Create a new object that represents an item + const item = { + text: req.body.text, + completed: false + }; + + // Add the item to the lists items array + list.items.push(item); + + // Save the list + return list.save(); + }) + .then(list => { + // Grab the newly created item, which is the last item in the array + const newItem = list.items[list.items.length - 1]; + + // Return that item + return res.json(newItem); + }) + .catch(err => next(err)); + }, + + update(req, res, next) { + const itemId = req.params.item_id; + + // Find the list by it's id, and ensure that the current user owns the list + List.find({ _id: req.params.list_id, user_id: req.user._id }) + .exec() + .then(list => { + // Find the item by it's _id + const item = list.items.id(itemId); + + // Update the item if new attributes are sent, or use the current attributes + item.text = req.body.text || item.text; + item.completed = req.body.completed || item.completed; + + return list.save(); + }) + .then(list => { + // Return the updated item + return req.json(list.items.id(itemId)); + }) + .catch(err => next(err)); + } +}; + + +export default ListsItemsController; diff --git a/src/index.js b/src/index.js index 0ab469c..b5be7a4 100644 --- a/src/index.js +++ b/src/index.js @@ -6,6 +6,7 @@ const bodyParser = require('body-parser'); const mongoose = require('mongoose'); const passport = require('passport'); import ListRoutes from '../routes/ListRoutes'; +import ListItemRoutes from '../routes/ListItemRoutes'; // Require our custom strategies @@ -29,6 +30,7 @@ app.use(authenticationRoutes); const authStrategy = passport.authenticate('authStrategy', { session: false }); app.use(authStrategy, ListRoutes); +app.use(authStrategy, ListItemRoutes); // Applied middleware to routes, and all ListRoutes will be protected by authStrategy. // This means a user will not have access unless they are logged into the application. diff --git a/src/routes/ListItemRoutes.js b/src/routes/ListItemRoutes.js new file mode 100644 index 0000000..2f99677 --- /dev/null +++ b/src/routes/ListItemRoutes.js @@ -0,0 +1,14 @@ +import express from 'express'; +import { create, update } from '../controllers/ListsItemsController'; + + +const router = express.Router(); + + +router.post('/lists/:list_id/items', create); + + +router.put('/lists/:list_id/items/:item_id', update); + + +export default router; From b830805b733ffd5ed5346617253d0485278e08d9 Mon Sep 17 00:00:00 2001 From: ssfiero Date: Mon, 27 Mar 2017 23:26:42 -0500 Subject: [PATCH 7/7] Final Project - Added CRUD actions --- client/src/SignUp.js | 2 +- src/controllers/ListsController.js | 53 +++++++++++++++++++++++-- src/controllers/ListsItemsController.js | 49 ++++++++++++++++++++++- src/index.js | 4 +- src/models/ListModel.js | 8 ++-- src/routes/ListItemRoutes.js | 18 +++++++-- src/routes/ListRoutes.js | 18 +++++++-- 7 files changed, 131 insertions(+), 21 deletions(-) diff --git a/client/src/SignUp.js b/client/src/SignUp.js index 701a24a..658fde2 100644 --- a/client/src/SignUp.js +++ b/client/src/SignUp.js @@ -9,7 +9,7 @@ class SignUp extends Component { this.state = { username: '', password: '', - confirmPassword: '', + confirmPassword: '' }; } diff --git a/src/controllers/ListsController.js b/src/controllers/ListsController.js index b4e3043..244a9a1 100644 --- a/src/controllers/ListsController.js +++ b/src/controllers/ListsController.js @@ -2,21 +2,48 @@ import ListModel from '../models/ListModel'; const ListController = { + + + // Declare a (GET = list) route. + list(req, res, next) { + ListModel.find({ _id: req.params.list_id, userId: req.user._id }) + .exec() + .then(list => { + return res.json(list); + }) + .catch(err => next(err)); + }, + + + // Declare a (GET/:id = show) route + show(req, res, next) { + ListModel.find({ _id: req.params.list_id, userId: req.user._id }) + .exec() + .then(list => { + return res.json(list); + }) + .catch(err => next(err)); + }, + + + // Declare a (POST = create) route. create(req, res, next ) { // In create method, assigning the title based on what is received // from the request body, and user_id from the req.user. - const list = new List({ title: req.body.title, user_id: req.user._id }); + const list = new List({ title: req.body.title, userId: req.user._id }); list .save() - .then(list => res.json(list)) + .then(newList => res.json(newList)) .catch(err => next(err)); }, + + // Declare a (PUT = update) route. update(req, res, next) { // In update method, finding the List based on the _id and user_id. // This means a user will not be able to update a list they do not own. - List.find({ _id: req.params.id, user_id: req.user._id }) - exec() + ListModel.find({ _id: req.params.id, userId: req.user._id }) + .exec() .then(list => { if (!list) { return next('No List Found'); @@ -29,6 +56,24 @@ const ListController = { return req.json(list); }) .catch(err => next(err)); + }, + + + // Declare a DELETE (remove) route. + remove(req, res, next) { + const itemId = req.params.item_id; + + ListModel.find({ _id: req.params.list_id, userId: req.user._id }) + .exec() + .then(list => { + list.items.id(itemId).remove(); + + return list.save(); + }) + .then(list => { + return req.json(list.items.id(itemId)); + }) + .catch(err => next(err)); } }; diff --git a/src/controllers/ListsItemsController.js b/src/controllers/ListsItemsController.js index bcbc93c..7d7c1b0 100644 --- a/src/controllers/ListsItemsController.js +++ b/src/controllers/ListsItemsController.js @@ -2,9 +2,34 @@ import ListModel from '../models/ListModel'; const ListsItemsController = { + + + // Declare a (GET = list) route. + list(req, res, next) { + ListModel.find({ _id: req.params.list_id, userId: req.user._id }) + .exec() + .then(list => { + return res.json(list); + }) + .catch(err => next(err)); + }, + + + // Declare a (GET/:id = show) route. + show(req, res, next) { + ListModel.find({ _id: req.params.list_id, userId: req.user._id }) + .exec() + .then(list => { + return res.json(list); + }) + .catch(err => next(err)); + }, + + + // Declare a (POST = create) route. create(req, res, next) { // Find the list by it's id, and ensure that the current user owns the list - ListModel.find({ _id: req.params.list_id, user_id: req.user._id }) + ListModel.find({ _id: req.params.list_id, userId: req.user._id }) .then(list => { // Create a new object that represents an item const item = { @@ -28,11 +53,13 @@ const ListsItemsController = { .catch(err => next(err)); }, + + // Declare a (PUT = update) route. update(req, res, next) { const itemId = req.params.item_id; // Find the list by it's id, and ensure that the current user owns the list - List.find({ _id: req.params.list_id, user_id: req.user._id }) + ListModel.find({ _id: req.params.list_id, userId: req.user._id }) .exec() .then(list => { // Find the item by it's _id @@ -49,6 +76,24 @@ const ListsItemsController = { return req.json(list.items.id(itemId)); }) .catch(err => next(err)); + }, + + + // Declare a (DELETE = remove) route. + remove(req, res, next) { + const itemId = req.params.item_id; + + ListModel.find({ _id: req.params.list_id, userId: req.user._id }) + .exec() + .then(list => { + list.items.id(itemId).remove(); + + return list.save(); + }) + .then(list => { + return req.json(list.items.id(itemId)); + }) + .catch(err => next(err)); } }; diff --git a/src/index.js b/src/index.js index b5be7a4..2fff3f5 100644 --- a/src/index.js +++ b/src/index.js @@ -5,8 +5,8 @@ const express = require('express'); const bodyParser = require('body-parser'); const mongoose = require('mongoose'); const passport = require('passport'); -import ListRoutes from '../routes/ListRoutes'; -import ListItemRoutes from '../routes/ListItemRoutes'; +import ListRoutes from './routes/ListRoutes'; +import ListItemRoutes from './routes/ListItemRoutes'; // Require our custom strategies diff --git a/src/models/ListModel.js b/src/models/ListModel.js index ddbd9c4..79454fc 100644 --- a/src/models/ListModel.js +++ b/src/models/ListModel.js @@ -7,16 +7,16 @@ const listSchema = new Schema({ required: true }, - user_id: { - type: mongoose.ObjectId, + userId: { + type: Schema.Types.ObjectId, required: true }, // Items do not have their own ItemModel.js file, but will instead exist // inside of a List document in the Lists collection. + // Declare a new property items, and set its value to an array. + // Mongoose will store an array of items. items: [{ - // Declare a new property items, and set its value to an array. - // Mongoose will store an array of items. text: { type: String, required: true diff --git a/src/routes/ListItemRoutes.js b/src/routes/ListItemRoutes.js index 2f99677..3da61e4 100644 --- a/src/routes/ListItemRoutes.js +++ b/src/routes/ListItemRoutes.js @@ -1,14 +1,24 @@ import express from 'express'; -import { create, update } from '../controllers/ListsItemsController'; +import ListsItemsController from '../controllers/ListsItemsController'; +const router = express.Router(); -const router = express.Router(); +// Declare GET routes. +router.get('/lists', ListsItemsController.list); + +router.get('/lists/:list_id/items', ListsItemsController.show); + + +// Declare a POST (create) route. +router.post('/lists/:list_id/items', ListsItemsController.create); -router.post('/lists/:list_id/items', create); +// Declare a PUT (update) route. +router.put('/lists/:list_id/items/:item_id', ListsItemsController.update); -router.put('/lists/:list_id/items/:item_id', update); +// Declare a DELETE (remove) route. +router.delete('/lists/:list_id/items/:item_id', ListsItemsController.remove); export default router; diff --git a/src/routes/ListRoutes.js b/src/routes/ListRoutes.js index 2426c6d..74e7c64 100644 --- a/src/routes/ListRoutes.js +++ b/src/routes/ListRoutes.js @@ -1,14 +1,24 @@ import express from 'express'; -import { create, update } from '../controllers/ListsController'; +import ListController from '../controllers/ListsController'; +const router = express.Router(); -const router = express.Router(); +// Declare GET routes. +router.get('/lists', ListController.list); + +router.get('/lists/:list_id', ListController.show); + + +// Declare a POST (create) route. +router.post('/lists', ListController.create); -router.post('/lists', create); +// Declare a PUT (update) route. +router.put('/lists/:id', ListController.update); -router.put('/lists/:id', update); +// Declare a DELETE (remove) route. +router.delete('/lists/:id', ListController.remove); export default router;