diff --git a/.gitignore b/.gitignore index 028e2a6..9c301b7 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ public/bundle.js package-lock.json *hot-update.js *hot-update.json +env.sh diff --git a/backend/routes.js b/backend/routes.js index 9dd2215..5fc13a7 100644 --- a/backend/routes.js +++ b/backend/routes.js @@ -1,11 +1,148 @@ const express = require('express'); const router = express.Router(); +const { User, Post } = require('../models'); +const bcrypt = require('bcrypt'); +let hashedPassword; -// YOUR API ROUTES HERE +module.exports = (passport) => { + // YOUR API ROUTES HERE + router.post('/register', (req, res) => { + User.findAll({where: {username: req.body.username}}) + .then(users => { + if(req.body.password === req.body.repeatPassword && !users[0]) { + bcrypt.hash(req.body.password, 10, (err, hash) => { + hashedPassword = hash; + User.create({ + username: req.body.username, + password: hashedPassword + }) + .then(() => { + res.json({success: true}); + }); + }); + } else { + res.json({success: false}); + } + }) + .catch((err)=>console.log(err)); + }); -// SAMPLE ROUTE -router.use('/users', (req, res) => { - res.json({ success: true }); -}); + router.post('/login', (req, res, next) => { + passport.authenticate('local', (err, user) => { + console.log('HERE'); + user ? req.login(user, error => { + if(err) {return next(error);} + return res.json({success: true, user: req.user}); + }) : res.json({success: false}); + })(req, res, next); + }); -module.exports = router; + router.get('/posts/all', (req, res) => { + Post.findAll({ + where: {fk_post_id: null} + }) + .then((posts) => { + var newPosts = posts.map(post => post.dataValues); + console.log(newPosts); + res.json({success: true, posts: newPosts}); + }); + }); + + router.use((req, res, next) => { + console.log(req.user, "MIddleware req.user is this"); + if (!req.user) { + res.status(401).json({success: 'failed'}); + } else { + next(); + } + }); + + router.get('/logout', (req, res) => { + req.logout(); + res.status(200).json({success: true}); + }); + + + router.get('/:username', (req, res) => { + User.findOne({where: {username: req.params.username}}) + .then((user) => { + if(user) { + console.log(user.dataValues, "USer data values"); + res.json({success: true, user: Object.assign({}, user.dataValues, {password: null})}); + } else { + res.json({success: false, user: null}); + } + }); + }); + + router.get('/user', (req, res) => { + User.findOne({where: {username: req.user.username}}) + .then((user) => { + res.json({success: true, user: user.dataValues}); + }); + }); + + router.post('/post/new', (req, res) => { + Post.create({ + fk_post_id: req.body.postId, + img: req.body.img, + description: req.body.description, + fk_user_id: req.user.id, + title: req.body.title + }) + .then(() => { + console.log('then inside'); + res.json({success: true}); + }) + .catch((error)=>{ + console.log('inside catch', error); + res.json({success: false, error: error}); + }); + }); + + async function recurse(post) { + try { + var comments = await Post.findAll({where: {fk_post_id: post.id}}); + if (!comments) { + return Object.assign({}, post); + } + return Object.assign({}, post, {children: comments.map((comment) => recurse(comment)) + }); + } catch (err) { + return false; + } + } + + router.get('/post/:id', (req, res) => { + Post.findOne({where: {id: req.params.id}}) + .then(async (post) => { + var comments = await recurse(post.dataValues); + if(!comments) { + res.json({ + success: false, + comments: null + }); + } + console.log(comments, 'COMMENTS AFTER RECURSE'); + res.json({ + success: true, + postContents: comments + }); + }) + .catch((err) => { + console.log('Inside catch for findOne'); + res.json({ + success: false, + error: err + }); + }); + }); + + + // SAMPLE ROUTE + router.use('/users', (req, res) => { + res.json({ success: true }); + }); + + return router; +}; diff --git a/frontend/actions/index.js b/frontend/actions/index.js index 2bba168..28433d5 100644 --- a/frontend/actions/index.js +++ b/frontend/actions/index.js @@ -1,3 +1,8 @@ // Action Creators // import * as types from './types'; +export function toggleLoginModal() { + return { + type: 'TOGGLE_LOGIN_MODAL' + }; +} diff --git a/frontend/assets/stylesheets/base.scss b/frontend/assets/stylesheets/base.scss index 1ddbba3..190630b 100644 --- a/frontend/assets/stylesheets/base.scss +++ b/frontend/assets/stylesheets/base.scss @@ -1,4 +1,42 @@ h1 { font-family: helvetica; font-weight: 200; -} \ No newline at end of file +} + +.headerContent { + background-image: url("http://pic.templetons.com/brad/pano/sfba/best-treas.jpg"); + height: 100px; + background-size: cover; + background-repeat: no-repeat; + border: 5px solid orange; +} + +.postContent { + display: flex; + border-radius: 5px; + border: 5px solid black; + padding-top: 100px; + width: 500px; +} + +.commentHolder { + position: relative; + left: 100px; +} + +.comment { + display: flex; +} + +.postHeader { + display: flex; + padding: 10px; + padding-bottom: 20px; + border-bottom: 2px solid grey; +} + +.postBody { + display: flex; + justify-content: space-between; + padding: 20px +} diff --git a/frontend/components/Comment.js b/frontend/components/Comment.js new file mode 100644 index 0000000..cb43804 --- /dev/null +++ b/frontend/components/Comment.js @@ -0,0 +1,59 @@ +var React = require('react'); +import PropTypes from 'prop-types'; +// var { Navbar, FormGroup, FormControl, Button, Checkbox, Col, Form, ControlLabel, HelpBlock} = require('react-bootstrap'); +// var axios = require('axios'); + +// This assumes we are passing in posts +// Each post has: img (string url) and description + + +class Comment extends React.Component { + constructor() { + super(); + this.state = { + hideChildren: true + } + } + + + clickHandler() { + this.setState({ + hideChildren: false + }) + } + + render() { + return ( +
+ + +
+ {this.props.comment.content.description} +
+
this.clickHandler()}> + {this.state.children.length} comments +
+ {this.state.hideChildren ? null : + this.props.comment.children.map((comment) => { + return ( +
+ +
+ ) + }) + }) +
+ ); + } +}; + +Comment.propTypes = { + comment: PropTypes.object, +}; + + +export default Comment; diff --git a/frontend/components/Description.js b/frontend/components/Description.js new file mode 100644 index 0000000..06db410 --- /dev/null +++ b/frontend/components/Description.js @@ -0,0 +1,17 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +const Description = ( { description } ) => { + return ( +
+

{description}

+
+ ); +}; + +Description.propTypes = { + description: PropTypes.string, +}; + + +export default Description; diff --git a/frontend/components/Feed.js b/frontend/components/Feed.js new file mode 100644 index 0000000..9b1805b --- /dev/null +++ b/frontend/components/Feed.js @@ -0,0 +1,92 @@ +var React = require('react'); +var { Navbar, FormGroup, FormControl, Button, Checkbox, Col, Form, ControlLabel, HelpBlock} = require('react-bootstrap'); +var axios = require('axios'); +var { Redirect } = require('react-router'); +// This assumes we are passing in posts +// Each post has: img (string url) and description +let posts = [{img: 'https://upload.wikimedia.org/wikipedia/commons/thumb/8/8b/Moose_superior.jpg/1200px-Moose_superior.jpg', +title: "This is what humans call a 'moose' "},{img: 'https://upload.wikimedia.org/wikipedia/commons/thumb/8/8b/Moose_superior.jpg/1200px-Moose_superior.jpg', +title: "This is what humans call a 'moose' "},{img: 'https://upload.wikimedia.org/wikipedia/commons/thumb/8/8b/Moose_superior.jpg/1200px-Moose_superior.jpg', +title: "This is what humans call a 'moose' "},{img: 'https://upload.wikimedia.org/wikipedia/commons/thumb/8/8b/Moose_superior.jpg/1200px-Moose_superior.jpg', +title: "This is what humans call a 'moose' "},{img: 'https://upload.wikimedia.org/wikipedia/commons/thumb/8/8b/Moose_superior.jpg/1200px-Moose_superior.jpg', +title: "This is what humans call a 'moose' "},{img: 'https://upload.wikimedia.org/wikipedia/commons/thumb/8/8b/Moose_superior.jpg/1200px-Moose_superior.jpg', +title: "This is what humans call a 'moose' "},{img: 'https://upload.wikimedia.org/wikipedia/commons/thumb/8/8b/Moose_superior.jpg/1200px-Moose_superior.jpg', +title: "This is what humans call a 'moose' "},{img: 'https://upload.wikimedia.org/wikipedia/commons/thumb/8/8b/Moose_superior.jpg/1200px-Moose_superior.jpg', +title: "This is what humans call a 'moose' "},{img: 'https://upload.wikimedia.org/wikipedia/commons/thumb/8/8b/Moose_superior.jpg/1200px-Moose_superior.jpg', +title: "This is what humans call a 'moose' "},{img: 'https://upload.wikimedia.org/wikipedia/commons/thumb/8/8b/Moose_superior.jpg/1200px-Moose_superior.jpg', +title: "This is what humans call a 'moose' "},{img: 'https://upload.wikimedia.org/wikipedia/commons/thumb/8/8b/Moose_superior.jpg/1200px-Moose_superior.jpg', +title: "This is what humans call a 'moose' "},{img: 'https://upload.wikimedia.org/wikipedia/commons/thumb/8/8b/Moose_superior.jpg/1200px-Moose_superior.jpg', +title: "This is what humans call a 'moose' "},{img: 'https://upload.wikimedia.org/wikipedia/commons/thumb/8/8b/Moose_superior.jpg/1200px-Moose_superior.jpg', +title: "This is what humans call a 'moose' "},{img: 'https://upload.wikimedia.org/wikipedia/commons/thumb/8/8b/Moose_superior.jpg/1200px-Moose_superior.jpg', +title: "This is what humans call a 'moose' "},{img: 'https://upload.wikimedia.org/wikipedia/commons/thumb/8/8b/Moose_superior.jpg/1200px-Moose_superior.jpg', +title: "This is what humans call a 'moose' "},{img: 'https://media.licdn.com/mpr/mpr/AAEAAQAAAAAAAALwAAAAJDliZTVkOTcyLWQ0NzUtNDNlZC1hZmEwLTY1NTQ0ZDBjNTE5ZA.jpg', +title: "This is what humans call a 'moose' "},{img: 'https://upload.wikimedia.org/wikipedia/commons/thumb/8/8b/Moose_superior.jpg/1200px-Moose_superior.jpg', +title: "This is what humans call a 'moose' "}, +{img: 'http://i.telegraph.co.uk/multimedia/archive/02622/geese_2622145b.jpg', +title: "This is what humans call 'geese'. The singular term is 'goose'. They are notorious for pooping in groups over the skies."}] + +class Feed extends React.Component { + constructor(){ + super(); + this.state = { + posts: posts, + redirect: false, + post_id: null + } + } + + componentDidMount() { + axios.get('http://localhost:3000/api/posts/all', { + withCredentials: true + }) + .then((resp) => { + this.setState({ + posts: [...this.state.posts, ...resp.data.posts] + }) + }) + .catch(e => { + console.log(e); + }) + } + postClick(i) { + console.log('clicked!'); + this.setState({ + redirect: true, + post_id: this.state.posts[i].id + }); + } + render() { + return ( + this.state.redirect ? : +
+ { + this.state.posts.map((post, i) => { + return ( +
+ +
+ + + + {post.title} +

{post.description}

+ this.postClick(i)}> + {post.title} + +
+ +
+ ); + }) + } +
+ ); + } + } + + +export default Feed; diff --git a/frontend/components/Header.js b/frontend/components/Header.js new file mode 100644 index 0000000..76ca778 --- /dev/null +++ b/frontend/components/Header.js @@ -0,0 +1,49 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Navbar, Nav, NavDropdown, MenuItem } from 'react-bootstrap'; + +const Header = ( { name } ) => { + return ( + + {/* + */} +
+
+
+ +
+
+
+ R/ HORIZONS +
+
+ {/* */} +
+
+ + {/*
+
*/} + +
+ ); + }; + + Header.propTypes = { + name: PropTypes.string, + }; + + + export default Header; diff --git a/frontend/components/Login.js b/frontend/components/Login.js new file mode 100644 index 0000000..f682276 --- /dev/null +++ b/frontend/components/Login.js @@ -0,0 +1,110 @@ +import React from 'react'; +import { Button, Modal, Glyphicon } from 'react-bootstrap'; +import axios from 'axios'; + +class Login extends React.Component { + constructor(props) { + super(props); + this.username = this.refs.username; + this.password = this.refs.password; + this.rPassword = this.refs.rPassword; + this.state = { + modalOpen: false, + signUp: false + }; + } + modalOpen() { + this.setState({ + modalOpen: !this.state.modalOpen + }); + } + onSignUp() { + this.setState({ + signUp: true + }); + } + login() { + axios.post('http://localhost:3000/api/login', { + username: this.refs.username.value, + password: this.refs.password.value + }, { + withCredentials: true + }) + .then((resp) => { + console.log(resp.data); + this.setState({ + modalOpen: false + }, () => this.props.onLogin()); + }) + .catch(err => { + console.log(err); + }); + } + register() { + axios.post('http://localhost:3000/api/register', { + username: this.refs.username.value, + password: this.refs.password.value, + repeatPassword: this.refs.rPassword.value + }) + .then((resp) => { + this.setState({ + modalOpen: false, + signUp: true + }, this.onSignUp()); + }) + .catch(err => { + console.log(err); + }); + } + + render() { + return ( +
+ + this.modalOpen()}> + + Hey Fam! + + + + {(this.state.signUp) ? ( +
+ + + +
+ ) : ( +
+ + +
+ )} + +
+ + + {(this.state.signUp) ? ( + + ) : ( +
+ + + +
+ )} + +
+ +
+
+ ); + } +} + + +export default Login; diff --git a/frontend/components/NewPost.js b/frontend/components/NewPost.js new file mode 100644 index 0000000..e5a7650 --- /dev/null +++ b/frontend/components/NewPost.js @@ -0,0 +1,120 @@ +var React = require('react'); +var {Redirect} = require('react-router'); +var {FormGroup, FormControl, Col, Form, ControlLabel, HelpBlock} = require('react-bootstrap'); +var axios = require('axios'); + +class NewPost extends React.Component { + constructor(props) { + super(props); + this.state = { + redirect: false, + title: '', + description: '', + link: '', + image: '' + }; + } + + postNewPost() { + axios.post('http://localhost:3000/api/post/new', { + postId: null, + img: this.state.link, + description: this.state.description, + title: this.state.title + }, { + withCredentials: true + }) + .catch(e => { + console.log("Posting new post failed", e); + }); + } + onSubmitPost() { + this.setState({ + redirect: true + }, () => this.postNewPost()); + } + onTitleChange(e) { + this.setState({ + title: e.target.value + }); + } + onDescriptionChange(e) { + this.setState({ + description: e.target.value + }); + } + onLinkChange(e) { + this.setState({ + link: e.target.value + }); + } + onImgSelect(e) { + this.setState({ + image: e.target.value + }); + } + render() { + function FieldGroup({ id, type, label, help }) { + return ( + + {label} + + {help && {help}} + + ); + } + return ( + (this.state.redirect) ? () : ( +
+ + + Title + + + this.onTitleChange(e)}/> + + + + + + Description + + + this.onDescriptionChange(e)}/> + + + + + + Links + + + this.onLinkChange(e)}/> + + + + {/* + Select image + + + this.onImgSelect(e)} + /> + */} + + + + + + ) + ); + } +} + +export default NewPost; diff --git a/frontend/components/SideBar.js b/frontend/components/SideBar.js new file mode 100644 index 0000000..abbf373 --- /dev/null +++ b/frontend/components/SideBar.js @@ -0,0 +1,49 @@ +import React from 'react'; +import { Panel, Button } from 'react-bootstrap'; +import SubmitPost from './SubmitPost'; +import Login from './Login'; +import Description from './Description'; +import axios from 'axios'; + +class SideBar extends React.Component { + constructor(props) { + super(props); + this.state = { + loggedIn: false, + description: "I don't know what you need here." + }; + } + onLogin() { + this.setState({ + loggedIn: true + }); + } + logout() { + axios.get('http://localhost:3000/api/logout', { + withCredentials: true + }) + .then((resp) => { + console.log(resp.data); + this.setState({ + loggedIn: false + }); + }) + .catch(err => { + console.log(err); + }); + } + render() { + return ( +
+ + {(this.state.loggedIn) ? () + : ( this.onLogin()} />)} + + + +
+ ); + } +} + +export default SideBar; diff --git a/frontend/components/SubmitPost.js b/frontend/components/SubmitPost.js new file mode 100644 index 0000000..c3098fd --- /dev/null +++ b/frontend/components/SubmitPost.js @@ -0,0 +1,30 @@ +import React from 'react'; +import { Button } from 'react-bootstrap'; +import { Redirect } from 'react-router'; + +class SubmitPost extends React.Component { + constructor(props) { + super(props); + this.state = { + redirect: false + }; + } + newPostPage() { + this.setState({ + redirect: true + }); + } + render() { + return ( +
+ { + (this.state.redirect) ? () + : () + } +
+ ); + } +} + + +export default SubmitPost; diff --git a/frontend/containers/AppContainer.js b/frontend/containers/AppContainer.js index 7c29205..5bbfaab 100644 --- a/frontend/containers/AppContainer.js +++ b/frontend/containers/AppContainer.js @@ -1,28 +1,39 @@ import PropTypes from 'prop-types'; import React from 'react'; import { connect } from 'react-redux'; -import Title from '../components/Title'; +import Header from '../components/Header'; +import DevTools from './DevTools'; +import SideBar from '../components/SideBar'; +import Feed from '../components/Feed'; +import NewPost from '../components/NewPost'; -const AppContainer = ({ name }) => { + +import { toggleLoginModal } from '../actions/index'; + +const AppContainer = ({ toggleLogin }) => { return (
- + <DevTools /> + <Header /> + <Feed /> + <SideBar toggleLogin={toggleLogin} /> </div> ); }; AppContainer.propTypes = { - name: PropTypes.string, + toggleLogin: PropTypes.func }; const mapStateToProps = (state) => { return { - name: state.name + state: state }; }; -const mapDispatchToProps = (/* dispatch */) => { +const mapDispatchToProps = (dispatch) => { return { + toggleLogin: () => {dispatch(toggleLoginModal());} }; }; diff --git a/frontend/containers/PostPage.js b/frontend/containers/PostPage.js new file mode 100644 index 0000000..e6d9efc --- /dev/null +++ b/frontend/containers/PostPage.js @@ -0,0 +1,72 @@ +var React = require('react'); +// var { Navbar, FormGroup, FormControl, Button, Checkbox, Col, Form, ControlLabel, HelpBlock} = require('react-bootstrap'); +var axios = require('axios'); +import Comment from '../components/Comment'; +import Header from '../components/Header'; +import Sidebar from '../components/SideBar'; + + +// This assumes we are passing in posts +// Each post has: img (string url) and description + + +class PostPage extends React.Component { + constructor() { + super(); + this.state = { + post: {} + }; + } + + componentDidMount() { + + console.log(this.props.match.params.id, 'ID parameter'); + axios.get(`http://localhost:3000/api/post/`+ this.props.match.params.id, + { + withCredentials: true + }) + .then((comments) => { + console.log(comments.data); + this.setState({ + post: comments.data.postContents + }); + }) + .catch(e => { + console.log(e); + }); + } + render() { + return ( + <div> + <Header /> + <Sidebar /> + <div className="postContent"> + <div className="postHeader"> + <button type="button" className="btn" style={{margin: 5}}> + <span className="glyphicon glyphicon-arrow-up"/> + </button> + <button type="button" className="btn" style={{margin: 5}}> + <span className="glyphicon glyphicon-arrow-down"/> + </button> + <div>{this.state.post.title}</div> + </div> + <div> + <div><img src={this.state.post.img} style={{width: '300px'}}/></div> + <div>{this.state.post.description}</div> + {/* <div className="postBody"> + {this.state.post.children.map((comment) => { + return( + <div className="commentHolder"> + <Comment comment={comment}/> + </div> + ) + })} + </div> */} + </div> + </div> + </div> + ); + } + } + +export default PostPage; diff --git a/frontend/containers/Root.dev.js b/frontend/containers/Root.dev.js index c0f8d69..9e3a138 100644 --- a/frontend/containers/Root.dev.js +++ b/frontend/containers/Root.dev.js @@ -1,20 +1,31 @@ import PropTypes from 'prop-types'; import React from 'react'; import {Provider} from 'react-redux'; +import Feed from '../components/Feed.js'; +import NewPost from '../components/NewPost.js'; +import Login from '../components/Login'; +import SubmitPost from '../components/SubmitPost'; +import { HashRouter, Route, Switch } from 'react-router-dom'; import AppContainer from './AppContainer.js'; -import DevTools from './DevTools'; +import PostPage from './PostPage.js'; + export default function Root({ store }) { return ( - <Provider store={store}> - <div> - <AppContainer /> - <DevTools /> - </div> - </Provider> + <Provider store={store}> + <HashRouter> + <Switch> + <Route exact path="/" component={AppContainer}/> + <Route exact path="/login" component={Login}/> + <Route exact path="/feed" component={Feed}/> + <Route exact path="/post/new" component={NewPost}/> + <Route exact path="/submit/new" component={SubmitPost}/> + <Route exact path="/post/page/:id" component={PostPage}/> + </Switch> + </HashRouter> + </Provider> ); } - Root.propTypes = { store: PropTypes.object.isRequired }; diff --git a/frontend/index.js b/frontend/index.js index 411485c..f2fa945 100644 --- a/frontend/index.js +++ b/frontend/index.js @@ -1,11 +1,13 @@ import React from 'react'; import { render } from 'react-dom'; -import { configureStore, history } from './store/configureStore'; +import { configureStore } from './store/configureStore'; +import createHistory from 'history/createBrowserHistory'; import Root from './containers/Root'; import './assets/stylesheets/base.scss'; -const store = configureStore(); +export const store = configureStore(); +export const history = createHistory(); render( <Root store={store} history={history} />, diff --git a/frontend/reducers/index.js b/frontend/reducers/index.js index 4d415fd..f7f0bcb 100644 --- a/frontend/reducers/index.js +++ b/frontend/reducers/index.js @@ -1,5 +1,12 @@ -function rootReducer(state = {name: 'Horizons'}, action) { +function rootReducer(state = { + isModalOpen: false, + signUp: false}, action) { switch (action.type) { + case "TOGGLE_LOGIN_MODAL": + console.log(state.isModalOpen); + return Object.assign({}, state, {isModalOpen: !state.isModalOpen}); + case "SIGN_UP": + return Object.assign({}, state, {signUp: !state.signUp}); default: return state; } diff --git a/models.js b/models.js new file mode 100644 index 0000000..79aa118 --- /dev/null +++ b/models.js @@ -0,0 +1,68 @@ +"use strict"; + +var Sequelize = require('sequelize'); +var sequelize = new Sequelize(process.env.DATABASE_NAME, 'postgres', process.env.DATABASE_PASSWORD, { + dialect: 'postgres' +}); + +sequelize +.authenticate() +.then(() => { + console.log('Connection has been established successfully.'); +}) +.catch(err => { + console.error('Unable to connect to the database:', err); +}); + +var User = sequelize.define('user', { + id: { + type: Sequelize.INTEGER, + primaryKey: true, + autoIncrement: true + }, + username: { + type: Sequelize.STRING, + allowNull: false, + unique: true + }, + password: { + type: Sequelize.STRING, + allowNull: false + }, + // ADD MORE ATTRIBUTES HERE +}); + +var Post = sequelize.define('posts', { + id: { + type: Sequelize.INTEGER, + primaryKey: true, + autoIncrement: true + }, + fk_post_id: { + type: Sequelize.INTEGER, + allowNull: true + }, + img: { + type: Sequelize.STRING, + allowNull: true + }, + description: { + type: Sequelize.STRING, + allowNull: true + }, + title: { + type: Sequelize.STRING, + allowNull: true + }, + fk_user_id: { + type: Sequelize.INTEGER, + sllowNull: false + } +}); + +module.exports = { + sequelize, + User, + Post, + // EXPORT models HERE +}; diff --git a/package.json b/package.json index 54019e8..ee3524d 100644 --- a/package.json +++ b/package.json @@ -14,26 +14,40 @@ "author": "", "license": "ISC", "dependencies": { + "axios": "^0.17.0", "babel-core": "^6.24.1", "babel-loader": "^6.4.1", "babel-preset-es2015": "^6.24.1", "babel-preset-react": "^6.24.1", + "bcrypt": "^1.0.3", + "body-parser": "^1.18.2", "css-loader": "^0.28.0", "enzyme": "^2.8.1", "expect": "^1.20.2", "express": "^4.15.2", + "express-session": "^1.15.6", + "history": "^4.7.2", "mocha": "^3.2.0", "node-sass": "^4.5.2", + "passport": "^0.4.0", + "passport-local": "^1.0.0", + "pg": "^7.4.0", + "pg-hstore": "^2.3.2", "prop-types": "^15.5.8", "react": "^15.5.4", + "react-bootstrap": "^0.31.5", "react-dom": "^15.5.4", "react-redux": "^5.0.5", + "react-router": "^4.2.0", + "react-router-dom": "^4.2.2", "redux": "^3.7.2", "redux-devtools": "^3.4.0", "redux-devtools-dock-monitor": "^1.1.2", "redux-devtools-log-monitor": "^1.3.0", "sass-loader": "^6.0.3", + "sequelize": "^4.10.0", "style-loader": "^0.16.1", + "underscore": "^1.8.3", "webpack": "^2.3.3" }, "devDependencies": { diff --git a/public/index.html b/public/index.html index 9604aed..a56a6f3 100644 --- a/public/index.html +++ b/public/index.html @@ -3,9 +3,10 @@ <head> <meta charset="utf-8"> <title>Node + React Starter +
- \ No newline at end of file + diff --git a/server.js b/server.js index ac6e4f3..8fcca08 100644 --- a/server.js +++ b/server.js @@ -1,16 +1,57 @@ const path = require('path'); const express = require('express'); +const session = require('express-session'); const app = express(); +const bodyParser = require('body-parser'); +const passport = require('passport'); +const LocalStrategy = require('passport-local'); +const bcrypt = require('bcrypt'); +const { User } = require('./models'); const PORT = process.env.PORT || 3000; const api = require('./backend/routes'); app.use(express.static(path.join(__dirname, 'public'))); - +app.use(bodyParser.json()); +app.use(bodyParser.urlencoded({ extended: false })); app.get('/', (request, response) => { response.sendFile(__dirname + '/public/index.html'); // For React/Redux }); -app.use('/api', api); +app.use(session({ + secret: 'a4f8071f-c873-4447-8ee2', +})); + +passport.serializeUser(function(user, done) { + done(null, user.id); +}); + +passport.deserializeUser((id, done) => { + User.findOne({where: {id: id}}) + .then(user => done(null, user.dataValues)) + .catch((err) => {throw new Error(err);}); +}); + +passport.use(new LocalStrategy((username, password, done) => { + User.findOne({where: {username: username}}) + .then( (user) => { + if(user) { + bcrypt.compare(password, user.dataValues.password, (err, res) => { + if (res) { + return done(null, user.dataValues); + } + return done(null, false); + }); + } else { + return done(null, false); + } + }) + .catch((err) => {return done(err);}); +})); + +app.use(passport.initialize()); +app.use(passport.session()); + +app.use('/api', api(passport)); app.listen(PORT, error => { error diff --git a/sync.js b/sync.js new file mode 100644 index 0000000..d894fc9 --- /dev/null +++ b/sync.js @@ -0,0 +1,13 @@ +"use strict"; + +var models = require('./models'); + +models.sequelize.sync({ force: true }) + .then(function() { + console.log('Successfully updated database tables!'); + process.exit(0); + }) + .catch(function(error) { + console.log('Error updating database tables', error); + process.exit(1); + });