Merged branch master into password-reset

This commit is contained in:
Riley Davis
2016-11-15 12:34:43 -07:00
15 changed files with 253 additions and 94 deletions
+8 -1
View File
@@ -5,7 +5,8 @@ import {
FETCH_COMMENTERS_SUCCESS,
FETCH_COMMENTERS_FAILURE,
SORT_UPDATE,
COMMENTERS_NEW_PAGE
COMMENTERS_NEW_PAGE,
SET_ROLE
} from '../constants/community';
import {base, getInit, handleResp} from '../helpers/response';
@@ -40,3 +41,9 @@ export const newPage = () => ({
type: COMMENTERS_NEW_PAGE
});
export const setRole = (id, role) => dispatch => {
return fetch(`${base}/user/${id}/role`, getInit('POST', {role}))
.then(() => {
return dispatch({type: SET_ROLE, id, role});
});
};
@@ -3,3 +3,4 @@ export const FETCH_COMMENTERS_SUCCESS = 'FETCH_COMMENTERS_SUCCESS';
export const FETCH_COMMENTERS_FAILURE = 'FETCH_COMMENTERS_FAILURE';
export const SORT_UPDATE = 'SORT_UPDATE';
export const COMMENTERS_NEW_PAGE = 'COMMENTERS_NEW_PAGE';
export const SET_ROLE = 'SET_ROLE';
@@ -19,6 +19,10 @@ const tableHeaders = [
{
title: lang.t('community.account_creation_date'),
field: 'created_at'
},
{
title: lang.t('community.newsroom_role'),
field: 'role'
}
];
@@ -1,34 +1,66 @@
import React from 'react';
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {SelectField, Option} from 'react-mdl-selectfield';
import styles from './Community.css';
import I18n from 'coral-framework/i18n/i18n';
import translations from '../../translations';
import {setRole} from '../../actions/community';
const Table = ({headers, data, onHeaderClickHandler}) => (
<table className={`mdl-data-table ${styles.dataTable}`}>
<thead>
<tr>
{headers.map((header, i) =>(
<th
key={i}
className="mdl-data-table__cell--non-numeric"
onClick={() => onHeaderClickHandler({field: header.field})}>
{header.title}
</th>
))}
</tr>
</thead>
<tbody>
{data.map((row, i)=> (
<tr key={i}>
<td className="mdl-data-table__cell--non-numeric">
{row.displayName}
<span className={styles.email}>{row.profiles.map(({id}) => id)}</span>
</td>
<td className="mdl-data-table__cell--non-numeric">
{row.created_at}
</td>
</tr>
))}
</tbody>
</table>
);
const lang = new I18n(translations);
export default Table;
class Table extends Component {
constructor (props) {
super(props);
this.onRoleChange = this.onRoleChange.bind(this);
}
onRoleChange (id, role) {
this.props.dispatch(setRole(id, role));
}
render () {
const {headers, commenters, onHeaderClickHandler} = this.props;
return (
<table className={`mdl-data-table ${styles.dataTable}`}>
<thead>
<tr>
{headers.map((header, i) =>(
<th
key={i}
className="mdl-data-table__cell--non-numeric"
onClick={() => onHeaderClickHandler({field: header.field})}>
{header.title}
</th>
))}
</tr>
</thead>
<tbody>
{commenters.map((row, i)=> (
<tr key={i}>
<td className="mdl-data-table__cell--non-numeric">
{row.displayName}
<span className={styles.email}>{row.profiles.map(({id}) => id)}</span>
</td>
<td className="mdl-data-table__cell--non-numeric">
{row.created_at}
</td>
<td className="mdl-data-table__cell--non-numeric">
<SelectField label={'Select me'} value={row.roles[0] || ''}
label={lang.t('community.role')}
onChange={role => this.onRoleChange(row.id, role)}>
<Option value={''}>.</Option>
<Option value={'moderator'}>{lang.t('community.moderator')}</Option>
<Option value={'admin'}>{lang.t('community.admin')}</Option>
</SelectField>
</td>
</tr>
))}
</tbody>
</table>
);
}
}
export default connect(state => ({commenters: state.community.get('commenters')}))(Table);
+9 -1
View File
@@ -4,7 +4,8 @@ import {
FETCH_COMMENTERS_REQUEST,
FETCH_COMMENTERS_FAILURE,
FETCH_COMMENTERS_SUCCESS,
SORT_UPDATE
SORT_UPDATE,
SET_ROLE
} from '../constants/community';
const initialState = Map({
@@ -37,6 +38,13 @@ export default function community (state = initialState, action) {
})
.set('commenters', commenters); // Sets to normal array
}
case SET_ROLE : {
const commenters = state.get('commenters');
const idx = commenters.findIndex(el => el.id === action.id);
commenters[idx].roles[0] = action.role;
return state.set('commenters', commenters.map(id => id));
}
case SORT_UPDATE :
return state
.set('field', action.sort.field)
@@ -34,7 +34,7 @@ export default store => next => action => {
// Get comments to fill each of the three lists on the mod queue
const fetchModerationQueueComments = store =>
Promise.all([fetch('/api/v1/comments/status/pending'), fetch('/api/v1/comments/status/rejected'), fetch('/api/v1/comments/action/flag')])
Promise.all([fetch('/api/v1/queue/comments/pending'), fetch('/api/v1/comments/status/rejected'), fetch('/api/v1/comments/action/flag')])
.then(res => Promise.all(res.map(r => r.json())))
.then(res => {
res[2] = res[2].map(comment => { comment.flagged = true; return comment; });
+10 -2
View File
@@ -2,7 +2,11 @@ export default {
en: {
'community': {
username_and_email: 'Username and Email',
account_creation_date: 'Account Creation Date'
account_creation_date: 'Account Creation Date',
newsroom_role: 'Newsroom Role',
admin: 'Administrator',
moderator: 'Moderator',
role: 'Select role...'
},
'modqueue': {
'pending': 'pending',
@@ -30,7 +34,11 @@ export default {
es: {
'community': {
username_and_email: 'Usuario y E-mail',
account_creation_date: 'Fecha de creación de la cuenta'
account_creation_date: 'Fecha de creación de la cuenta',
newsroom_role: 'Rol en la redacción',
admin: 'Administrador',
moderator: 'Moderador',
role: 'Select role...'
},
'modqueue': {
'pending': 'pendiente',
+3 -1
View File
@@ -24,7 +24,9 @@ const UserSchema = new mongoose.Schema({
required: true
}
}],
roles: [String]
roles: {
type: [{type: String, enum: ['admin', 'moderator']}]
}
}, {
timestamps: {
createdAt: 'created_at',
+1
View File
@@ -57,6 +57,7 @@
"nodemailer": "^2.6.4",
"nodemailer-sendgrid-transport": "^0.2.0",
"prompt": "^1.0.0",
"react-mdl-selectfield": "^0.2.0",
"uuid": "^2.0.3"
},
"devDependencies": {
+26 -36
View File
@@ -1,8 +1,6 @@
const express = require('express');
const Comment = require('../../../models/comment');
const Setting = require('../../../models/setting');
const router = express.Router();
//==============================================================================
@@ -25,11 +23,7 @@ router.get('/:comment_id', (req, res, next) => {
.catch(next);
});
//==============================================================================
// Moderation Queues Routes
//==============================================================================
// Get all the comments that have that action_type over them.
// Get all the comments that have an action_type over them.
router.get('/action/:action_type', (req, res, next) => {
Comment
.findByActionType(req.params.action_type)
@@ -41,34 +35,30 @@ router.get('/action/:action_type', (req, res, next) => {
// Get all the comments that were rejected.
router.get('/status/rejected', (req, res, next) => {
Comment
.findByStatus('rejected')
.then(comments => {
res.status(200).json(comments);
})
.catch(next);
Comment.findByStatus('rejected').then(comments => {
res.status(200).json(comments);
})
.catch(next);
});
// Returns back all the comments that are in the moderation queue. The moderation queue is pre or post moderated,
// depending on the settings. The :moderation overwrites this settings.
// 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('/status/pending', (req, res, next) => {
Setting
.getModerationSetting()
.then(({moderation}) => {
let moderationValue = req.query.moderation;
if (typeof moderationValue === 'undefined' || moderationValue === undefined) {
moderationValue = moderation;
}
Comment
.moderationQueue(moderationValue)
.then((comments) => {
res.status(200).json(comments);
});
})
.catch(next);
// Get all the comments that were accepted.
router.get('/status/accepted', (req, res, next) => {
Comment.findByStatus('accepted').then((comments) => {
res.status(200).json(comments);
})
.catch(error => {
next(error);
});
});
// Get all the not moderated comments.
router.get('/status/new', (req, res, next) => {
Comment.findByStatus('').then((comments) => {
res.status(200).json(comments);
})
.catch(error => {
next(error);
});
});
//==============================================================================
@@ -76,9 +66,9 @@ router.get('/status/pending', (req, res, next) => {
//==============================================================================
router.post('/', (req, res, next) => {
const {body, author_id, asset_id, parent_id, status, username} = req.body;
Comment
.new(body, author_id, asset_id, parent_id, status, username)
.then((comment) => {
@@ -109,7 +99,7 @@ router.post('/:comment_id', (req, res, next) => {
});
router.post('/:comment_id/status', (req, res, next) => {
Comment
.changeStatus(req.params.comment_id, req.body.status)
.then(comment => res.status(200).send(comment))
+1
View File
@@ -4,6 +4,7 @@ const router = express.Router();
router.use('/asset', require('./asset'));
router.use('/comments', require('./comments'));
router.use('/queue', require('./queue'));
router.use('/settings', require('./settings'));
router.use('/stream', require('./stream'));
router.use('/user', require('./user'));
+27
View File
@@ -0,0 +1,27 @@
const express = require('express');
const Comment = require('../../../models/comment');
const Setting = require('../../../models/setting');
const router = express.Router();
//==============================================================================
// Get Routes
//==============================================================================
// Returns back all the comments that are in the moderation queue. The moderation queue is pre or post moderated,
// depending on the settings. The :moderation overwrites this settings.
// 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);
});
})
.catch(error => {
next(error);
});
});
module.exports = router;
+12 -2
View File
@@ -40,11 +40,13 @@ router.get('/', (req, res, next) => {
])
.then(([data, count]) => {
const users = data.map((user) => {
const {displayName, created_at} = user;
const {id, displayName, created_at} = user;
return {
id,
displayName,
created_at,
profiles: user.toObject().profiles
profiles: user.toObject().profiles,
roles: user.toObject().roles
};
});
@@ -60,4 +62,12 @@ router.get('/', (req, res, next) => {
.catch(next);
});
router.post('/:user_id/role', (req, res, next) => {
User.addRoleToUser(req.params.user_id, req.body.role)
.then(role => {
res.send(role);
})
.catch(next);
});
module.exports = router;
+6 -19
View File
@@ -75,7 +75,7 @@ describe('Get /comments', () => {
});
});
describe('Get moderation queues rejected, pending, flags', () => {
describe('Get comments by status and action', () => {
const comments = [{
id: 'abc',
body: 'comment 10',
@@ -133,21 +133,20 @@ describe('Get moderation queues rejected, pending, flags', () => {
});
});
it('should return all the pending comments', function(done){
it('should return all the approved comments', function(done){
chai.request(app)
.get('/api/v1/comments/status/pending')
.get('/api/v1/comments/status/accepted')
.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[0]).to.have.property('id', 'hij');
done();
});
});
it('should return all the pending comments as pre moderated', function(done){
it('should return all the new comments', function(done){
chai.request(app)
.get('/api/v1/comments/status/pending')
.query({'moderation': 'pre'})
.get('/api/v1/comments/status/new')
.end(function(err, res){
expect(err).to.be.null;
expect(res).to.have.status(200);
@@ -156,18 +155,6 @@ describe('Get moderation queues rejected, pending, flags', () => {
});
});
it('should return all the pending comments as post moderated', function(done){
chai.request(app)
.get('/api/v1/comments/status/pending')
.query({'moderation': 'post'})
.end(function(err, res){
expect(err).to.be.null;
expect(res).to.have.status(200);
expect(res.body).to.have.lengthOf(0);
done();
});
});
it('should return all the flagged comments', function(done){
chai.request(app)
.get('/api/v1/comments/action/flag')
+81
View File
@@ -0,0 +1,81 @@
process.env.NODE_ENV = 'test';
require('../../../utils/mongoose');
const app = require('../../../../app');
const chai = require('chai');
const expect = chai.expect;
// Setup chai.
chai.should();
chai.use(require('chai-http'));
const Comment = require('../../../../models/comment');
const Action = require('../../../../models/action');
const User = require('../../../../models/user');
const Setting = require('../../../../models/setting');
const settings = {id: '1', moderation: 'pre'};
beforeEach(() => {
return Setting.create(settings);
});
describe('Get moderation queues rejected, pending, flags', () => {
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'
}];
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',
item_type: 'comment'
}, {
action_type: 'like',
item_id: 'hij',
item_type: 'comment'
}];
beforeEach(() => {
return Promise.all([
Comment.create(comments),
User.createLocalUsers(users),
Action.create(actions)
]);
});
it('should return all the pending comments', function(done){
chai.request(app)
.get('/api/v1/queue/comments/pending')
.end(function(err, res){
expect(err).to.be.null;
expect(res).to.have.status(200);
expect(res.body[0]).to.have.property('id', 'def');
done();
});
});
});