diff --git a/package-lock.json b/package-lock.json index fd7ce49..6173d45 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3297,12 +3297,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3317,17 +3319,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -3444,7 +3449,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -3456,6 +3462,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -3470,6 +3477,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -3483,6 +3491,7 @@ "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -3581,7 +3590,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -3593,6 +3603,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -3714,6 +3725,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", diff --git a/server/api/comments.js b/server/api/comments.js index c0b9c7c..fa31563 100755 --- a/server/api/comments.js +++ b/server/api/comments.js @@ -1,13 +1,13 @@ const router = require('express').Router() -const { Vote, Comment } = require('../db/models') -const Sequelize = require('sequelize') +const { Comment } = require('../db/models') + module.exports = router + router.get('/', async (req, res, next) => { try { const comments = await Comment.findAll({ - attributes: ['id', 'secs', 'text'], - }) + attributes: ['id', 'secs', 'text']}) res.send(comments) } catch (err) { next(err) @@ -17,8 +17,7 @@ router.get('/', async (req, res, next) => { router.get('/:id', async (req, res, next) => { try { const comment = await Comment.findByPk(req.params.id, { - attributes: ['id', 'secs', 'text'], - }) + attributes: ['id', 'secs', 'text']}) res.json(comment) } catch (err) { next(err) diff --git a/server/api/index.js b/server/api/index.js index 9bfc4ce..3e01f11 100755 --- a/server/api/index.js +++ b/server/api/index.js @@ -2,7 +2,9 @@ const router = require('express').Router() module.exports = router const ascii = require('../ascii') +router.use('/users', require('./users')) router.use('/comments', require('./comments')) +router.use('/votes', require('./votes')) router.get('/', async (req, res, next) => { try { diff --git a/server/api/users.js b/server/api/users.js new file mode 100644 index 0000000..3c9fcd1 --- /dev/null +++ b/server/api/users.js @@ -0,0 +1,38 @@ +const router = require('express').Router() +const { User, Vote } = require('../db/models') +module.exports = router + +router.get('/', async (req, res, next) => { + try { + const users = await User.findAll({ + attributes: ['id', 'name'] + }) + res.send(users) + } catch (err) { + next(err) + } +}) + +router.get('/:id', async (req, res, next) => { + try { + const user = await User.findByPk(req.params.id, + {attributes: ['id', 'name'], + include: {model: Vote, + attributes: ['id', 'userId','commentId', 'upvote', 'downvote']}}) + res.json(user) + } catch (err) { + next(err) + } +}) + +router.get('/:id/votes', async (req, res, next) => { + try { + const user = await User.findByPk(req.params.id, + {attributes: ['id', 'name'], + include: {model: Vote, + attributes: ['id', 'userId','commentId', 'upvote', 'downvote']}}) + res.json(user.votes) + } catch (err) { + next(err) + } +}) diff --git a/server/api/votes.js b/server/api/votes.js new file mode 100644 index 0000000..0768c0f --- /dev/null +++ b/server/api/votes.js @@ -0,0 +1,53 @@ +const router = require('express').Router() +const { Vote } = require('../db/models') +module.exports = router + +router.get('/', async (req, res, next) => { + try { + const votes = await Vote.findAll() + res.send(votes) + } catch (err) { + next(err) + } +}) + +router.post('/', async (req, res, next) => { + const {userId, commentId, upvote, downvote} = req.body + try { + const votes = await Vote.create({userId, commentId, upvote, downvote}) + res.send(votes) + } catch (err) { + next(err) + } +}) + +router.get('/:id', async (req, res, next) => { + try { + const vote = await Vote.findByPk(+req.params.id) + res.json(vote) + } catch (err) { + next(err) + } +}) + +router.post('/:id/delete', async (req, res, next) => { + try { + const vote = await Vote.findByPk(+req.params.id) + await vote.destroy() + res.json(vote) + } catch (err) { + next(err) + } +}) + +router.put('/:id/update', async (req, res, next) => { + const upvote = req.body.downvote + const downvote = req.body.upvote + try { + const vote = await Vote.findByPk(+req.params.id) + await vote.update({upvote, downvote}) + res.json(vote) + } catch (err) { + next(err) + } +}) diff --git a/server/db/models/index.js b/server/db/models/index.js index 23ac366..2b06bbb 100755 --- a/server/db/models/index.js +++ b/server/db/models/index.js @@ -1,3 +1,13 @@ const Comment = require('./comment') +const Vote = require('./vote') +const User = require('./user') -module.exports = { Comment } +Vote.belongsTo(Comment) +Comment.hasMany(Vote) + +Vote.belongsTo(User) +User.hasMany(Vote) + +Comment.belongsTo(User) +User.hasMany(Comment) +module.exports = { Comment, Vote, User } diff --git a/server/db/models/user.js b/server/db/models/user.js new file mode 100644 index 0000000..21cefe6 --- /dev/null +++ b/server/db/models/user.js @@ -0,0 +1,9 @@ +const Sequelize = require('sequelize') +const db = require('../db') + +const User = db.define('users', { + name: Sequelize.STRING, + +}) + +module.exports = User diff --git a/server/db/models/vote.js b/server/db/models/vote.js new file mode 100644 index 0000000..820b8e9 --- /dev/null +++ b/server/db/models/vote.js @@ -0,0 +1,9 @@ +const Sequelize = require('sequelize') +const db = require('../db') + +const Vote = db.define('votes', { + upvote: Sequelize.INTEGER, + downvote: Sequelize.INTEGER, +}) + +module.exports = Vote diff --git a/src/components/waveform/CommentList.js b/src/components/waveform/CommentList.js index 8f87cca..de51eca 100644 --- a/src/components/waveform/CommentList.js +++ b/src/components/waveform/CommentList.js @@ -4,15 +4,22 @@ import { connect } from 'react-redux' import { ListGroup } from 'react-bootstrap' const CommentList = props => { + return ( {props.comments.map(comment => ( - + vote.commentId===comment.id && vote.userId===props.user.id)} + updateSongPos={props.updateSongPos} + comment={comment} + /> ))} ) } -const mapState = state => ({ ...state }) +const mapState = state => ({...state}) export default connect(mapState)(CommentList) diff --git a/src/components/waveform/CommentRow.js b/src/components/waveform/CommentRow.js index d92881b..afbbf49 100644 --- a/src/components/waveform/CommentRow.js +++ b/src/components/waveform/CommentRow.js @@ -1,5 +1,6 @@ import React from 'react' import { Row, Col, Media } from 'react-bootstrap' +import CommentVotes from './CommentVotes' const parseTime = secs => { var hours = Math.floor(secs / 3600) @@ -18,6 +19,22 @@ const parseTime = secs => { return minutes + ':' + seconds } +// const toggleVote = (vote) => { +// const userVote = this.props.user.votes.find(vote=>vote.commentId===this.props.id) +// userVote.upvote = vote === 'up' ? (userVote.upvote === 1 ? 0 : 1) : 0 +// userVote.downvote = vote === 'down' ? (userVote.downvote === 1 ? 0 : 1) : 0 +// const sendThisToBackend = { +// userId: this.props.user.id, +// commentId: this.props.id, +// userUpvote: userVote.upvote, +// userDownvote: userVote.downvote, +// prevUpvote: userVote.upvote, +// prevDownvote: userVote.downvote +// } +// +// this.props.saveVote(sendThisToBackend) +// } + const CommentRow = props => { console.log(props) @@ -32,6 +49,11 @@ const CommentRow = props => { {parseTime(props.comment.secs)} + {props.comment.text} @@ -40,4 +62,16 @@ const CommentRow = props => { ) } + +// const mapState = state => { +// return { +// user: state.user +// } +// } +// const mapDispatch = dispatch => { +// return { +// saveVote: vote => dispatch(addVote(vote)) +// } +// } +//export default connect(mapState)(CommentRow) export default CommentRow diff --git a/src/components/waveform/CommentRowClass.js b/src/components/waveform/CommentRowClass.js deleted file mode 100644 index b22ab5d..0000000 --- a/src/components/waveform/CommentRowClass.js +++ /dev/null @@ -1,58 +0,0 @@ -import React from 'react' -import { Row, Col, Media } from 'react-bootstrap' -import { connect } from 'react-redux' -import { addVote } from '../../store' -const parseTime = secs => { - var hours = Math.floor(secs / 3600) - var minutes = Math.floor((secs - hours * 3600) / 60) - var seconds = secs - hours * 3600 - minutes * 60 - - if (hours < 10) { - hours = '0' + hours - } - if (minutes < 10) { - minutes = '0' + minutes - } - if (seconds < 10) { - seconds = '0' + seconds - } - return minutes + ':' + seconds -} - -class CommentRow extends React.Component { - constructor() { - super() - } - - render() { - return ( - - - - - - Anon at{' '} - this.props.updateSongPos(this.props.secs)}> - {parseTime(this.props.secs)} - - - - - {this.props.text} - - - - ) - } -} - -const mapState = state => ({ user: { id: 1 }, ...state }) -// const mapDispatch = dispatch => { -// return { -// saveVote: vote => dispatch(addVote(vote)), -// } -// } -export default connect( - mapState, - mapDispatch, -)(CommentRow) diff --git a/src/components/waveform/CommentVotes.js b/src/components/waveform/CommentVotes.js new file mode 100644 index 0000000..8bf8cc8 --- /dev/null +++ b/src/components/waveform/CommentVotes.js @@ -0,0 +1,68 @@ +import React from 'react' +import { Row, Col } from 'react-bootstrap' +import { connect } from 'react-redux' +import { destroyVote, updateVote, addUpvote, addDownvote } from '../../store' + +const CommentVotes = props => { + const {commentId, userId, userVote} = props + + const upvote = () => { + console.log('userVote',userVote) + userVote + ? userVote.upvote===1 + ? props.deleteVote(userVote.id) + : props.editVote(userVote) + : props.upvote(userId, commentId) + } + + const downvote = () => { + userVote + ? userVote.downvote===1 + ? props.deleteVote(userVote.id) + : props.editVote(userVote) + : props.downvote(userId, commentId) + } + + return ( + + + + + ⏶ + + + + + + + ⏷ + + + + + ) +} + +const mapDispatch = dispatch => { + return { + deleteVote: userVote => dispatch(destroyVote(userVote)), + editVote: userVote => dispatch(updateVote(userVote)), + upvote: (userId, commentId) => dispatch(addUpvote(userId, commentId)), + downvote: (userId, commentId) => dispatch(addDownvote(userId, commentId)), + } +} +export default connect(null, mapDispatch)(CommentVotes) diff --git a/src/components/waveform/index.js b/src/components/waveform/index.js index b3aafd9..0968bbe 100644 --- a/src/components/waveform/index.js +++ b/src/components/waveform/index.js @@ -2,11 +2,10 @@ import React, { Component } from 'react' import Waveform from 'react-audio-waveform' import { connect } from 'react-redux' import ReactAudioPlayer from 'react-audio-player' -import CommentPopup from './CommentPopup' import Comment from './Comment' import CommentList from './CommentList' -import { fetchAllComments, addComment } from '../../store' -import { Media, Form, Button, Container, Row, Col } from 'react-bootstrap' +import { getUser, fetchAllComments, addComment, getAllVotes } from '../../store' +import { Media, Form, Container, Row, Col } from 'react-bootstrap' const config = require('./audio/loneDigger.json') const parseTime = secs => { @@ -48,7 +47,9 @@ class Player extends Component { } async fetchData() { + await this.props.fetchUser() await this.props.fetchComments() + await this.props.fetchAllVotes() await this.setState({ loading: false }) } @@ -81,7 +82,6 @@ class Player extends Component { updateSongPos(secs) { this.setState({ songPos: secs }) this.rap.audioEl.currentTime = secs - this.rap.audioEl.play() } render() { @@ -158,6 +158,8 @@ const mapState = state => ({ ...state }) const mapDispatch = dispatch => { return { fetchComments: () => dispatch(fetchAllComments()), + fetchUser: () => dispatch(getUser()), + fetchAllVotes: userId => dispatch(getAllVotes(userId)), postComment: comment => dispatch(addComment(comment)), } } diff --git a/src/store/index.js b/src/store/index.js index f176446..81ffb74 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -5,8 +5,11 @@ import { composeWithDevTools } from 'redux-devtools-extension' import episodes from './reducers/episodes' import captions from './reducers/captions' import comments from './reducers/comments' +import user from './reducers/users' +import votes from './reducers/votes' -const reducer = combineReducers({ episodes, captions, comments }) + +const reducer = combineReducers({episodes, captions, comments, user, votes}) const middleware = composeWithDevTools( applyMiddleware(thunkMiddleware, createLogger({ collapsed: true })), ) @@ -16,3 +19,5 @@ export default store export * from './reducers/episodes' export * from './reducers/captions' export * from './reducers/comments' +export * from './reducers/users' +export * from './reducers/votes' diff --git a/src/store/reducers/comments.js b/src/store/reducers/comments.js index 357b09d..8b16999 100644 --- a/src/store/reducers/comments.js +++ b/src/store/reducers/comments.js @@ -1,8 +1,11 @@ import axios from 'axios' +import {addedVote} from './votes' // ACTION TYPES const GOT_ALL_COMMENTS = 'GOT_ALL_COMMENTS' const ADD_COMMENT = 'ADD_COMMENT' +const UPVOTED_COMMENT = 'UPVOTED_COMMENT' +const DOWNVOTED_COMMENT = 'DOWNVOTED_COMMENT' const initialComments = [] // ACTION CREATORS @@ -16,6 +19,15 @@ export const addedComment = comment => ({ comment, }) +export const upvotedComment = comment => ({ + type: UPVOTED_COMMENT, + comment, +}) + +export const downvotedComment = comment => ({ + type: DOWNVOTED_COMMENT, + comment, +}) // THUNK CREATORS export const fetchAllComments = () => async dispatch => { try { @@ -36,6 +48,28 @@ export const addComment = comment => async dispatch => { } } +export const upvoteComment = (userId, commentId) => async dispatch => { + console.log('REDUCER UPVOTECOMMENT', userId, commentId) + try { + const {data} = await axios.post(`/api/comments/${commentId}/upvote`, {userId}) + console.log('the votesum should have changed', data) + dispatch(upvotedComment(data.comment)) + dispatch(addedVote(data.vote)) + } catch (err) { + console.error(err) + } +} + +export const downvoteComment = (userId, commentId) => async dispatch => { + try { + const {data} = await axios.post(`/api/comments/${commentId}/downvote`, {userId}) + console.log('the votesum should have changed', data) + dispatch(downvotedComment(data.comment)) + dispatch(addedVote(data.vote)) + } catch (err) { + console.error(err) + } +} // export const addVote = vote => async dispatch => { // // try { @@ -53,6 +87,10 @@ const commentReducer = (comments = initialComments, action) => { return action.comments case ADD_COMMENT: return comments.concat(action.comment) + case UPVOTED_COMMENT: { + const oldComments = comments.filter(comment=>comment.id !== action.comment.id) + return oldComments.concat(action.comment) + } default: return comments } diff --git a/src/store/reducers/users.js b/src/store/reducers/users.js new file mode 100644 index 0000000..1409c5c --- /dev/null +++ b/src/store/reducers/users.js @@ -0,0 +1,34 @@ +import axios from 'axios' + +const initialUser = {} + +// ACTION TYPES +const GOT_USER = 'GOT_USER' + +export const gotUser = user => ({ + type: GOT_USER, + user, +}) + +export const getUser = () => async dispatch => { + try { + const {data} = await axios.get(`/api/users/${1}`) + const user = {id: data.id, name: data.name} + dispatch(gotUser(user)) + } catch (err) { + console.error(err) + } +} + + +// REDUCER +const userReducer = (user = initialUser, action) => { + switch (action.type) { + case GOT_USER: + return action.user + default: + return user + } +} + +export default userReducer diff --git a/src/store/reducers/votes.js b/src/store/reducers/votes.js new file mode 100644 index 0000000..30e5b17 --- /dev/null +++ b/src/store/reducers/votes.js @@ -0,0 +1,102 @@ +import axios from 'axios' + +const initialUserVotes = [] + +// ACTION TYPES +const GOT_ALL_VOTES = 'GOT_ALL_VOTES' +const DELETED_VOTE = 'DELETED_VOTE' +const UPVOTED = 'UPVOTED' +const DOWNVOTED = 'DOWNVOTED' +const UPDATED_VOTE = 'UPDATED_VOTE' + +export const gotAllVotes = votes => ({ + type: GOT_ALL_VOTES, + votes +}) + +export const deletedVote = oldVote => ({ + type: DELETED_VOTE, + oldVote +}) + +export const upvoted = newVote => ({ + type: UPVOTED, + newVote +}) +export const downvoted = newVote => ({ + type: DOWNVOTED, + newVote +}) +export const updatedVote = newVote => ({ + type: UPDATED_VOTE, + newVote +}) + +export const addUpvote = (userId, commentId) => async dispatch => { + try { + const {data} = await axios.post('/api/votes', {upvote: 1, downvote: 0, userId, commentId}) + dispatch(upvoted(data)) + } catch(e) { + console.log(e) + } +} + +export const addDownvote = (userId, commentId) => async dispatch => { + try { + const {data} = await axios.post('/api/votes', {upvote: 0, downvote: 1, userId, commentId}) + dispatch(downvoted(data)) + } catch(e) { + console.log(e) + } +} +export const destroyVote = id => async dispatch => { + try { + const {data} = await axios.post(`/api/votes/${id}/delete`) + dispatch(deletedVote(data)) + } catch(e) { + console.log(e) + } +} + +export const updateVote = userVote => async dispatch => { + try { + const {data} = await axios.put(`/api/votes/${userVote.id}/update`, userVote) + dispatch(updatedVote(data)) + } catch(e) { + console.log(e) + } +} + +export const getAllVotes = () => async dispatch => { + try { + const {data} = await axios.get('/api/votes') + dispatch(gotAllVotes(data)) + } catch(e) { + console.log(e) + } +} +// REDUCER +const voteReducer = (votes = initialUserVotes, action) => { + switch (action.type) { + case GOT_ALL_VOTES: + return action.votes + case DELETED_VOTE: { + const newVotes = votes.filter(vote => vote.id !== action.oldVote.id) + return newVotes + } + case UPDATED_VOTE: { + const newVotes = votes.filter(vote => vote.id !== action.newVote.id) + return newVotes.concat(action.newVote) + } + case UPVOTED: { + return votes.concat(action.newVote) + } + case DOWNVOTED: { + return votes.concat(action.newVote) + } + default: + return votes + } +} + +export default voteReducer