diff --git a/.eslintrc b/.eslintrc index cd8434b..1c86c5e 100644 --- a/.eslintrc +++ b/.eslintrc @@ -14,7 +14,7 @@ "__DEV__": true }, parserOptions: { - ecmaVersion: 6, + ecmaVersion: 8, # sourceType: 'module', ecmaFeatures: { "experimentalObjectRestSpread": true, @@ -123,7 +123,7 @@ */ "indent": [ 2, - 4, + 2, { "SwitchCase": 1 } ], // http://eslint.org/docs/rules/indent "brace-style": [ diff --git a/backend/auth.js b/backend/auth.js new file mode 100644 index 0000000..f12ce4b --- /dev/null +++ b/backend/auth.js @@ -0,0 +1,44 @@ +const express = require('express'); +const router = express.Router(); +const {User} = require('./models'); +const axios = require('axios'); + +module.exports = (passport) => { + router.post('/register', (req, res) => { + User.create({username: req.body.username, password: req.body.password}) + .then(user => { + // axios.post('/login', {user}) + // .then(resp => {console.log(resp)}) + // .catch(err => {console.log(err)}) + res.json({user}); + }) + .catch(err => { + res.json({err}); + }); + }); + + router.post('/login', passport.authenticate('local'), (req, res) => { + res.json({user: req.user}); + }); + + router.get('/logout', (req, res) => { + req.logout(); + res.redirect('/'); + }); + + router.get('/:username', (req, res) => { + User.findOne({where: {username: req.params.username}}) + .then(user => { + res.json({user}); + }) + .catch(err => { + res.json({err}); + }); + }); + + router.get('/', (req, res) => { + res.json({user: req.user}); + }); + + return router; +}; diff --git a/backend/models.js b/backend/models.js new file mode 100644 index 0000000..4614afd --- /dev/null +++ b/backend/models.js @@ -0,0 +1,73 @@ +"use strict"; + +var Sequelize = require('sequelize'); +var sequelize = new Sequelize(process.env.DATABASE_NAME, 'postgres', null, { + dialect: 'postgres' +}); + +sequelize.authenticate() +.then(() => { + console.log('Connection has been established successfully.'); +}) +.catch(err => { + console.error('Unable to connect to the database:', err); +}); + +const 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 + }, +}); + +const Post = sequelize.define('post', { + id: { + type: Sequelize.INTEGER, + primaryKey: true, + autoIncrement: true + }, + title: { + type: Sequelize.STRING, + allowNull: false, + }, + body: { + type: Sequelize.STRING, + }, + parent_id: { + type: Sequelize.INTEGER, + }, +}); + +const Vote = sequelize.define('vote', { + id: { + type: Sequelize.INTEGER, + primaryKey: true, + autoIncrement: true + }, + isUpvote: { + type: Sequelize.INTEGER, + allowNull: false, + }, +}); + +Post.belongsTo(User); +Vote.belongsTo(User); +Vote.belongsTo(Post); +Post.hasMany(Vote, { as: 'votes', foreignKey: 'postId' }); + +module.exports = { + sequelize, + User, + Post, + Vote +}; diff --git a/backend/routes.js b/backend/routes.js index 9dd2215..2a50f96 100644 --- a/backend/routes.js +++ b/backend/routes.js @@ -1,11 +1,99 @@ const express = require('express'); const router = express.Router(); +const {Post, Vote} = require('./models'); +var sequelize = require('sequelize'); -// YOUR API ROUTES HERE +router.post('/post/new', (req, res) => { + Post.create({userId: req.user.id, title: req.body.title, body: req.body.body}) + .then(() => res.json({msg: 'new post successful'})) + .catch(err => res.json({err})); +}); + +router.get('/post/all', (req, res) => { + Post.findAll({ where: { parent_id: null }}) + .then(resp => res.json({posts: resp})); +}); + +router.post('/post/:id', (req, res) => { + Post.create({userId: req.user.id, title: req.body.title, body: req.body.body, parent_id: req.params.id}) + .then( resp => res.json(resp)); +}); + +router.get('/post/:id', (req, res) => { + Post.findAll({ + where: {id: req.params.id}, + attributes: ['id', 'title', 'body', 'createdAt', 'userId', 'parent_id', + [sequelize.fn('sum', sequelize.col('votes.isUpvote')), 'votecount'] + ], + include: [ { model: Vote, as: 'votes' } ], + group: ['post.id', 'votes.id'] + }) + .then( resp => {// TODO @ROB EXPLAIN TO ME WHAT THE FUCK IS GOING ON HERE + const childrenObj = {}; + childrenObj[resp[0].dataValues.id] = resp[0].dataValues; + childrenObj[resp[0].dataValues.id].children = []; + let recurseLeft = 1; + const find = (children) => { + recurseLeft --; + if (!children.length && !recurseLeft) res.json(childrenObj[req.params.id]); + children.forEach(child => { + if (child.dataValues.id !== parseInt(req.params.id, 10)) { + childrenObj[child.dataValues.parent_id].children.push(child.dataValues); + childrenObj[child.dataValues.id] = child.dataValues; + childrenObj[child.dataValues.id].children = []; + } + recurseLeft ++; + (async function x() { + var resp = await Post.findAll({ + where: {parent_id: child.id}, + attributes: ['id', 'title', 'body', 'createdAt', 'userId', 'parent_id', [sequelize.fn('sum', sequelize.col('votes.isUpvote')), 'votecount']], + include: [ + { model: Vote, as: 'votes' } + ], + group: ['post.id', 'votes.id'] + }); + var resp = await find(resp); + return resp; + })() + }); + }; + find(resp); + }); +}); + +router.get('/post/:id/:vote', (req, res) => { + Vote.findOne({where: {postId: req.params.id, userId: req.user.id}}) + .then( resp => { + + if(resp) { + // They upvoted an upvote --> destroy vote + if(resp.dataValues.isUpvote && req.params.vote === 'upvote') { + Vote.destroy({where: {postId: req.params.id, userId: req.user.id}}) + .then(() => res.json({success: 'true'})); + } + + // They downvoted an upvote --> change isUpvote to false + else if(resp.dataValues.isUpvote && req.params.vote === 'downvote') { + Vote.update({isUpvote: -1}, {where: {postId: req.params.id, userId: req.user.id}}) + .then(() => res.json({success: 'true'})); + } + + // They upvoted a downvote --> change isUpvote to true + if(!resp.dataValues.isUpvote && req.params.vote === 'upvote') { + Vote.update({isUpvote: 1}, {where: {postId: req.params.id, userId: req.user.id}}) + .then(() => res.json({success: 'true'})); + } -// SAMPLE ROUTE -router.use('/users', (req, res) => { - res.json({ success: true }); + // They downvoted a downvote --> destroy vote + else if(!resp.dataValues.isUpvote && req.params.vote === 'downvote') { + Vote.destroy({where: {postId: req.params.id, userId: req.user.id}}) + .then(() => res.json({success: 'true'})); + } + } else { + Vote.create({postId: req.params.id, userId: req.user.id, isUpvote: req.params.vote === 'upvote' ? 1 : -1}) + .then(() => res.json({success: 'true'})); + } + }); }); module.exports = router; diff --git a/backend/sync.js b/backend/sync.js new file mode 100644 index 0000000..6d81270 --- /dev/null +++ b/backend/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); +}); diff --git a/env.sh b/env.sh new file mode 100644 index 0000000..6ac5e29 --- /dev/null +++ b/env.sh @@ -0,0 +1,2 @@ +export DATABASE_URL=postgresql://postgres@localhost/steemit +export DATABASE_NAME=steemit diff --git a/frontend/assets/stylesheets/appcontainer.css b/frontend/assets/stylesheets/appcontainer.css new file mode 100644 index 0000000..abaa5c1 --- /dev/null +++ b/frontend/assets/stylesheets/appcontainer.css @@ -0,0 +1,33 @@ +html, body { + width: 100%; + height: 100%; + padding: 0; + margin: 0; + background-color: #BDBDBD; +} + +#root { + width: 100%; + height: 100%; +} + +.appcontainer_container { + display: flex; + width: 100%; + height: 100%; + flex-direction: column; +} + +.appcontainer_header_container { + display: flex; + width: 100%; + height: 75px; + flex: 1; +} + +.appcontainer_body_container { + display: flex; + width: 100%; + height: 100%; + flex: 10; +} diff --git a/frontend/assets/stylesheets/feed.css b/frontend/assets/stylesheets/feed.css new file mode 100644 index 0000000..4cb2874 --- /dev/null +++ b/frontend/assets/stylesheets/feed.css @@ -0,0 +1,12 @@ +.feed_container { + background-color: #BDBDBD; + width: 100%; + flex: 5; +} + +.feed_body { + height: 97%; + width: 96%; + margin: auto; + margin-top: 2%; +} diff --git a/frontend/assets/stylesheets/header.css b/frontend/assets/stylesheets/header.css new file mode 100644 index 0000000..a4d12b8 --- /dev/null +++ b/frontend/assets/stylesheets/header.css @@ -0,0 +1,29 @@ +.header_container { + width: 100%; + overflow: hidden; + flex: 1; + position: fixed; + height: 100px; +} + +.header_content { + height: 98px; + width: 100%; + display: flex; +} + +.header_container_logo { + max-height: 100%; + max-width: 30%; + width: auto; + height: auto; + margin-left: 5px; + padding: 10px +} + + .header_container_title{ + color: #06d6a8; + font-family: helvetica, sans-serif; + font-size: 40px; + font-weight: bold; + } diff --git a/frontend/assets/stylesheets/sidebar.css b/frontend/assets/stylesheets/sidebar.css new file mode 100644 index 0000000..aaa8d6d --- /dev/null +++ b/frontend/assets/stylesheets/sidebar.css @@ -0,0 +1,51 @@ +.sidebar_container { + width: 100%; + flex: 1; + height: 100%; + margin-right: 10px; +} + +.sidebar_content { + width: 100%; + height: 30%; + margin-top: 10%; +} + +.submit_post_button { + width: 100%; +} + +.logout_button { + width: 100%; +} + +.login_register_button_container { + margin-top: 10px; + display: flex; +} + +.login_button { + flex: 1; + margin-right: 5px; +} + +.register_button { + flex: 1; +} + +.description_right_container { + display: flex; + flex-direction: column; + margin-top: 10px; +} + +.right_sidebar_description_title { + flex: 1; + margin: 5px; +} + +.right_sidebar_description_content { + flex: 1; + margin-top: 0; + margin: 5px; +} diff --git a/frontend/components/Feed.js b/frontend/components/Feed.js new file mode 100644 index 0000000..ab358f3 --- /dev/null +++ b/frontend/components/Feed.js @@ -0,0 +1,47 @@ +import React from 'react'; +import {Link} from 'react-router-dom'; +import styles from '../assets/stylesheets/feed.css' +import {Paper, RaisedButton} from 'material-ui'; +import axios from 'axios'; + +class Feed extends React.Component { + constructor(props){ + super(props); + this.state = { + posts: [] + } + } + + componentDidMount() { + axios.get(localStorage.getItem('webAddress') + '/api/post/all') + .then(resp => { + console.log(resp); // TODO: add voteCounts to this GET route + this.setState({ + posts: resp.data.posts + }); + }); + } + + render() { + return ( +
+ +

All posts

+ {this.state.posts.map(post => { + return ( +
+

{post.title}

+

{post.body}

+
+ ); + })} +
+
+ ); + } +} + +export default Feed; diff --git a/frontend/components/Header.js b/frontend/components/Header.js new file mode 100644 index 0000000..62654dc --- /dev/null +++ b/frontend/components/Header.js @@ -0,0 +1,17 @@ +import React from 'react'; +import styles from '../assets/stylesheets/header.css' +import Paper from 'material-ui/Paper'; + +export default function Header() { + return ( +
+ + +

steemit

+
+
+ ); +}; diff --git a/frontend/components/NewPost.js b/frontend/components/NewPost.js new file mode 100644 index 0000000..c074a86 --- /dev/null +++ b/frontend/components/NewPost.js @@ -0,0 +1,49 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import axios from 'axios'; +import {TextField, RaisedButton} from 'material-ui'; + +class NewPost extends React.Component { + constructor(props) { + super(props); + this.state = { + title: '', + body: '', + } + } + + handleInputChange(event) { + const target = event.target; + const value = target.type === 'checkbox' ? target.checked : target.value; + const name = target.name; + this.setState({ + [name]: value + }); + } + + handleSubmitPost() { + axios.post(localStorage.getItem('webAddress') + '/api/post/new', this.state) + .then(resp => { + console.log(resp); + window.location.hash = '/' + }) + .catch(err => {console.log(err)}); + } + + render() { + return ( +
+ {this.state.title}, {this.state.body} +
+ +
+ +
+ + +
+ ); + } +} + +export default NewPost; diff --git a/frontend/components/PostPage.js b/frontend/components/PostPage.js new file mode 100644 index 0000000..df42d68 --- /dev/null +++ b/frontend/components/PostPage.js @@ -0,0 +1,121 @@ +import React from 'react'; +import axios from 'axios'; +import {TextField, RaisedButton} from 'material-ui'; +import FontIcon from 'material-ui/FontIcon'; +import IconButton from 'material-ui/IconButton'; +import KeyboardArrowUp from 'material-ui/svg-icons/hardware/keyboard-arrow-up'; +import KeyboardArrowDown from 'material-ui/svg-icons/hardware/keyboard-arrow-down'; + + +class NewPost extends React.Component { + constructor(props) { + super(props); + this.state = { + title: '', + comment: '', + data: { title: 'loading', body: 'loading', id: '0', userId: '0', parentId: 1, children: [] }, + } + this.handleVoteClick = this.handleVoteClick.bind(this); + this.handleSubmitPost = this.handleSubmitPost.bind(this); + } + + componentDidMount() { + axios.get(localStorage.getItem('webAddress') + '/api/post/' + this.props.postId) + .then(resp => { + this.setState({data: resp.data}); + console.log(resp.data); + }) + .catch(err => { + console.log(err); + }); + } + + handleInputChange(event) { + const target = event.target; + const value = target.type === 'checkbox' ? target.checked : target.value; + const name = target.name; + this.setState({ + [name]: value + }); + } + + handleSubmitPost(postId) { + axios.post(localStorage.getItem('webAddress') + '/api/post/' + (postId || this.props.postId), this.state) + .then(resp => { + console.log(resp); + window.location.hash = '/'; + }) + .catch(err => {console.log(err)}); + } + + handleVoteClick(voteType, postId) { + axios.get(localStorage.getItem('webAddress') + '/api/post/' + (postId || this.props.postId) + '/' + voteType) + .then(resp => console.log(resp)) + .catch(resp => console.log(resp)); + } + + render() { + const self = this; + function traverse(parent) { + if (!parent.children.length) { + return; + } + return parent.children.map(child => { + return ( +
+

{child.title} - user {child.userId}

+
+ {child.votecount} + self.handleVoteClick('upvote', child.id)} + > + + + self.handleVoteClick('downvote', child.id)} + > + + +
+ {child.body} (id: {child.id}) +
+ self.handleInputChange(e)} hintText='Title' /> +
+ self.handleInputChange(e)} hintText='Reply' multiLine={true} /> + self.handleSubmitPost(post.id)} label='Reply' secondary={true} /> + {traverse(child)} +
+ ); + }); + } + + return ( +
+ {this.state.comment} +

{this.state.data.title} - user {this.state.data.userId}

+

{this.state.data.body} (postId: {this.state.data.id})

+
+ {this.state.data.votecount} + this.handleVoteClick('upvote')} + > + + + this.handleVoteClick('downvote')} + > + + +
+ + + this.handleSubmitPost()} label='Comment' primary={true} /> + {traverse(this.state.data)} +
+ ); + + } + +} + +export default NewPost; diff --git a/frontend/components/Sidebar2.js b/frontend/components/Sidebar2.js new file mode 100644 index 0000000..2e87f18 --- /dev/null +++ b/frontend/components/Sidebar2.js @@ -0,0 +1,135 @@ +import React from 'react'; +import styles from '../assets/stylesheets/sidebar.css' +import { RaisedButton, Paper, FlatButton, Dialog, TextField } from 'material-ui'; +import LoginIcon from 'material-ui/svg-icons/social/person'; +import RegisterIcon from 'material-ui/svg-icons/social/person-add'; +import SubmitPostIcon from 'material-ui/svg-icons/editor/border-color'; +import axios from 'axios'; +const baseUrl = localStorage.getItem('webAddress') + '/api/user'; + +class Sidebar2 extends React.Component { + constructor(props) { + super(props); + this.state = { + modalIsOpen: false, + modalTitle: null, + username: "", + password: "", + user: null, + } + } + + handleSubmitUser() { + const endUrl = this.state.modalTitle === 'Login' ? '/login' : '/register'; + axios.post(baseUrl + endUrl, this.state) + .then(resp => { + this.setState({modalIsOpen: false, user: resp.data.user}); + this.props.login(resp.data.user); + }) + .catch(err => { + console.log(`err with ${endUrl}: ${err}`); + }); + } + + handleInputChange(key) { + return (e) => { + const state = {}; + state[key] = e.target.value; + this.setState(state); + }; + } + + render() { + const actionButtons = [ + this.setState({modalIsOpen: false})} + />, + , + ]; + + return (this.props.side === "left" + ?
+ : +
+
+ } + label="Submit Post" + backgroundColor="black" + labelColor="white" + onClick={() => this.state.user ? + window.location.hash = '/post/new' : + this.setState({modalIsOpen: true, modalTitle: 'Login'}) + } + /> + {this.state.user ? +
+

{this.state.username}

+ axios.get(baseUrl + '/logout').then(() => this.setState({user: null}))} + backgroundColor="white" + /> +
+ : +
+ } + labelColor="#06d6a8" + label='login' + onClick={() => this.setState({modalIsOpen: true, modalTitle: 'Login'})} + backgroundColor="white" + /> + } + labelColor="white" + label='register' + onClick={() => this.setState({modalIsOpen: true, modalTitle: 'Register'})} + backgroundColor="#06d6a8" + /> +
+ } + +

Description

+

+ This is the description of Steemit, an up and coming social media platform built on the Steem blockchain. Handling + hundreds of thousands of transactions a day with 3 second confirmmations and no fee's, this is a blockchain you don't want + to miss. +

+
+
+
+ + + + +
+
+ ); + } +} + +export default Sidebar2; diff --git a/frontend/containers/AppContainer.js b/frontend/containers/AppContainer.js index 7c29205..04583f8 100644 --- a/frontend/containers/AppContainer.js +++ b/frontend/containers/AppContainer.js @@ -1,32 +1,21 @@ -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 Feed from '../components/Feed'; +import Sidebar2 from '../components/Sidebar2'; -const AppContainer = ({ name }) => { - return ( -
- - </div> - ); -}; - -AppContainer.propTypes = { - name: PropTypes.string, -}; +import styles from '../assets/stylesheets/appcontainer.css' -const mapStateToProps = (state) => { - return { - name: state.name - }; +export default function AppContainer() { + return ( + <div className="appcontainer_container"> + <div className="appcontainer_header_container"> + <Header /> + </div> + <div className="appcontainer_body_container"> + <Sidebar2 side='left' /> + <Feed /> + <Sidebar2 side='right' /> + </div> + </div> + ); }; - -const mapDispatchToProps = (/* dispatch */) => { - return { - }; -}; - -export default connect( - mapStateToProps, - mapDispatchToProps -)(AppContainer); diff --git a/frontend/containers/Root.dev.js b/frontend/containers/Root.dev.js index c0f8d69..93eefae 100644 --- a/frontend/containers/Root.dev.js +++ b/frontend/containers/Root.dev.js @@ -1,20 +1,27 @@ -import PropTypes from 'prop-types'; import React from 'react'; import {Provider} from 'react-redux'; -import AppContainer from './AppContainer.js'; -import DevTools from './DevTools'; +import { HashRouter, Route, Switch } from 'react-router-dom'; +import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'; +import AppContainer from './AppContainer'; +import NewPost from '../components/NewPost' +import PostPage from '../components/PostPage' -export default function Root({ store }) { - return ( - <Provider store={store}> - <div> - <AppContainer /> - <DevTools /> - </div> - </Provider> - ); +export default function Root({ store, history }) { + // localStorage.setItem('webAddress', 'http://10.2.106.68:3000'); + localStorage.setItem('webAddress', 'http://localhost:3000'); + return ( + <Provider store={store}> + <MuiThemeProvider> + <HashRouter> + <Switch> + <Route exact path='/' component={AppContainer} /> + <Route exact path='/post/new' component={NewPost} /> + <Route exact path='/post/:postId' render={(props) => ( + <PostPage postId={props.match.params.postId} /> + )} /> + </Switch> + </HashRouter> + </MuiThemeProvider> + </Provider> + ); } - -Root.propTypes = { - store: PropTypes.object.isRequired -}; diff --git a/frontend/index.js b/frontend/index.js index 411485c..0c07068 100644 --- a/frontend/index.js +++ b/frontend/index.js @@ -3,6 +3,7 @@ import { render } from 'react-dom'; import { configureStore, history } from './store/configureStore'; import Root from './containers/Root'; +// Import styles import './assets/stylesheets/base.scss'; const store = configureStore(); diff --git a/frontend/reducers/index.js b/frontend/reducers/index.js index 4d415fd..77a4aa9 100644 --- a/frontend/reducers/index.js +++ b/frontend/reducers/index.js @@ -1,8 +1,16 @@ -function rootReducer(state = {name: 'Horizons'}, action) { - switch (action.type) { - default: - return state; - } +const defaultState = { + name: 'Horizons', + user: null, +}; + +function rootReducer(state = defaultState, action) { + switch (action.type) { + case ('USER'): + console.log(action); + return {user: action.user || state.user}; + default: + return state; + } } export default rootReducer; diff --git a/frontend/store/configureStore.dev.js b/frontend/store/configureStore.dev.js index f87288c..24d7ae3 100644 --- a/frontend/store/configureStore.dev.js +++ b/frontend/store/configureStore.dev.js @@ -1,12 +1,18 @@ -import { createStore, compose } from 'redux'; +import createHistory from 'history/createBrowserHistory'; +import { applyMiddleware, createStore, compose } from 'redux'; +import { routerMiddleware } from 'react-router-redux'; import rootReducer from '../reducers'; import DevTools from '../containers/DevTools'; +export const history = createHistory(); +const middleware = routerMiddleware(history); + export function configureStore(initialState) { return createStore( rootReducer, initialState, compose( + applyMiddleware(middleware), DevTools.instrument() ) ); diff --git a/package.json b/package.json index 54019e8..245fe38 100644 --- a/package.json +++ b/package.json @@ -14,25 +14,37 @@ "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", + "body-parser": "^1.18.2", + "cookie-session": "^2.0.0-beta.3", "css-loader": "^0.28.0", "enzyme": "^2.8.1", "expect": "^1.20.2", "express": "^4.15.2", + "history": "^4.7.2", + "material-ui": "^0.19.4", "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-dom": "^15.5.4", "react-redux": "^5.0.5", + "react-router-dom": "^4.2.2", + "react-router-redux": "^4.0.8", "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.22.5", "style-loader": "^0.16.1", "webpack": "^2.3.3" }, diff --git a/public/index.html b/public/index.html index 9604aed..249c296 100644 --- a/public/index.html +++ b/public/index.html @@ -7,5 +7,7 @@ <body> <div id='root'/> </body> + <!-- <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> --> <script src="/bundle.js"></script> -</html> \ No newline at end of file + +</html> diff --git a/server.js b/server.js index ac6e4f3..c58e27f 100644 --- a/server.js +++ b/server.js @@ -1,19 +1,77 @@ const path = require('path'); const express = require('express'); const app = express(); +const bodyParser = require('body-parser'); const PORT = process.env.PORT || 3000; +const auth = require('./backend/auth'); const api = require('./backend/routes'); +const cookieSession = require('cookie-session'); + +const {User} = require('./backend/models.js'); app.use(express.static(path.join(__dirname, 'public'))); +app.use(bodyParser.json()); +app.use(bodyParser.urlencoded({ extended: false })); +app.use(cookieSession({ + keys: [process.env.SECRET || 'super duper secret string'] +})); + +app.use((req, res, next) => { + res.header("Access-Control-Allow-Credentials", true); + res.header("Access-Control-Allow-Origin", "http://localhost:3000"); + res.header("Access-Control-Allow-Methods", "OPTIONS, POST, GET"); + res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); + next(); +}); + +const passport = require('passport'); +const LocalStrategy = require('passport-local').Strategy; + +passport.serializeUser((user, done) => { + console.log('\nSERIALIZE', user.get()); + const userId = user.id; + done(null, userId); +}); + +passport.deserializeUser((id, done) => { + User.findById(id) + .then(user => { + console.log('\nDESERIALIZE', user.get()); + done(null, user); + }) + .catch(err => { + done(err, null); + }); +}); + +passport.use(new LocalStrategy((username, password, done) => { + User.findOne({where: {username}}) + .then(user => { + if (!user) { + return done('no user found with that username', null); + } + if (user.password !== password) { + return done('passwords do not match', null); + } + return done(null, user); + }) + .catch(err => { + return done(err, null); + }); +})); + +app.use(passport.initialize()); +app.use(passport.session()); app.get('/', (request, response) => { - response.sendFile(__dirname + '/public/index.html'); // For React/Redux + response.sendFile(__dirname + '/public/index.html'); // For React/Redux }); +app.use('/api/user', auth(passport)); app.use('/api', api); app.listen(PORT, error => { - error - ? console.error(error) - : console.info(`==> 🌎 Listening on port ${PORT}. Visit http://localhost:${PORT}/ in your browser.`); + error + ? console.error(error) + : console.info('==> 🌎 Listening🌎 '); });