diff --git a/client/coral-admin/src/actions/community.js b/client/coral-admin/src/actions/community.js
index d6b77b61c..24c1b403d 100644
--- a/client/coral-admin/src/actions/community.js
+++ b/client/coral-admin/src/actions/community.js
@@ -49,7 +49,7 @@ export const setRole = (id, role) => dispatch => {
};
export const setCommenterStatus = (id, status) => dispatch => {
- return fetch(`${base}/user/${id}/status`, getInit('POST', {status}))
+ return coralApi(`/user/${id}/status`, {method: 'POST', body: {status}})
.then(() => {
return dispatch({type: SET_COMMENTER_STATUS, id, status});
});
diff --git a/client/coral-admin/src/components/Comment.js b/client/coral-admin/src/components/Comment.js
index b499dc1c6..998b5f774 100644
--- a/client/coral-admin/src/components/Comment.js
+++ b/client/coral-admin/src/components/Comment.js
@@ -12,17 +12,18 @@ const linkify = new Linkify();
// Render a single comment for the list
export default props => {
- const links = linkify.getMatches(props.comment.get('body'));
- const banned = props.comment.get('banned');
+ const author_status = props.author.get('status');
+ const {comment, author} = props;
+ const links = linkify.getMatches(comment.get('body'));
return (
person
-
{props.comment.get('name') || lang.t('comment.anon')}
-
{timeago().format(props.comment.get('createdAt') || (Date.now() - props.index * 60 * 1000), lang.getLocale().replace('-', '_'))}
- {props.comment.get('flagged') ?
{lang.t('comment.flagged')}
: null}
+
{author.get('displayName') || lang.t('comment.anon')}
+
{timeago().format(comment.get('createdAt') || (Date.now() - props.index * 60 * 1000), lang.getLocale().replace('-', '_'))}
+ {comment.get('flagged') ?
{lang.t('comment.flagged')}
: null}
{links ?
@@ -32,14 +33,14 @@ export default props => {
- {banned ?
+ {author_status === 'banned' ?
Banned User : null}
- {props.comment.get('body')}
+ {comment.get('body')}
@@ -59,6 +60,7 @@ const getActionButton = (action, i, props) => {
return (
);
}
diff --git a/client/coral-admin/src/components/CommentList.js b/client/coral-admin/src/components/CommentList.js
index 95b6c4117..2df70209e 100644
--- a/client/coral-admin/src/components/CommentList.js
+++ b/client/coral-admin/src/components/CommentList.js
@@ -113,13 +113,15 @@ export default class CommentList extends React.Component {
}
render () {
- const {singleView, commentIds, comments, hideActive, key} = this.props;
+ const {singleView, commentIds, comments, users, hideActive, key} = this.props;
const {active} = this.state;
return (
- {commentIds.map((commentId, index) => (
- {
+ const comment = comments.get(commentId);
+ return { if (el && commentId === active) { this._active = el; } }}
key={index}
index={index}
@@ -127,8 +129,8 @@ export default class CommentList extends React.Component {
actions={this.props.actions}
actionsMap={actions}
isActive={commentId === active}
- hideActive={hideActive} />
- )).toArray()}
+ hideActive={hideActive} />;
+ }).toArray()}
);
}
diff --git a/client/coral-admin/src/containers/CommentStream/CommentStream.js b/client/coral-admin/src/containers/CommentStream/CommentStream.js
index b1e002549..113a7bc86 100644
--- a/client/coral-admin/src/containers/CommentStream/CommentStream.js
+++ b/client/coral-admin/src/containers/CommentStream/CommentStream.js
@@ -40,7 +40,7 @@ class CommentStream extends React.Component {
}
// Render the comment box along with the CommentList
- render ({comments}, {snackbar, snackbarMsg}) {
+ render ({comments, users}, {snackbar, snackbarMsg}) {
return (
@@ -48,6 +48,7 @@ class CommentStream extends React.Component {
singleView={false}
commentIds={comments.get('ids')}
comments={comments.get('byId')}
+ users={users.get('byId')}
onClickAction={this.onClickAction}
actions={['flag']}
loading={comments.loading} />
@@ -57,4 +58,4 @@ class CommentStream extends React.Component {
}
}
-export default connect(({comments}) => ({comments}))(CommentStream);
+export default connect(({comments, users}) => ({comments, users}))(CommentStream);
diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js b/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js
index 2de7d7d13..b1a01c234 100644
--- a/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js
+++ b/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js
@@ -90,11 +90,11 @@ class ModerationQueue extends React.Component {
commentIds={
comments.get('ids')
.filter(id => !comments.get('byId')
- .get(id)
- .get('status'))
+ .get(id)
+ .get('status'))
}
comments={comments.get('byId')}
- commenters={commenters}
+ commenters={commenters.get('byId')}
onClickAction={(action, id) => this.onCommentAction(action, id)}
actions={['reject', 'approve', 'ban']}
loading={comments.loading} />
@@ -113,7 +113,7 @@ class ModerationQueue extends React.Component {
.get('status') === 'rejected')
}
comments={comments.get('byId')}
- commenters={commenters}
+ commenters={commenters.get('byId')}
onClickAction={(action, id) => this.onCommentAction(action, id)}
actions={['approve']}
loading={comments.loading} />
@@ -127,7 +127,7 @@ class ModerationQueue extends React.Component {
return !data.get('status') && data.get('flagged') === true;
})}
comments={comments.get('byId')}
- commenters={commenters}
+ commenters={commenters.get('byId')}
onClickAction={(action, id) => this.onCommentAction(action, id)}
actions={['reject', 'approve']}
loading={comments.loading} />
@@ -140,6 +140,6 @@ class ModerationQueue extends React.Component {
}
}
-export default connect(({comments}) => ({comments}))(ModerationQueue);
+export default connect(({comments, commenters}) => ({comments, commenters}))(ModerationQueue);
const lang = new I18n(translations);
diff --git a/client/coral-admin/src/reducers/index.js b/client/coral-admin/src/reducers/index.js
index 61029539a..1f1b444fc 100644
--- a/client/coral-admin/src/reducers/index.js
+++ b/client/coral-admin/src/reducers/index.js
@@ -2,6 +2,7 @@ import {combineReducers} from 'redux';
import comments from 'reducers/comments';
import settings from 'reducers/settings';
import community from 'reducers/community';
+import users from 'reducers/users';
import auth from 'reducers/auth';
// Combine all reducers into a main one
@@ -9,6 +10,6 @@ export default combineReducers({
settings,
comments,
community,
- auth
+ auth,
+ users
});
-
diff --git a/client/coral-admin/src/reducers/users.js b/client/coral-admin/src/reducers/users.js
new file mode 100644
index 000000000..872ae904a
--- /dev/null
+++ b/client/coral-admin/src/reducers/users.js
@@ -0,0 +1,20 @@
+import {Map, List, fromJS} from 'immutable';
+
+const initialState = Map({
+ byId: Map(),
+ ids: List()
+});
+
+export default (state = initialState, action) => {
+ switch (action.type) {
+ case 'USERS_MODERATION_QUEUE_FETCH_SUCCESS': return replaceUsers(action, state);
+ default: return state;
+ }
+};
+
+// Replace the comment list with a new one
+const replaceUsers = (action, state) => {
+ const users = fromJS(action.users.reduce((prev, curr) => { prev[curr.id] = curr; return prev; }, {}));
+ return state.set('byId', users)
+ .set('ids', List(users.keys()));
+};
diff --git a/client/coral-admin/src/services/talk-adapter.js b/client/coral-admin/src/services/talk-adapter.js
index b3c460007..8b9fb1a8d 100644
--- a/client/coral-admin/src/services/talk-adapter.js
+++ b/client/coral-admin/src/services/talk-adapter.js
@@ -15,9 +15,6 @@ export default store => next => action => {
case 'COMMENTS_MODERATION_QUEUE_FETCH':
fetchModerationQueueComments(store);
break;
- // case 'COMMENT_STREAM_FETCH':
- // fetchCommentStream(store);
- // break;
case 'COMMENT_UPDATE':
updateComment(store, action.comment);
break;
@@ -41,12 +38,31 @@ Promise.all([
coralApi('/comments?action_type=flag')
])
.then(([pending, rejected, flagged]) => {
- flagged.forEach(comment => comment.flagged = true);
- return [...pending, ...rejected, ...flagged];
+ /* Combine seperate calls into a single object */
+ let all = {};
+ all.comments = pending.comments
+ .concat(rejected.comments)
+ .concat(flagged.comments.map(comment => {
+ comment.flagged = true;
+ return comment;
+ }));
+ all.users = pending.users
+ .concat(rejected.users)
+ .concat(flagged.users);
+ all.actions = pending.actions
+ .concat(rejected.actions)
+ .concat(flagged.actions);
+ return all;
})
-.then(res => store.dispatch({type: 'COMMENTS_MODERATION_QUEUE_FETCH_SUCCESS',
- comments: res}))
-.catch(error => store.dispatch({type: 'COMMENTS_MODERATION_QUEUE_FETCH_FAILED', error}));
+.then(all => {
+ /* Post comments and users to redux store. Actions will be posted when they are needed. */
+ store.dispatch({type: 'USERS_MODERATION_QUEUE_FETCH_SUCCESS',
+ users: all.users});
+ store.dispatch({type: 'COMMENTS_MODERATION_QUEUE_FETCH_SUCCESS',
+ comments: all.comments});
+
+});
+// .catch(error => store.dispatch({type: 'COMMENTS_MODERATION_QUEUE_FETCH_FAILED', error}));
// Update a comment. Now to update a comment we need to send back the whole object
@@ -71,8 +87,7 @@ const createComment = (store, name, comment) => {
// Ban a user
const banUser = (store, comment) => {
- fetch(`${base}/user/${comment.get('author_id')}/status`, getInit('POST', {status: comment.get('status')}))
- .then(handleResp)
+ coralApi(`/user/${comment.get('author_id')}/status`, {method: 'POST', body: {status: comment.get('status')}})
.then(res => store.dispatch({type: 'USER_BAN_SUCESSS', res}))
.catch(error => store.dispatch({type: 'USER_BAN_FAILED', error}));
};
diff --git a/models/setting.js b/models/setting.js
index f0c6a1abc..e23f654a7 100644
--- a/models/setting.js
+++ b/models/setting.js
@@ -42,6 +42,7 @@ SettingSchema.statics.getSettings = function () {
* @return {Promise} moderation the settings for how to moderate comments
*/
SettingSchema.statics.getModerationSetting = function () {
+ console.log('Getting moderation setting');
return this.findOne({id: '1'}).select('moderation');
};
diff --git a/routes/api/comments/index.js b/routes/api/comments/index.js
index f766760f7..08e8bc967 100644
--- a/routes/api/comments/index.js
+++ b/routes/api/comments/index.js
@@ -1,7 +1,10 @@
const express = require('express');
const Comment = require('../../../models/comment');
+const User = require('../../../models/user');
+const Action = require('../../../models/action');
const wordlist = require('../../../services/wordlist');
const authorization = require('../../../middleware/authorization');
+const _ = require('lodash');
const router = express.Router();
@@ -16,9 +19,22 @@ router.get('/', authorization.needed('admin'), (req, res, next) => {
query = Comment.all();
}
- query.then(comments => {
- res.json(comments);
+ query.then((comments) => {
+ return Promise.all([
+ comments,
+ User.findByIdArray(_.uniq(comments.map((comment) => comment.author_id))),
+ Action.getActionSummaries(_.uniq([
+ ...comments.map((comment) => comment.id),
+ ...comments.map((comment) => comment.author_id)
+ ]))
+ ]);
})
+ .then(([comments, users, actions])=>
+ res.status(200).json({
+ comments,
+ users,
+ actions
+ }))
.catch((err) => {
next(err);
});
diff --git a/routes/api/queue/index.js b/routes/api/queue/index.js
index f661992f1..448149387 100644
--- a/routes/api/queue/index.js
+++ b/routes/api/queue/index.js
@@ -1,6 +1,9 @@
const express = require('express');
const Comment = require('../../../models/comment');
+const User = require('../../../models/user');
+const Action = require('../../../models/action');
const Setting = require('../../../models/setting');
+const _ = require('lodash');
const router = express.Router();
@@ -13,11 +16,24 @@ const router = express.Router();
// Pre-moderation: New comments are shown in the moderator queues immediately.
// Post-moderation: New comments do not appear in moderation queues unless they are flagged by other users.
router.get('/comments/pending', (req, res, next) => {
- Setting.getModerationSetting().then(function({moderation}){
- Comment.moderationQueue(moderation).then((comments) => {
- res.status(200).json(comments);
- });
+ Setting.getModerationSetting().then(({moderation}) =>
+ Comment.moderationQueue(moderation))
+ .then((comments) => {
+ return Promise.all([
+ comments,
+ User.findByIdArray(_.uniq(comments.map((comment) => comment.author_id))),
+ Action.getActionSummaries(_.uniq([
+ ...comments.map((comment) => comment.id),
+ ...comments.map((comment) => comment.author_id)
+ ]))
+ ]);
})
+ .then(([comments, users, actions])=>
+ res.status(200).json({
+ comments,
+ users,
+ actions
+ }))
.catch(error => {
next(error);
});
diff --git a/routes/api/stream/index.js b/routes/api/stream/index.js
index 5a110e2d6..ce8871ab8 100644
--- a/routes/api/stream/index.js
+++ b/routes/api/stream/index.js
@@ -34,7 +34,7 @@ router.get('/', (req, res, next) => {
// Merge the asset specific settings with the returned settings object in
// the event that the asset that was returned also had settings.
if (asset.settings) {
- settings = Object.assign(settings, asset.settings);
+ settings = Object.assign({}, settings, asset.settings);
}
// Fetch the appropriate comments stream.
diff --git a/tests/routes/api/comments/index.js b/tests/routes/api/comments/index.js
index 5ac9af7b7..bc8b67bc1 100644
--- a/tests/routes/api/comments/index.js
+++ b/tests/routes/api/comments/index.js
@@ -37,6 +37,7 @@ describe('/api/v1/comments', () => {
id: 'hij',
body: 'comment 30',
asset_id: '456',
+ author_id: '456',
status: 'accepted'
}];
@@ -90,7 +91,7 @@ describe('/api/v1/comments', () => {
.set(passport.inject({roles: ['admin']}))
.then((res) => {
expect(res).to.have.status(200);
- expect(res.body[0]).to.have.property('id', 'def-rejected');
+ expect(res.body.comments[0]).to.have.property('id', 'def-rejected');
});
});
@@ -100,8 +101,8 @@ describe('/api/v1/comments', () => {
.set(passport.inject({roles: ['admin']}))
.then((res) => {
expect(res).to.have.status(200);
- expect(res.body).to.have.length(1);
- expect(res.body[0]).to.have.property('id', 'hij');
+ expect(res.body.comments).to.have.length(1);
+ expect(res.body.comments[0]).to.have.property('id', 'hij');
});
});
@@ -111,7 +112,7 @@ describe('/api/v1/comments', () => {
.set(passport.inject({roles: ['admin']}))
.then((res) => {
expect(res).to.have.status(200);
- expect(res.body).to.have.length(2);
+ expect(res.body.comments).to.have.length(2);
});
});
@@ -122,8 +123,8 @@ describe('/api/v1/comments', () => {
.then((res) => {
expect(res).to.have.status(200);
- expect(res.body).to.have.length(1);
- expect(res.body[0]).to.have.property('id', 'abc');
+ expect(res.body.comments).to.have.length(1);
+ expect(res.body.comments[0]).to.have.property('id', 'abc');
});
});
diff --git a/tests/routes/api/queue/index.js b/tests/routes/api/queue/index.js
index 733e5a1be..1e99ae61e 100644
--- a/tests/routes/api/queue/index.js
+++ b/tests/routes/api/queue/index.js
@@ -15,63 +15,81 @@ const User = require('../../../../models/user');
const Setting = require('../../../../models/setting');
const settings = {id: '1', moderation: 'pre'};
-describe('/api/v1/queue', () => {
- const comments = [{
- id: 'abc',
- body: 'comment 10',
- asset_id: 'asset',
- author_id: '123',
- status: 'rejected'
- }, {
- id: 'def',
- body: 'comment 20',
- asset_id: 'asset',
- author_id: '456'
- }, {
- id: 'hij',
- body: 'comment 30',
- asset_id: '456',
- status: 'accepted'
- }];
+beforeEach(() => {
+ return Setting.create(settings);
+});
- const users = [{
- displayName: 'Ana',
- email: 'ana@gmail.com',
- password: '123'
- }, {
- displayName: 'Maria',
- email: 'maria@gmail.com',
- password: '123'
- }];
+describe('Get moderation queues rejected, pending, flags', () => {
- const actions = [{
- action_type: 'flag',
- item_id: 'abc',
- item_type: 'comment'
- }, {
- action_type: 'like',
- item_id: 'hij',
- item_type: 'comment'
- }];
+ describe('/api/v1/queue', () => {
+ let comments;
- beforeEach(() => {
- return Promise.all([
- Comment.create(comments),
- User.createLocalUsers(users),
- Action.create(actions),
- Setting.create(settings)
- ]);
- });
+ const users = [{
+ id: '456',
+ displayName: 'Ana',
+ email: 'ana@gmail.com',
+ password: '123'
+ }, {
+ id: '123',
+ displayName: 'Maria',
+ email: 'maria@gmail.com',
+ password: '123'
+ }];
- describe('#get', () => {
- it('should return all the pending comments', function(done){
+ let actions;
+
+ beforeEach(() => {
+
+ comments = [{
+ id: 'abc',
+ body: 'comment 10',
+ asset_id: 'asset',
+ status: 'rejected'
+ }, {
+ id: 'def',
+ body: 'comment 20',
+ asset_id: 'asset'
+ }, {
+ id: 'hij',
+ body: 'comment 30',
+ asset_id: '456',
+ status: 'accepted'
+ }];
+
+ actions = [{
+ action_type: 'flag',
+ item_type: 'comment'
+ }, {
+ action_type: 'like',
+ item_type: 'comment'
+ }];
+
+ return User.createLocalUsers(users)
+ .then((u) => {
+ comments[0].author_id = u[0].id;
+ comments[1].author_id = u[1].id;
+ comments[2].author_id = u[1].id;
+
+ return Comment.create(comments);
+ })
+ .then((c) => {
+ actions[0].item_id = c[0].id;
+ actions[1].item_id = c[1].id;
+
+ return Action.create(actions);
+ });
+ });
+
+ it('should return all the pending comments, users and actions', function(done){
chai.request(app)
.get('/api/v1/queue/comments/pending')
.set(passport.inject({roles: ['admin']}))
.end(function(err, res){
expect(err).to.be.null;
expect(res).to.have.status(200);
- expect(res.body[0]).to.have.property('id', 'def');
+ expect(res.body.comments[0]).to.have.property('body');
+ expect(res.body.users[0]).to.have.property('displayName');
+ expect(res.body.actions[0]).to.have.property('action_type');
done();
});
});
diff --git a/tests/routes/api/stream/index.js b/tests/routes/api/stream/index.js
index ea08ceebb..6484fdc12 100644
--- a/tests/routes/api/stream/index.js
+++ b/tests/routes/api/stream/index.js
@@ -14,90 +14,90 @@ const Asset = require('../../../../models/asset');
const Setting = require('../../../../models/setting');
describe('/api/v1/stream', () => {
+ describe('#get', () => {
+ const settings = {
+ id: '1',
+ moderation: 'post'
+ };
- const settings = {
- id: '1',
- moderation: 'post'
- };
+ let comments;
- const comments = [{
- id: 'abc',
- body: 'comment 10',
- author_id: '',
- parent_id: '',
- status: 'accepted'
- }, {
- id: 'def',
- body: 'comment 20',
- author_id: '',
- parent_id: '',
- status: ''
- }, {
- id: 'uio',
- body: 'comment 30',
- asset_id: 'asset',
- author_id: '456',
- parent_id: '',
- status: 'accepted'
- }, {
- id: 'hij',
- body: 'comment 40',
- asset_id: '456',
- status: 'rejected'
- }];
+ const users = [{
+ displayName: 'Ana',
+ email: 'ana@gmail.com',
+ password: '123'
+ }, {
+ displayName: 'Maria',
+ email: 'maria@gmail.com',
+ password: '123'
+ }];
- const users = [{
- displayName: 'Ana',
- email: 'ana@gmail.com',
- password: '123'
- }, {
- displayName: 'Maria',
- email: 'maria@gmail.com',
- password: '123'
- }];
+ const actions = [{
+ action_type: 'flag',
+ item_id: 'abc'
+ }, {
+ action_type: 'like',
+ item_id: 'hij'
+ }];
- const actions = [{
- action_type: 'flag',
- item_id: 'abc'
- }, {
- action_type: 'like',
- item_id: 'hij'
- }];
+ beforeEach(() => {
- beforeEach(() => {
-
- return Promise.all([
- User.createLocalUsers(users),
- Asset.findOrCreateByUrl('http://test.com'),
- Asset
- .findOrCreateByUrl('http://coralproject.net/asset2')
- .then((asset) => {
- return Asset
- .overrideSettings(asset.id, {moderation: 'pre'})
- .then(() => asset);
- })
- ])
- .then(([users, asset1, asset2]) => {
-
- comments[0].author_id = users[0].id;
- comments[1].author_id = users[1].id;
- comments[2].author_id = users[0].id;
- comments[3].author_id = users[1].id;
-
- comments[0].asset_id = asset1.id;
- comments[1].asset_id = asset1.id;
- comments[2].asset_id = asset2.id;
- comments[3].asset_id = asset2.id;
+ comments = [{
+ id: 'abc',
+ body: 'comment 10',
+ author_id: '',
+ parent_id: '',
+ status: 'accepted'
+ }, {
+ id: 'def',
+ body: 'comment 20',
+ author_id: '',
+ parent_id: '',
+ status: ''
+ }, {
+ id: 'uio',
+ body: 'comment 30',
+ asset_id: 'asset',
+ author_id: '456',
+ parent_id: '',
+ status: 'accepted'
+ }, {
+ id: 'hij',
+ body: 'comment 40',
+ asset_id: '456',
+ status: 'rejected'
+ }];
return Promise.all([
- Comment.create(comments),
- Action.create(actions),
- Setting.create(settings)
- ]);
- });
- });
+ User.createLocalUsers(users),
+ Asset.findOrCreateByUrl('http://test.com'),
+ Asset
+ .findOrCreateByUrl('http://coralproject.net/asset2')
+ .then((asset) => {
+ return Asset
+ .overrideSettings(asset.id, {moderation: 'pre'})
+ .then(() => asset);
+ })
+ ])
+ .then(([users, asset1, asset2]) => {
- describe('#get', () => {
+ comments[0].author_id = users[0].id;
+ comments[1].author_id = users[1].id;
+ comments[2].author_id = users[0].id;
+ comments[3].author_id = users[1].id;
+
+ comments[0].asset_id = asset1.id;
+ comments[1].asset_id = asset1.id;
+ comments[2].asset_id = asset2.id;
+ comments[3].asset_id = asset2.id;
+
+ return Promise.all([
+ Comment.create(comments),
+ Action.create(actions),
+ Setting.init().then(() => Setting.updateSettings(settings))
+ ]);
+ });
+ });
it('should return a stream with comments, users and actions for an existing asset', () => {
return chai.request(app)
.get('/api/v1/stream')