diff --git a/Dockerfile b/Dockerfile index 130c70b28..2ee6d739c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,6 +2,7 @@ FROM node:8 as base RUN mkdir -p /usr/src/app WORKDIR /usr/src/app EXPOSE 8000 +EXPOSE 27017 FROM base as development ENV NODE_ENV development diff --git a/Intl/localizationData/en.js b/Intl/localizationData/en.js index 79b481d3c..32965c07b 100644 --- a/Intl/localizationData/en.js +++ b/Intl/localizationData/en.js @@ -7,6 +7,8 @@ export default { twitterMessage: 'We are on Twitter', by: 'By', deletePost: 'Delete Post', + addComment: 'Add comment', + commentAuthor: 'Author', createNewPost: 'Create new post', authorName: 'Author\'s Name', postTitle: 'Post Title', diff --git a/Intl/localizationData/fr.js b/Intl/localizationData/fr.js index 7e5b81b3f..955aec4c3 100644 --- a/Intl/localizationData/fr.js +++ b/Intl/localizationData/fr.js @@ -7,6 +7,8 @@ export default { twitterMessage: 'Nous sommes sur Twitter', by: 'Par', deletePost: 'Supprimer le message', + addComment: 'Ajouter un commentaire', + commentAuthor: 'Auteur', createNewPost: 'Créer un nouveau message', authorName: 'Nom de l\'auteur', postTitle: 'Titre de l\'article', diff --git a/client/modules/Post/PostActions.js b/client/modules/Post/PostActions.js index 7e5932fa5..70db6680c 100644 --- a/client/modules/Post/PostActions.js +++ b/client/modules/Post/PostActions.js @@ -5,6 +5,11 @@ export const ADD_POST = 'ADD_POST'; export const ADD_POSTS = 'ADD_POSTS'; export const DELETE_POST = 'DELETE_POST'; +export const COMMENT_FORM_OPEN = 'COMMENT_FORM_OPEN'; +export const COMMENT_FORM_CLOSE = 'COMMENT_FORM_CLOSE'; +export const COMMENT_ADD = 'COMMENT_ADD'; +export const COMMENT_REMOVE = 'COMMENT_REMOVE'; + // Export Actions export function addPost(post) { return { @@ -58,3 +63,70 @@ export function deletePostRequest(cuid) { return callApi(`posts/${cuid}`, 'delete').then(() => dispatch(deletePost(cuid))); }; } + +export function commentFormForPostClose(postId) { + return { + type: COMMENT_FORM_OPEN, + payload: { + postId, + }, + }; +} + +export function commentAdd(authorName, comment, postId, _id) { + return { + type: COMMENT_ADD, + payload: { + authorName, + comment, + postId, + _id, + }, + }; +} + +export function commentRemove(commentId, postId) { + return { + type: COMMENT_REMOVE, + payload: { + commentId, + postId, + }, + }; +} + +export function commentRemoveRequest(commentId, postId) { + return (dispatch) => { + return callApi('comment', 'delete', { commentId, postId }) + .then((response) => { + dispatch(commentRemove( + response.commentId, + response.postId + )); + }); + }; +} + +export function commentRequestAdd(authorName, comment, postId) { + return (dispatch) => { + return callApi('comment', 'post', { authorName, comment, postId }) + .then((response) => { + dispatch(commentFormForPostClose()); + dispatch(commentAdd( + response.authorName, + response.comment, + response.postId, + response._id + )); + }); + }; +} + +export function commentFormForPostOpen(postId) { + return { + type: COMMENT_FORM_OPEN, + payload: { + postId, + }, + }; +} diff --git a/client/modules/Post/PostReducer.js b/client/modules/Post/PostReducer.js index 5a5054369..e950c9510 100644 --- a/client/modules/Post/PostReducer.js +++ b/client/modules/Post/PostReducer.js @@ -1,25 +1,98 @@ -import { ADD_POST, ADD_POSTS, DELETE_POST } from './PostActions'; +import { + ADD_POST, + ADD_POSTS, + DELETE_POST, + COMMENT_ADD, + COMMENT_REMOVE, + COMMENT_FORM_OPEN, +} from './PostActions'; +// Post +// { +// _id, +// name, +// title, +// slug, +// cuid, +// content, +// comments: [], +// } // Initial State -const initialState = { data: [] }; +const initialState = { + data: [], + addCommentForm: { + isVisible: false, + postId: '', + }, +}; const PostReducer = (state = initialState, action) => { switch (action.type) { case ADD_POST : return { + ...state, data: [action.post, ...state.data], }; case ADD_POSTS : return { + ...state, data: action.posts, }; case DELETE_POST : return { + ...state, data: state.data.filter(post => post.cuid !== action.cuid), }; + case COMMENT_FORM_OPEN : + return { + ...state, + addCommentForm: { + isVisible: true, + postId: action.payload.postId, + }, + }; + + case COMMENT_ADD : + return { + ...state, + data: state.data.map(post => { + if (post._id === action.payload.postId) { + return { + ...post, + comments: [ + ...post.comments, + { + authorName: action.payload.authorName, + comment: action.payload.comment, + postId: action.payload.postId, + _id: action.payload._id, + }, + ], + }; + } + return post; + }), + }; + + case COMMENT_REMOVE : + return { + ...state, + data: state.data.map(post => { + if (post._id === action.payload.postId) { + return { + ...post, + comments: post.comments.filter( + ({ _id }) => _id !== action.payload.commentId + ), + }; + } + return post; + }), + }; + default: return state; } @@ -31,7 +104,9 @@ const PostReducer = (state = initialState, action) => { export const getPosts = state => state.posts.data; // Get post by cuid -export const getPost = (state, cuid) => state.posts.data.filter(post => post.cuid === cuid)[0]; +export const getPost = (state, cuid) => state.posts.data.filter( + post => post.cuid === cuid +)[0]; // Export Reducer export default PostReducer; diff --git a/client/modules/Post/PostSelectors.js b/client/modules/Post/PostSelectors.js new file mode 100644 index 000000000..9119fe34d --- /dev/null +++ b/client/modules/Post/PostSelectors.js @@ -0,0 +1,37 @@ +import { createSelector } from 'reselect'; + +export const commentFormValuesSelector = state => + state.form.addCommentForm && state.form.addCommentForm.values; + +export const authorNameSelector = createSelector( + commentFormValuesSelector, + values => { + return values && values.authorName ? values.authorName : ''; + } +); + +export const commentSelector = createSelector( + commentFormValuesSelector, + values => { + return values && values.comment ? values.comment : ''; + } +); + +export const isCommentFormVisibleSelector = state => { + // render on server error ".addCommentForm is undefined" + return !!state.posts.addCommentForm && state.posts.addCommentForm.isVisible; +}; + +export const isCommentFormVisiblePostIdSelector = (state, props) => { + // render on server error ".addCommentForm is undefined" + return !!state.posts.addCommentForm + && + props.post._id === state.posts.addCommentForm.postId; +}; + +export const isVommentFormVisibleInPostSelector = createSelector( + isCommentFormVisibleSelector, + isCommentFormVisiblePostIdSelector, + (isCommentFormVisible, isCommentFormVisiblePostId) => + isCommentFormVisible && isCommentFormVisiblePostId +); diff --git a/client/modules/Post/__tests__/components/PostList.spec.js b/client/modules/Post/__tests__/components/PostList.spec.js index fe66822e4..367b095f6 100644 --- a/client/modules/Post/__tests__/components/PostList.spec.js +++ b/client/modules/Post/__tests__/components/PostList.spec.js @@ -10,7 +10,11 @@ const posts = [ test('renders the list', t => { const wrapper = shallow( - {}} handleDeletePost={() => {}} /> + {}} + handleDeletePost={() => {}} + /> ); t.is(wrapper.find('PostListItem').length, 2); diff --git a/client/modules/Post/components/CommentAddForm/CommentAddForm.css b/client/modules/Post/components/CommentAddForm/CommentAddForm.css new file mode 100644 index 000000000..1ec02997a --- /dev/null +++ b/client/modules/Post/components/CommentAddForm/CommentAddForm.css @@ -0,0 +1,4 @@ +.close-comment{ + margin-left: 100px; + cursor: pointer; +} diff --git a/client/modules/Post/components/CommentAddForm/CommentAddForm.jsx b/client/modules/Post/components/CommentAddForm/CommentAddForm.jsx new file mode 100644 index 000000000..74048ffc7 --- /dev/null +++ b/client/modules/Post/components/CommentAddForm/CommentAddForm.jsx @@ -0,0 +1,79 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; +import { Field } from 'redux-form'; + +import styles from './CommentAddForm.css'; + +import { + commentFormForPostClose, + commentRequestAdd, +} from '../../PostActions'; + +const CommentAddForm = (props) => { + function addCommentHandler(event) { + const { + authorName, + comment, + commentAdd, + postId, + } = props; + + event.preventDefault(); + commentAdd(authorName, comment, postId); + } + + return ( +
+
+ + + × + +
+ +
+
+
+ +
+ +
+
+
+ +
+
+ ); +}; + +CommentAddForm.propTypes = { + postId: PropTypes.string.isRequired, + authorName: PropTypes.string.isRequired, + comment: PropTypes.string.isRequired, + commentAdd: PropTypes.func.isRequired, + addFormClose: PropTypes.func.isRequired, +}; + +const mapDispatchToProps = dispatch => ({ + addFormClose: () => dispatch(commentFormForPostClose()), + commentAdd: (authorName, comment, postId) => + dispatch(commentRequestAdd(authorName, comment, postId)), +}); + +export default connect(null, mapDispatchToProps)(CommentAddForm); diff --git a/client/modules/Post/components/CommentAddForm/index.js b/client/modules/Post/components/CommentAddForm/index.js new file mode 100644 index 000000000..75e84b5b6 --- /dev/null +++ b/client/modules/Post/components/CommentAddForm/index.js @@ -0,0 +1,22 @@ +import { connect } from 'react-redux'; +import { reduxForm } from 'redux-form'; + +import CommentAddForm from './CommentAddForm' +import { authorNameSelector, commentSelector } from '../../PostSelectors'; +import { addCommentRequest } from '../../PostActions'; + +const mapStateToProps = (state, props) => ({ + authorName: authorNameSelector(state), + comment: commentSelector(state), +}); + +const mapDispatchToProps = (dispatch, props) => ({ + addComment: (authorName, comment) => + dispatch(addCommentRequest(authorName, comment, props.postId)) +}); + +const Connected = connect(mapStateToProps, mapDispatchToProps)(CommentAddForm) + +export default reduxForm({ + form: 'addCommentForm', // a unique identifier for this form +})(Connected); diff --git a/client/modules/Post/components/PostList.js b/client/modules/Post/components/PostList.js index 6719b3124..9b315e5b8 100644 --- a/client/modules/Post/components/PostList.js +++ b/client/modules/Post/components/PostList.js @@ -27,6 +27,12 @@ PostList.propTypes = { content: PropTypes.string.isRequired, slug: PropTypes.string.isRequired, cuid: PropTypes.string.isRequired, + comments: PropTypes.arrayOf(PropTypes.shape({ + authorName: PropTypes.string.isRequired, + comment: PropTypes.string.isRequired, + postId: PropTypes.string.isRequired, + _id: PropTypes.string.isRequired, + })).isRequired, })).isRequired, handleDeletePost: PropTypes.func.isRequired, }; diff --git a/client/modules/Post/components/PostListItem/PostListItem.css b/client/modules/Post/components/PostListItem/PostListItem.css index 49b3345f2..46f8c9835 100644 --- a/client/modules/Post/components/PostListItem/PostListItem.css +++ b/client/modules/Post/components/PostListItem/PostListItem.css @@ -56,3 +56,29 @@ font-size: 16px; color: #555; } + +.post-item--add-comment{ + margin-left: 15px; + cursor: pointer; +} + +.comment-block{ + position: relative; + width: 300px; + margin-top: 12px; + padding: 15px; + border: 1px solid #eee; +} + +.comment-content{ + margin-bottom: 12px; +} + +.comment-remove{ + position: absolute; + right: 5px; + top: 5px; + padding: 3px; + cursor: pointer; + color: #F33; +} diff --git a/client/modules/Post/components/PostListItem/PostListItem.js b/client/modules/Post/components/PostListItem/PostListItem.js index 2925e2199..a8555eddc 100644 --- a/client/modules/Post/components/PostListItem/PostListItem.js +++ b/client/modules/Post/components/PostListItem/PostListItem.js @@ -1,36 +1,129 @@ import React from 'react'; +import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import { Link } from 'react-router'; import { FormattedMessage } from 'react-intl'; +import CommentAddForm from '../CommentAddForm'; + +import { + commentFormForPostOpen, + commentRemoveRequest, +} from '../../PostActions'; +import { isVommentFormVisibleInPostSelector } from '../../PostSelectors'; + // Import Style import styles from './PostListItem.css'; -function PostListItem(props) { +const PostListItem = ({ + post, + onDelete, + isAddFormVisible, + addFormOpen, + removeComment, +}) => { + function removeCommentHandler(commentId) { + if (confirm('Do you want to delete this comment')) { // eslint-disable-line + removeComment(commentId, post._id); + } + } + return (

- - {props.post.title} + + {post.title}

-

{props.post.name}

-

{props.post.content}

-

+

+ {post.name} +

+ +

+ {post.content} +

+

+ + + + + + +

+ + { + isAddFormVisible && ( + + ) + } + + { + !!post.comments.length && post.comments.map(({ + authorName, + comment, + _id, + }) => ( +
+
{comment}
+
+ : {authorName} +
+
removeCommentHandler(_id)} + > + × +
+
+ )) + }
); -} +}; PostListItem.propTypes = { + isAddFormVisible: PropTypes.bool.isRequired, post: PropTypes.shape({ + _id: PropTypes.string.isRequired, name: PropTypes.string.isRequired, title: PropTypes.string.isRequired, content: PropTypes.string.isRequired, slug: PropTypes.string.isRequired, cuid: PropTypes.string.isRequired, + comments: PropTypes.arrayOf(PropTypes.shape({ + authorName: PropTypes.string.isRequired, + comment: PropTypes.string.isRequired, + postId: PropTypes.string.isRequired, + _id: PropTypes.string.isRequired, + })).isRequired, }).isRequired, onDelete: PropTypes.func.isRequired, + addFormOpen: PropTypes.func.isRequired, + removeComment: PropTypes.func.isRequired, }; -export default PostListItem; +const mapStateToProps = (state, props) => ({ + isAddFormVisible: isVommentFormVisibleInPostSelector(state, props), +}); + +const mapDispatchToProps = (dispatch, props) => ({ + addFormOpen: () => dispatch(commentFormForPostOpen(props.post._id)), + removeComment: (commentId, postId) => + dispatch(commentRemoveRequest(commentId, postId)), +}); + +export default connect(mapStateToProps, mapDispatchToProps)(PostListItem); diff --git a/client/modules/Post/pages/PostListPage/PostListPage.js b/client/modules/Post/pages/PostListPage/PostListPage.js index 7c31229e6..cd5904edf 100644 --- a/client/modules/Post/pages/PostListPage/PostListPage.js +++ b/client/modules/Post/pages/PostListPage/PostListPage.js @@ -33,8 +33,14 @@ class PostListPage extends Component { render() { return (
- - + +
); } diff --git a/client/reducers.js b/client/reducers.js index 2aa143142..4c5b3437f 100644 --- a/client/reducers.js +++ b/client/reducers.js @@ -2,6 +2,7 @@ * Root Reducer */ import { combineReducers } from 'redux'; +import { reducer as reduxFormReducer } from 'redux-form'; // Import Reducers import app from './modules/App/AppReducer'; @@ -13,4 +14,5 @@ export default combineReducers({ app, posts, intl, + form: reduxFormReducer, }); diff --git a/docker-compose.yml b/docker-compose.yml index b581c9c59..e58f07521 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -31,5 +31,7 @@ services: image: mongo:latest volumes: - dbdata:/data/db + ports: + - "27017:27017" volumes: dbdata: diff --git a/package-lock.json b/package-lock.json index 14a36b319..c22982d54 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,21 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@babel/runtime": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.3.1.tgz", + "integrity": "sha512-7jGW8ppV0ant637pIqAcFfQDDH1orEPGJb8aXfUozuCU3QqX7rX4DA8iwrbPrR1hcH0FTTHz47yQnk+bl5xHQA==", + "requires": { + "regenerator-runtime": "^0.12.0" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", + "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==" + } + } + }, "@types/node": { "version": "10.5.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-10.5.1.tgz", @@ -3881,6 +3896,11 @@ "next-tick": "1" } }, + "es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==" + }, "es6-iterator": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", @@ -6510,8 +6530,7 @@ "is-promise": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", - "dev": true + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" }, "is-property": { "version": "1.0.2", @@ -14306,6 +14325,11 @@ "react-base16-styling": "^0.5.1" } }, + "react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, "react-proxy": { "version": "3.0.0-alpha.1", "resolved": "https://registry.npmjs.org/react-proxy/-/react-proxy-3.0.0-alpha.1.tgz", @@ -14590,6 +14614,48 @@ "base16": "^1.0.0" } }, + "redux-form": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/redux-form/-/redux-form-8.1.0.tgz", + "integrity": "sha512-d2+0OaJpSq3kwkbPtFlG3W/HENWLxX8NqqTHSOnfgIrID/9faH/rxejLa1X3HChilCTm71zWe/g9zaLPCMCofQ==", + "requires": { + "@babel/runtime": "^7.2.0", + "es6-error": "^4.1.1", + "hoist-non-react-statics": "^3.2.1", + "invariant": "^2.2.4", + "is-promise": "^2.1.0", + "lodash": "^4.17.11", + "lodash-es": "^4.17.11", + "prop-types": "^15.6.1", + "react-is": "^16.7.0", + "react-lifecycles-compat": "^3.0.4" + }, + "dependencies": { + "hoist-non-react-statics": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz", + "integrity": "sha512-0XsbTXxgiaCDYDIWFcwkmerZPSwywfUqYmwT4jzewKTQSWoE6FCMoUVOeBJWK3E/CrWbxRG3m5GzY4lnIwGRBA==", + "requires": { + "react-is": "^16.7.0" + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + }, + "lodash-es": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.11.tgz", + "integrity": "sha512-DHb1ub+rMjjrxqlB3H56/6MXtm1lSksDp2rA2cNWjG8mlDUYFhUj3Di2Zn5IwSU87xLv8tNIQ7sSwE/YOX/D/Q==" + }, + "react-is": { + "version": "16.8.3", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.3.tgz", + "integrity": "sha512-Y4rC1ZJmsxxkkPuMLwvKvlL1Zfpbcu+Bf4ZigkHup3v9EfdYhAlWAaVyA19olXq2o2mGn0w+dFKvk3pVVlYcIA==" + } + } + }, "redux-thunk": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.3.0.tgz", @@ -14861,6 +14927,11 @@ "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", "dev": true }, + "reselect": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.0.0.tgz", + "integrity": "sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA==" + }, "resolve": { "version": "1.7.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.7.1.tgz", diff --git a/package.json b/package.json index f0fa1f581..8e46acc57 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,9 @@ "react-redux": "^4.4.5", "react-router": "^3.2.1", "redux": "^3.5.2", + "redux-form": "^8.1.0", "redux-thunk": "^2.1.0", + "reselect": "^4.0.0", "sanitize-html": "^1.11.4" }, "devDependencies": { diff --git a/server/controllers/comment.controller.js b/server/controllers/comment.controller.js new file mode 100644 index 000000000..2d30f29f1 --- /dev/null +++ b/server/controllers/comment.controller.js @@ -0,0 +1,63 @@ +import mongoose from 'mongoose'; +import Comment from '../models/comment'; +import Post from '../models/post'; + +export function addComment(req, res) { + const { authorName, comment, postId } = req.body; + + if (!authorName || !comment || !postId) { + res.status(403).end(); + } + + const newComment = new Comment({ authorName, comment, postId }); + + newComment.save((err, saved) => { + if (err) { + res.status(500).send(err); + } + + Post.update( + { + // eslint-disable-next-line + _id: mongoose.Types.ObjectId(postId) + }, + { + $push: { + comments: saved._id, + }, + }, () => { + res.json({ + ...saved._doc, + }); + }); + }); +} + +export function removeComment(req, res) { + const { postId, commentId } = req.body; + + if (!postId || !commentId) { + res.status(403).end(); + } + + Comment.find({ + _id: commentId, + }).remove().exec((err) => { + if (err) { + console.log('err', err); + res.status(500).end(); + } + Post.update({ + // eslint-disable-next-line + _id: mongoose.Types.ObjectId(postId) + }, { + $pull: { + // eslint-disable-next-line + comments: mongoose.Types.ObjectId(commentId) + }, + }, () => { + console.log('commentId', commentId); + res.json({ postId, commentId }); + }); + }); +} diff --git a/server/controllers/post.controller.js b/server/controllers/post.controller.js index e62804c41..c3d9e9a61 100644 --- a/server/controllers/post.controller.js +++ b/server/controllers/post.controller.js @@ -10,12 +10,13 @@ import sanitizeHtml from 'sanitize-html'; * @returns void */ export function getPosts(req, res) { - Post.find().sort('-dateAdded').exec((err, posts) => { - if (err) { - res.status(500).send(err); - } - res.json({ posts }); - }); + Post.find().populate('comments').sort('-dateAdded') + .exec((err, posts) => { + if (err) { + res.status(500).send(err); + } + res.json({ posts }); + }); } /** diff --git a/server/models/comment.js b/server/models/comment.js new file mode 100644 index 000000000..7e9c935b9 --- /dev/null +++ b/server/models/comment.js @@ -0,0 +1,10 @@ +import mongoose from 'mongoose'; +const Schema = mongoose.Schema; + +const commentSchema = new Schema({ + authorName: { type: 'String', required: true }, + comment: { type: 'String', required: true }, + postId: { type: mongoose.Schema.Types.ObjectId, ref: 'Post' }, +}); + +export default mongoose.model('Comment', commentSchema); diff --git a/server/models/post.js b/server/models/post.js index e781bf7db..fc2bb93f6 100644 --- a/server/models/post.js +++ b/server/models/post.js @@ -8,6 +8,11 @@ const postSchema = new Schema({ slug: { type: 'String', required: true }, cuid: { type: 'String', required: true }, dateAdded: { type: 'Date', default: Date.now, required: true }, + comments: [{ + type: mongoose.Schema.Types.ObjectId, + ref: 'Comment', + default: null, + }], }); export default mongoose.model('Post', postSchema); diff --git a/server/routes/comment.routes.js b/server/routes/comment.routes.js new file mode 100644 index 000000000..2c0789b8c --- /dev/null +++ b/server/routes/comment.routes.js @@ -0,0 +1,9 @@ +import { Router } from 'express'; +import * as CommentController from '../controllers/comment.controller'; +const router = new Router(); + +// Add a new Post +router.route('/comment').post(CommentController.addComment); +router.route('/comment').delete(CommentController.removeComment); + +export default router; diff --git a/server/server.js b/server/server.js index 382249c91..cfaa2df36 100644 --- a/server/server.js +++ b/server/server.js @@ -46,6 +46,7 @@ import Helmet from 'react-helmet'; import routes from '../client/routes'; import { fetchComponentData } from './util/fetchData'; import posts from './routes/post.routes'; +import comments from './routes/comment.routes'; import dummyData from './dummyData'; import serverConfig from './config'; @@ -71,6 +72,7 @@ app.use(bodyParser.json({ limit: '20mb' })); app.use(bodyParser.urlencoded({ limit: '20mb', extended: false })); app.use(Express.static(path.resolve(__dirname, '../dist/client'))); app.use('/api', posts); +app.use('/api', comments); // Render Initial HTML const renderFullPage = (html, initialState) => { @@ -133,7 +135,6 @@ app.use((req, res, next) => { } const store = configureStore(); - return fetchComponentData(store, renderProps.components, renderProps.params) .then(() => { const initialView = renderToString(