mirror of
https://github.com/wassname/talk.git
synced 2026-07-09 00:48:15 +08:00
@@ -219,6 +219,7 @@ function listUsers() {
|
||||
'Display Name',
|
||||
'Profiles',
|
||||
'Roles',
|
||||
'Status',
|
||||
'State'
|
||||
]
|
||||
});
|
||||
@@ -229,6 +230,7 @@ function listUsers() {
|
||||
user.displayName,
|
||||
user.profiles.map((p) => p.provider).join(', '),
|
||||
user.roles.join(', '),
|
||||
user.status,
|
||||
user.disabled ? 'Disabled' : 'Enabled'
|
||||
]);
|
||||
});
|
||||
@@ -296,6 +298,40 @@ function removeRole(userID, role) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Ban a user
|
||||
* @param {String} userID id of the user to ban
|
||||
*/
|
||||
function ban(userID) {
|
||||
User
|
||||
.setStatus(userID, 'banned', '')
|
||||
.then(() => {
|
||||
console.log(`Banned the User ${userID}.`);
|
||||
util.shutdown();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
util.shutdown(1);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Unban a user
|
||||
* @param {String} userUD id of the user to remove the role from
|
||||
*/
|
||||
function unban(userID) {
|
||||
User
|
||||
.setStatus(userID, 'active', '')
|
||||
.then(() => {
|
||||
console.log(`Unban the User ${userID}.`);
|
||||
util.shutdown();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
util.shutdown(1);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable a given user.
|
||||
* @param {String} userID the ID of a user to disable
|
||||
@@ -384,6 +420,16 @@ program
|
||||
.description('removes a role from a given user')
|
||||
.action(removeRole);
|
||||
|
||||
program
|
||||
.command('ban <userID>')
|
||||
.description('ban a given user')
|
||||
.action(ban);
|
||||
|
||||
program
|
||||
.command('uban <userID>')
|
||||
.description('unban a given user')
|
||||
.action(unban);
|
||||
|
||||
program
|
||||
.command('disable <userID>')
|
||||
.description('disable a given user from logging in')
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
/**
|
||||
* Action disptacher related to comments
|
||||
*/
|
||||
@@ -16,3 +15,16 @@ export const flagComment = id => (dispatch, getState) => {
|
||||
export const createComment = (name, body) => dispatch => {
|
||||
dispatch({type: 'COMMENT_CREATE', name, body});
|
||||
};
|
||||
|
||||
// Dialog Actions
|
||||
export const showBanUserDialog = (userId, userName, commentId) => {
|
||||
return dispatch => {
|
||||
dispatch({type: 'SHOW_BANUSER_DIALOG', userId, userName, commentId});
|
||||
};
|
||||
};
|
||||
|
||||
export const hideBanUserDialog = (showDialog) => {
|
||||
return dispatch => {
|
||||
dispatch({type: 'HIDE_BANUSER_DIALOG', showDialog});
|
||||
};
|
||||
};
|
||||
|
||||
@@ -6,7 +6,8 @@ import {
|
||||
FETCH_COMMENTERS_FAILURE,
|
||||
SORT_UPDATE,
|
||||
COMMENTERS_NEW_PAGE,
|
||||
SET_ROLE
|
||||
SET_ROLE,
|
||||
SET_COMMENTER_STATUS
|
||||
} from '../constants/community';
|
||||
|
||||
import coralApi from '../../../coral-framework/helpers/response';
|
||||
@@ -46,3 +47,10 @@ export const setRole = (id, role) => dispatch => {
|
||||
return dispatch({type: SET_ROLE, id, role});
|
||||
});
|
||||
};
|
||||
|
||||
export const setCommenterStatus = (id, status) => dispatch => {
|
||||
return coralApi(`/user/${id}/status`, {method: 'POST', body: {status}})
|
||||
.then(() => {
|
||||
return dispatch({type: SET_COMMENTER_STATUS, id, status});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
|
||||
/**
|
||||
* Action disptacher related to users
|
||||
*/
|
||||
//
|
||||
// export const banUser = (status, author_id) => (dispatch) => {
|
||||
// dispatch({type: 'USER_STATUS_UPDATE', author_id, status});
|
||||
// };
|
||||
export const banUser = (status, userId, commentId) => {
|
||||
return dispatch => {
|
||||
dispatch({type: 'USER_BAN', status, userId, commentId});
|
||||
dispatch({type: 'COMMENTS_MODERATION_QUEUE_FETCH'});
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,147 @@
|
||||
.dialog {
|
||||
border: none;
|
||||
box-shadow: 0 9px 46px 8px rgba(0, 0, 0, 0.14), 0 11px 15px -7px rgba(0, 0, 0, 0.12), 0 24px 38px 3px rgba(0, 0, 0, 0.2);
|
||||
width: 280px;
|
||||
top: 10px;
|
||||
}
|
||||
|
||||
.header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.header h1, .separator h1{
|
||||
text-align: center;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.formField {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.formField label {
|
||||
font-size: 1.08em;
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.formField input {
|
||||
width: 100%;
|
||||
display: block;
|
||||
border: none;
|
||||
outline: none;
|
||||
border: 1px solid rgba(0,0,0,.12);
|
||||
padding: 10px 6px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 2px;
|
||||
margin: 5px auto;
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin: 20px auto 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.footer span {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.footer a {
|
||||
color: #2c69b6;
|
||||
cursor: pointer;
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
.socialConnections {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.signInButton {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.close {
|
||||
font-size: 20px;
|
||||
line-height: 14px;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
position: absolute;
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
color: #363636;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close:hover {
|
||||
color: #6b6b6b;
|
||||
}
|
||||
|
||||
input.error{
|
||||
border: solid 2px #f44336;
|
||||
}
|
||||
|
||||
.errorMsg, .hint {
|
||||
color: grey;
|
||||
font-weight: 600;
|
||||
padding: 3px 0 16px;
|
||||
}
|
||||
|
||||
.alert {
|
||||
padding: 10px;
|
||||
margin-bottom: 20px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.alert--success {
|
||||
border: solid 1px #1ec00e;
|
||||
background: #cbf1b8;
|
||||
color: #006900;
|
||||
}
|
||||
|
||||
.alert--error {
|
||||
background: #FFEBEE;
|
||||
color: #B71C1C;
|
||||
}
|
||||
|
||||
.userBox a {
|
||||
color: #2c69b6;
|
||||
cursor: pointer;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.attention {
|
||||
display: inline-block;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
background: #B71C1C;
|
||||
color: #FFEBEE;
|
||||
font-weight: bolder;
|
||||
padding: 4px;
|
||||
vertical-align: middle;
|
||||
border-radius: 20px;
|
||||
box-sizing: border-box;
|
||||
font-size: 9px;
|
||||
line-height: 7px;
|
||||
text-align: center;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.action {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.passwordRequestSuccess {
|
||||
border: 1px solid green;
|
||||
background-color: lightgreen;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.passwordRequestFailure {
|
||||
border: 1px solid orange;
|
||||
background-color: 1px solid coral;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.cancel {
|
||||
margin: 10px 0;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import React from 'react';
|
||||
|
||||
import {Dialog} from 'coral-ui';
|
||||
import Button from 'coral-ui/components/Button';
|
||||
|
||||
import styles from './BanUserDialog.css';
|
||||
|
||||
import I18n from 'coral-framework/modules/i18n/i18n';
|
||||
import translations from '../translations';
|
||||
const lang = new I18n(translations);
|
||||
|
||||
const BanUserDialog = ({open, handleClose, onClickBanUser, user = {}}) => {
|
||||
const {userName = '', userId = '', commentId = ''} = user;
|
||||
|
||||
return (
|
||||
<Dialog className={styles.dialog} open={open} onClose={() => handleClose()} onCancel={() => handleClose()} title={lang.t('bandialog.ban_user')}>
|
||||
<span className={styles.close} onClick={() => handleClose()}>×</span>
|
||||
<div>
|
||||
<div className={styles.header}>
|
||||
<h3>
|
||||
{lang.t('bandialog.ban_user')}
|
||||
</h3>
|
||||
</div>
|
||||
<div className={styles.separator}>
|
||||
<h4>
|
||||
{lang.t('bandialog.are_you_sure', userName)}
|
||||
</h4>
|
||||
<i>
|
||||
{lang.t('bandialog.note')}
|
||||
</i>
|
||||
</div>
|
||||
<div className={styles.buttons}>
|
||||
<Button cStyle="cancel" className={styles.cancel} onClick={() => handleClose()} full>
|
||||
{lang.t('bandialog.cancel')}
|
||||
</Button>
|
||||
<Button cStyle="black" onClick={() => onClickBanUser(userId, commentId)} full>
|
||||
{lang.t('bandialog.yes_ban_user')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default BanUserDialog;
|
||||
@@ -1,17 +1,20 @@
|
||||
|
||||
import React from 'react';
|
||||
import timeago from 'timeago.js';
|
||||
import Linkify from 'react-linkify';
|
||||
|
||||
import styles from './CommentList.css';
|
||||
|
||||
import I18n from 'coral-framework/modules/i18n/i18n';
|
||||
import translations from '../translations.json';
|
||||
import Linkify from 'react-linkify';
|
||||
import {FabButton} from 'coral-ui';
|
||||
|
||||
import {Icon} from 'react-mdl';
|
||||
import {FabButton, Button} from 'coral-ui';
|
||||
|
||||
const linkify = new Linkify();
|
||||
|
||||
// Render a single comment for the list
|
||||
export default props => {
|
||||
const authorStatus = props.author.get('status');
|
||||
const {comment, author} = props;
|
||||
const links = linkify.getMatches(comment.get('body'));
|
||||
|
||||
@@ -28,15 +31,13 @@ export default props => {
|
||||
{links ?
|
||||
<span className={styles.hasLinks}><Icon name='error_outline'/> Contains Link</span> : null}
|
||||
<div className={styles.actions}>
|
||||
{props.actions.map((action, i) => canShowAction(action, comment) ? (
|
||||
<FabButton icon={props.actionsMap[action].icon} className={styles.actionButton}
|
||||
cStyle={action}
|
||||
key={i}
|
||||
onClick={() => props.onClickAction(props.actionsMap[action].status, comment.get('id'))}
|
||||
/>
|
||||
) : null)}
|
||||
{props.actions.map((action, i) => getActionButton(action, i, props))}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{authorStatus === 'banned' ?
|
||||
<span className={styles.banned}><Icon name='error_outline'/> {lang.t('comment.banned_user')}</span> : null}
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.itemBody}>
|
||||
<span className={styles.body}>
|
||||
@@ -49,15 +50,33 @@ export default props => {
|
||||
);
|
||||
};
|
||||
|
||||
// Check if an action can be performed over a comment
|
||||
const canShowAction = (action, comment) => {
|
||||
const status = comment.get('status');
|
||||
const flagged = comment.get('flagged');
|
||||
// Get the button of the action performed over a comment if any
|
||||
const getActionButton = (action, i, props) => {
|
||||
const status = props.comment.get('status');
|
||||
const flagged = props.comment.get('flagged');
|
||||
const banned = (props.author.get('status') === 'banned');
|
||||
|
||||
if (action === 'flag' && (status || flagged === true)) {
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
return true;
|
||||
if (action === 'ban') {
|
||||
return (
|
||||
<Button
|
||||
disabled={banned ? 'disabled' : ''}
|
||||
cStyle='black'
|
||||
onClick={() => props.onClickShowBanDialog(props.author.get('id'), props.author.get('displayName'), props.comment.get('id'))}
|
||||
key={i} >
|
||||
{lang.t('comment.ban_user')}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<FabButton icon={props.actionsMap[action].icon} className={styles.actionButton}
|
||||
cStyle={action}
|
||||
key={i}
|
||||
onClick={() => props.onClickAction(props.actionsMap[action].status, props.comment.get('id'))}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const linkStyles = {
|
||||
|
||||
@@ -122,7 +122,6 @@
|
||||
|
||||
}
|
||||
|
||||
|
||||
.hasLinks {
|
||||
color: #f00;
|
||||
text-align: right;
|
||||
@@ -133,3 +132,14 @@
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.banned {
|
||||
color: #f00;
|
||||
text-align: left;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
i {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,8 @@ import Comment from 'components/Comment';
|
||||
const actions = {
|
||||
'reject': {status: 'rejected', icon: 'close', key: 'r'},
|
||||
'approve': {status: 'accepted', icon: 'done', key: 't'},
|
||||
'flag': {status: 'flagged', icon: 'flag', filter: 'Untouched'}
|
||||
'flag': {status: 'flagged', icon: 'flag', filter: 'Untouched'},
|
||||
'ban': {status: 'banned', icon: 'not interested'}
|
||||
};
|
||||
|
||||
// Renders a comment list and allow performing actions
|
||||
@@ -19,6 +20,7 @@ export default class CommentList extends React.Component {
|
||||
|
||||
this.state = {active: null};
|
||||
this.onClickAction = this.onClickAction.bind(this);
|
||||
this.onClickShowBanDialog = this.onClickShowBanDialog.bind(this);
|
||||
}
|
||||
|
||||
// remove key handlers before leaving
|
||||
@@ -99,7 +101,8 @@ export default class CommentList extends React.Component {
|
||||
// If we are performing an action over a comment (aka removing from the list) we need to select a new active.
|
||||
// TODO: In the future this can be improved and look at the actual state to
|
||||
// resolve since the content of the list could change externally. For now it works as expected
|
||||
onClickAction (action, id) {
|
||||
onClickAction (action, id, author_id) {
|
||||
// activate the next comment
|
||||
if (id === this.state.active) {
|
||||
const {commentIds} = this.props;
|
||||
if (commentIds.last() === this.state.active) {
|
||||
@@ -108,7 +111,11 @@ export default class CommentList extends React.Component {
|
||||
this.setState({active: commentIds.get(Math.min(commentIds.indexOf(this.state.active) + 1, commentIds.size - 1))});
|
||||
}
|
||||
}
|
||||
this.props.onClickAction(action, id);
|
||||
this.props.onClickAction(action, id, author_id);
|
||||
}
|
||||
|
||||
onClickShowBanDialog(userId, userName, commentId) {
|
||||
this.props.onClickShowBanDialog(userId, userName, commentId);
|
||||
}
|
||||
|
||||
render () {
|
||||
@@ -125,6 +132,7 @@ export default class CommentList extends React.Component {
|
||||
key={index}
|
||||
index={index}
|
||||
onClickAction={this.onClickAction}
|
||||
onClickShowBanDialog={this.onClickShowBanDialog}
|
||||
actions={this.props.actions}
|
||||
actionsMap={actions}
|
||||
isActive={commentId === active}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export const SHOW_BANUSER_DIALOG = 'SHOW_BANUSER_DIALOG';
|
||||
export const HIDE_BANUSER_DIALOG = 'HIDE_BANUSER_DIALOG';
|
||||
export const USER_BAN_SUCESS = 'USER_BAN_SUCESS';
|
||||
@@ -4,3 +4,4 @@ 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';
|
||||
export const SET_COMMENTER_STATUS = 'SET_COMMENTER_STATUS';
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
.dataTable {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.roleButton {
|
||||
display: block;
|
||||
}
|
||||
@@ -9,14 +5,13 @@
|
||||
.searchInput {
|
||||
display: block;
|
||||
padding-left: 40px;
|
||||
/*border: none;*/
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.searchBox {
|
||||
/*border: 1px solid rgba(0,0,0,.12);*/
|
||||
background: white;
|
||||
}
|
||||
|
||||
.email {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,10 @@ const tableHeaders = [
|
||||
title: lang.t('community.account_creation_date'),
|
||||
field: 'created_at'
|
||||
},
|
||||
{
|
||||
title: lang.t('community.status'),
|
||||
field: 'status'
|
||||
},
|
||||
{
|
||||
title: lang.t('community.newsroom_role'),
|
||||
field: 'role'
|
||||
@@ -30,7 +34,7 @@ const Community = ({isFetching, commenters, ...props}) => {
|
||||
const hasResults = !isFetching && !!commenters.length;
|
||||
return (
|
||||
<Grid>
|
||||
<Cell col={4}>
|
||||
<Cell col={2}>
|
||||
<form action="">
|
||||
<div className={`mdl-textfield ${styles.searchBox}`}>
|
||||
<label className="mdl-button mdl-js-button mdl-button--icon" htmlFor="commenters-search">
|
||||
@@ -49,7 +53,7 @@ const Community = ({isFetching, commenters, ...props}) => {
|
||||
</div>
|
||||
</form>
|
||||
</Cell>
|
||||
<Cell col={8}>
|
||||
<Cell col={6}>
|
||||
{ isFetching && <Loading /> }
|
||||
{ !hasResults && <NoResults /> }
|
||||
{ hasResults &&
|
||||
|
||||
@@ -4,7 +4,7 @@ import {SelectField, Option} from 'react-mdl-selectfield';
|
||||
import styles from './Community.css';
|
||||
import I18n from 'coral-framework/modules/i18n/i18n';
|
||||
import translations from '../../translations';
|
||||
import {setRole} from '../../actions/community';
|
||||
import {setRole, setCommenterStatus} from '../../actions/community';
|
||||
|
||||
const lang = new I18n(translations);
|
||||
|
||||
@@ -19,6 +19,10 @@ class Table extends Component {
|
||||
this.props.dispatch(setRole(id, role));
|
||||
}
|
||||
|
||||
onCommenterStatusChange (id, status) {
|
||||
this.props.dispatch(setCommenterStatus(id, status));
|
||||
}
|
||||
|
||||
render () {
|
||||
const {headers, commenters, onHeaderClickHandler} = this.props;
|
||||
|
||||
@@ -46,6 +50,14 @@ class Table extends Component {
|
||||
<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.status || ''}
|
||||
label={lang.t('community.status')}
|
||||
onChange={status => this.onCommenterStatusChange(row.id, status)}>
|
||||
<Option value={'active'}>{lang.t('community.active')}</Option>
|
||||
<Option value={'banned'}>{lang.t('community.banned')}</Option>
|
||||
</SelectField>
|
||||
</td>
|
||||
<td className="mdl-data-table__cell--non-numeric">
|
||||
<SelectField label={'Select me'} value={row.roles[0] || ''}
|
||||
label={lang.t('community.role')}
|
||||
|
||||
@@ -4,8 +4,10 @@ import key from 'keymaster';
|
||||
|
||||
import ModerationKeysModal from 'components/ModerationKeysModal';
|
||||
import CommentList from 'components/CommentList';
|
||||
import BanUserDialog from 'components/BanUserDialog';
|
||||
|
||||
import {updateStatus} from 'actions/comments';
|
||||
import {updateStatus, showBanUserDialog, hideBanUserDialog} from 'actions/comments';
|
||||
import {banUser} from 'actions/users';
|
||||
import styles from './ModerationQueue.css';
|
||||
|
||||
import I18n from 'coral-framework/modules/i18n/i18n';
|
||||
@@ -51,8 +53,21 @@ class ModerationQueue extends React.Component {
|
||||
}
|
||||
|
||||
// Dispatch the update status action
|
||||
onCommentAction (status, id) {
|
||||
this.props.dispatch(updateStatus(status, id));
|
||||
onCommentAction (action, id) {
|
||||
// If not banning then change the status to approved or flagged as action = status
|
||||
this.props.dispatch(updateStatus(action, id));
|
||||
}
|
||||
|
||||
showBanUserDialog (userId, userName, commentId) {
|
||||
this.props.dispatch(showBanUserDialog(userId, userName, commentId));
|
||||
}
|
||||
|
||||
hideBanUserDialog () {
|
||||
this.props.dispatch(hideBanUserDialog(false));
|
||||
}
|
||||
|
||||
banUser (userId, commentId) {
|
||||
this.props.dispatch(banUser('banned', userId, commentId));
|
||||
}
|
||||
|
||||
onTabClick (activeTab) {
|
||||
@@ -89,10 +104,16 @@ class ModerationQueue extends React.Component {
|
||||
}
|
||||
comments={comments.get('byId')}
|
||||
users={users.get('byId')}
|
||||
onClickAction={(action, id) => this.onCommentAction(action, id)}
|
||||
actions={['reject', 'approve']}
|
||||
onClickAction={(action, commentId) => this.onCommentAction(action, commentId)}
|
||||
onClickShowBanDialog={(userId, userName, commentId) => this.showBanUserDialog(userId, userName, commentId)}
|
||||
actions={['reject', 'approve', 'ban']}
|
||||
loading={comments.loading} />
|
||||
</div>
|
||||
<BanUserDialog
|
||||
open={comments.get('showBanUserDialog')}
|
||||
handleClose={() => this.hideBanUserDialog()}
|
||||
onClickBanUser={(userId, commentId) => this.banUser(userId, commentId)}
|
||||
user={comments.get('banUser')}/>
|
||||
</div>
|
||||
<div className={`mdl-tabs__panel ${styles.listContainer}`} id='rejected'>
|
||||
<CommentList
|
||||
isActive={activeTab === 'rejected'}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
|
||||
import * as actions from '../constants/comments';
|
||||
import {Map, List, fromJS} from 'immutable';
|
||||
|
||||
/**
|
||||
@@ -11,7 +11,13 @@ import {Map, List, fromJS} from 'immutable';
|
||||
const initialState = Map({
|
||||
byId: Map(),
|
||||
ids: List(),
|
||||
loading: false
|
||||
loading: false,
|
||||
showBanUserDialog: false,
|
||||
banUser: {
|
||||
'userName': '',
|
||||
'userId': '',
|
||||
'commentId': ''
|
||||
}
|
||||
});
|
||||
|
||||
// Handle the comment actions
|
||||
@@ -24,10 +30,21 @@ export default (state = initialState, action) => {
|
||||
case 'COMMENT_FLAG': return flag(state, action);
|
||||
case 'COMMENT_CREATE_SUCCESS': return addComment(state, action);
|
||||
case 'COMMENT_STREAM_FETCH_SUCCESS': return replaceComments(action, state);
|
||||
case actions.SHOW_BANUSER_DIALOG: return setBanUser(state, true, action);
|
||||
case actions.HIDE_BANUSER_DIALOG: return setBanUser(state, false, action);
|
||||
case actions.USER_BAN_SUCESS: return setBanUser(state, false, action);
|
||||
default: return state;
|
||||
}
|
||||
};
|
||||
|
||||
// hide or show the UI for the dialog confirming the ban
|
||||
// set the user that is going to set and the comment that is the reason
|
||||
const setBanUser = (state, showBanUser, action) => {
|
||||
const banUser = {'userName': action.userName, 'userId': action.userId, 'commentId': action.commentId};
|
||||
return state.set('showBanUserDialog', showBanUser)
|
||||
.set('banUser', banUser);
|
||||
};
|
||||
|
||||
// Update a comment status
|
||||
const updateStatus = (state, action) => {
|
||||
const byId = state.get('byId');
|
||||
|
||||
@@ -5,7 +5,8 @@ import {
|
||||
FETCH_COMMENTERS_FAILURE,
|
||||
FETCH_COMMENTERS_SUCCESS,
|
||||
SORT_UPDATE,
|
||||
SET_ROLE
|
||||
SET_ROLE,
|
||||
SET_COMMENTER_STATUS
|
||||
} from '../constants/community';
|
||||
|
||||
const initialState = Map({
|
||||
@@ -45,6 +46,14 @@ export default function community (state = initialState, action) {
|
||||
commenters[idx].roles[0] = action.role;
|
||||
return state.set('commenters', commenters.map(id => id));
|
||||
}
|
||||
case SET_COMMENTER_STATUS: {
|
||||
const commenters = state.get('commenters');
|
||||
const idx = commenters.findIndex(el => el.id === action.id);
|
||||
|
||||
commenters[idx].status = action.status;
|
||||
return state.set('commenters', commenters.map(id => id));
|
||||
|
||||
}
|
||||
case SORT_UPDATE :
|
||||
return state
|
||||
.set('field', action.sort.field)
|
||||
|
||||
@@ -8,6 +8,7 @@ const initialState = Map({
|
||||
export default (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
case 'USERS_MODERATION_QUEUE_FETCH_SUCCESS': return replaceUsers(action, state);
|
||||
case 'USER_STATUS_UPDATE': return updateUserStatus(state, action);
|
||||
default: return state;
|
||||
}
|
||||
};
|
||||
@@ -18,3 +19,10 @@ const replaceUsers = (action, state) => {
|
||||
return state.set('byId', users)
|
||||
.set('ids', List(users.keys()));
|
||||
};
|
||||
|
||||
// Update a user status
|
||||
const updateUserStatus = (state, action) => {
|
||||
const byId = state.get('byId');
|
||||
const data = byId.get(action.author_id).set('status', action.status.toLowerCase());
|
||||
return state.set('byId', byId.set(action.author_id, data));
|
||||
};
|
||||
|
||||
@@ -21,6 +21,9 @@ export default store => next => action => {
|
||||
case 'COMMENT_CREATE':
|
||||
createComment(store, action.name, action.body);
|
||||
break;
|
||||
case 'USER_BAN':
|
||||
userStatusUpdate(store, action.status, action.userId, action.commentId);
|
||||
break;
|
||||
}
|
||||
|
||||
next(action);
|
||||
@@ -81,3 +84,10 @@ const createComment = (store, name, comment) => {
|
||||
.then(res => store.dispatch({type: 'COMMENT_CREATE_SUCCESS', comment: res}))
|
||||
.catch(error => store.dispatch({type: 'COMMENT_CREATE_FAILED', error}));
|
||||
};
|
||||
|
||||
// Ban a user
|
||||
const userStatusUpdate = (store, status, userId, commentId) => {
|
||||
return coralApi(`/user/${userId}/status`, {method: 'POST', body: {status: status, comment_id: commentId}})
|
||||
.then(res => store.dispatch({type: 'USER_BAN_SUCESS', res}))
|
||||
.catch(error => store.dispatch({type: 'USER_BAN_FAILED', error}));
|
||||
};
|
||||
|
||||
@@ -6,7 +6,14 @@
|
||||
"newsroom_role": "Newsroom Role",
|
||||
"admin": "Administrator",
|
||||
"moderator": "Moderator",
|
||||
"role": "Select role..."
|
||||
"role": "Select role...",
|
||||
"no-results": "No users found with that user name or email address.",
|
||||
"status": "Status",
|
||||
"select-status": "Select status...",
|
||||
"active": "Active",
|
||||
"banned": "Banned",
|
||||
"banned-user": "Banned User",
|
||||
"loading": "Loading results"
|
||||
},
|
||||
"modqueue": {
|
||||
"pending": "pending",
|
||||
@@ -25,7 +32,9 @@
|
||||
},
|
||||
"comment": {
|
||||
"flagged": "flagged",
|
||||
"anon": "Anonymous"
|
||||
"anon": "Anonymous",
|
||||
"ban_user": "Ban User",
|
||||
"banned_user": "Banned User"
|
||||
},
|
||||
"embedlink": {
|
||||
"copy": "Copy to Clipboard"
|
||||
@@ -47,6 +56,13 @@
|
||||
"community": "Community",
|
||||
"closed-comments-desc": "Write a message for closed threads",
|
||||
"closed-comments-label": "Write a message..."
|
||||
},
|
||||
"bandialog": {
|
||||
"ban_user": "Ban User?",
|
||||
"are_you_sure": "Are you sure you would like to ban {0}?",
|
||||
"note": "Note: Banning this user will also place this comment in the Rejected queue.",
|
||||
"cancel": "Cancel",
|
||||
"yes_ban_user": "Yes, Ban User"
|
||||
}
|
||||
},
|
||||
"es": {
|
||||
@@ -56,7 +72,14 @@
|
||||
"newsroom_role": "Rol en la redacción",
|
||||
"admin": "Administrador",
|
||||
"moderator": "Moderador",
|
||||
"role": "Select role..."
|
||||
"role": "Seleccionar rol...",
|
||||
"no-results": "No se encontraron usuarios con ese nombre de usuario o correo electronico.",
|
||||
"status": "Estado",
|
||||
"select-status": "Seleccionar estado...",
|
||||
"active": "Activa",
|
||||
"banned": "Suspendido",
|
||||
"banned-user": "Usuario Suspendido",
|
||||
"loading": "Cargando resultados"
|
||||
},
|
||||
"modqueue": {
|
||||
"pending": "pendiente",
|
||||
@@ -67,7 +90,9 @@
|
||||
},
|
||||
"comment": {
|
||||
"flagged": "marcado",
|
||||
"anon": "Anónimo"
|
||||
"anon": "Anónimo",
|
||||
"ban_user": "Suspender Usuario",
|
||||
"banned_user": "Usuario Suspendido"
|
||||
},
|
||||
"configure": {
|
||||
"enable-pre-moderation": "Habilitar pre-moderación",
|
||||
@@ -86,6 +111,13 @@
|
||||
"community": "Comunidad",
|
||||
"closed-comments-desc": "Escribe un mensaje para cuando los comentarios se encuentran cerrados",
|
||||
"closed-comments-label": "Escribe un mensaje..."
|
||||
},
|
||||
"bandialog": {
|
||||
"ban_user": "Quieres suspender el Usuario?",
|
||||
"are_you_sure": "Estas segura que quieres suspender a {props.author.displayName}?",
|
||||
"note": "Nota: Suspender este usuario también va a colocar este comentario en la cola de Rechazados.",
|
||||
"cancel": "Cancelar",
|
||||
"yes_ban_user": "Si, Suspendan el usuario"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,6 +93,8 @@ class CommentStream extends Component {
|
||||
const {loggedIn, user, showSignInDialog, signInOffset} = this.props.auth;
|
||||
const {status, closedMessage} = this.props.config;
|
||||
const {activeTab} = this.state;
|
||||
const banned = (this.props.userData.status === 'banned');
|
||||
|
||||
const expandForLogin = showSignInDialog ? {
|
||||
minHeight: document.body.scrollHeight + 150
|
||||
} : {};
|
||||
@@ -106,8 +108,6 @@ class CommentStream extends Component {
|
||||
<Tab>Configure Stream</Tab>
|
||||
</TabBar>
|
||||
{loggedIn && <UserBox user={user} logout={this.props.logout} />}
|
||||
{/* Add to the restricted param a boolean if the user is suspended*/}
|
||||
<RestrictedContent restricted={false} restrictedComp={<SuspendedAccount />}>
|
||||
<TabContent show={activeTab === 0}>
|
||||
{
|
||||
status === 'open'
|
||||
@@ -116,6 +116,7 @@ class CommentStream extends Component {
|
||||
content={this.props.config.infoBoxContent}
|
||||
enable={this.props.config.infoBoxEnable}
|
||||
/>
|
||||
<RestrictedContent restricted={banned} restrictedComp={<SuspendedAccount />}>
|
||||
<CommentBox
|
||||
addNotification={this.props.addNotification}
|
||||
postItem={this.props.postItem}
|
||||
@@ -124,8 +125,11 @@ class CommentStream extends Component {
|
||||
id={rootItemId}
|
||||
premod={this.props.config.moderation}
|
||||
reply={false}
|
||||
currentUser={this.props.auth.user}
|
||||
banned={banned}
|
||||
author={user}
|
||||
/>
|
||||
</RestrictedContent>
|
||||
</div>
|
||||
: <p>{closedMessage}</p>
|
||||
}
|
||||
@@ -142,7 +146,9 @@ class CommentStream extends Component {
|
||||
<ReplyButton
|
||||
updateItem={this.props.updateItem}
|
||||
id={commentId}
|
||||
showReply={comment.showReply}/>
|
||||
currentUser={this.props.auth.user}
|
||||
showReply={comment.showReply}
|
||||
banned={banned}/>
|
||||
<LikeButton
|
||||
addNotification={this.props.addNotification}
|
||||
id={commentId}
|
||||
@@ -152,7 +158,8 @@ class CommentStream extends Component {
|
||||
deleteAction={this.props.deleteAction}
|
||||
addItem={this.props.addItem}
|
||||
updateItem={this.props.updateItem}
|
||||
currentUser={this.props.auth.user}/>
|
||||
currentUser={this.props.auth.user}
|
||||
banned={banned}/>
|
||||
</div>
|
||||
<div className="commentActionsRight">
|
||||
<FlagButton
|
||||
@@ -164,6 +171,7 @@ class CommentStream extends Component {
|
||||
addItem={this.props.addItem}
|
||||
showSignInDialog={this.props.showSignInDialog}
|
||||
updateItem={this.props.updateItem}
|
||||
banned={banned}
|
||||
currentUser={this.props.auth.user}/>
|
||||
<PermalinkButton
|
||||
commentId={commentId}
|
||||
@@ -177,6 +185,7 @@ class CommentStream extends Component {
|
||||
id={rootItemId}
|
||||
author={user}
|
||||
parent_id={commentId}
|
||||
currentUser={this.props.auth.user}
|
||||
premod={this.props.config.moderation}
|
||||
showReply={comment.showReply}/>
|
||||
{
|
||||
@@ -192,6 +201,8 @@ class CommentStream extends Component {
|
||||
<ReplyButton
|
||||
updateItem={this.props.updateItem}
|
||||
id={replyId}
|
||||
banned={banned}
|
||||
currentUser={this.props.auth.user}
|
||||
showReply={reply.showReply}/>
|
||||
<LikeButton
|
||||
addNotification={this.props.addNotification}
|
||||
@@ -202,7 +213,8 @@ class CommentStream extends Component {
|
||||
addItem={this.props.addItem}
|
||||
showSignInDialog={this.props.showSignInDialog}
|
||||
updateItem={this.props.updateItem}
|
||||
currentUser={this.props.auth.user}/>
|
||||
currentUser={this.props.auth.user}
|
||||
banned={banned}/>
|
||||
</div>
|
||||
<div className="replyActionsRight">
|
||||
<FlagButton
|
||||
@@ -214,6 +226,7 @@ class CommentStream extends Component {
|
||||
deleteAction={this.props.deleteAction}
|
||||
addItem={this.props.addItem}
|
||||
updateItem={this.props.updateItem}
|
||||
banned={banned}
|
||||
currentUser={this.props.auth.user}/>
|
||||
<PermalinkButton
|
||||
commentId={reply.parent_id}
|
||||
@@ -230,6 +243,8 @@ class CommentStream extends Component {
|
||||
parent_id={commentId}
|
||||
child_id={replyId}
|
||||
premod={this.props.config.moderation}
|
||||
banned={banned}
|
||||
currentUser={this.props.auth.user}
|
||||
showReply={reply.showReply}/>
|
||||
</div>;
|
||||
})
|
||||
@@ -258,7 +273,6 @@ class CommentStream extends Component {
|
||||
/>
|
||||
</RestrictedContent>
|
||||
</TabContent>
|
||||
</RestrictedContent>
|
||||
<Notification
|
||||
notifLength={4500}
|
||||
clearNotification={this.props.clearNotification}
|
||||
|
||||
@@ -2,7 +2,10 @@ import React from 'react';
|
||||
import I18n from 'coral-framework/modules/i18n/i18n';
|
||||
import translations from 'coral-framework/translations.json';
|
||||
const lang = new I18n(translations);
|
||||
import styles from './RestrictedContent.css';
|
||||
|
||||
export default () => (
|
||||
<span>{lang.t('suspendedAccountMsg')}</span>
|
||||
<div className={styles.message}>
|
||||
<span>{lang.t('suspendedAccountMsg')}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -4,7 +4,7 @@ import translations from './translations.json';
|
||||
|
||||
const name = 'coral-plugin-flags';
|
||||
|
||||
const FlagButton = ({flag, id, postAction, deleteAction, addItem, showSignInDialog, updateItem, addNotification, currentUser}) => {
|
||||
const FlagButton = ({flag, id, postAction, deleteAction, addItem, showSignInDialog, updateItem, addNotification, currentUser, banned}) => {
|
||||
const flagged = flag && flag.current_user;
|
||||
const onFlagClick = () => {
|
||||
if (!currentUser) {
|
||||
@@ -12,6 +12,9 @@ const FlagButton = ({flag, id, postAction, deleteAction, addItem, showSignInDial
|
||||
showSignInDialog(offset);
|
||||
return;
|
||||
}
|
||||
if (banned) {
|
||||
return;
|
||||
}
|
||||
if (!flagged) {
|
||||
postAction(id, 'flag', currentUser.id, 'comments')
|
||||
.then((action) => {
|
||||
|
||||
@@ -4,7 +4,7 @@ import translations from './translations.json';
|
||||
|
||||
const name = 'coral-plugin-flags';
|
||||
|
||||
const LikeButton = ({like, id, postAction, deleteAction, addItem, showSignInDialog, updateItem, currentUser}) => {
|
||||
const LikeButton = ({like, id, postAction, deleteAction, addItem, showSignInDialog, updateItem, currentUser, banned}) => {
|
||||
const liked = like && like.current_user;
|
||||
const onLikeClick = () => {
|
||||
if (!currentUser) {
|
||||
@@ -12,6 +12,9 @@ const LikeButton = ({like, id, postAction, deleteAction, addItem, showSignInDial
|
||||
showSignInDialog(offset);
|
||||
return;
|
||||
}
|
||||
if (banned) {
|
||||
return;
|
||||
}
|
||||
if (!liked) {
|
||||
postAction(id, 'like', currentUser.id, 'comments')
|
||||
.then((action) => {
|
||||
|
||||
@@ -6,7 +6,12 @@ const name = 'coral-plugin-replies';
|
||||
|
||||
const ReplyButton = (props) => <button
|
||||
className={`${name}-reply-button`}
|
||||
onClick={() => props.updateItem(props.id, 'showReply', !props.showReply, 'comments')}>
|
||||
onClick={() => {
|
||||
if (props.banned) {
|
||||
return;
|
||||
}
|
||||
props.updateItem(props.id, 'showReply', !props.showReply, 'comments');
|
||||
}}>
|
||||
{lang.t('reply')}
|
||||
<i className={`${name}-icon material-icons`}
|
||||
aria-hidden={true}>reply</i>
|
||||
|
||||
@@ -4,6 +4,8 @@ const _ = require('lodash');
|
||||
const bcrypt = require('bcrypt');
|
||||
const jwt = require('jsonwebtoken');
|
||||
|
||||
const Comment = require('./comment');
|
||||
|
||||
// SALT_ROUNDS is the number of rounds that the bcrypt algorithm will run
|
||||
// through during the salting process.
|
||||
const SALT_ROUNDS = 10;
|
||||
@@ -14,6 +16,12 @@ const USER_ROLES = [
|
||||
'moderator'
|
||||
];
|
||||
|
||||
// USER_STATUSES is the list of statuses that are permitted for the user status.
|
||||
const USER_STATUS = [
|
||||
'active',
|
||||
'banned'
|
||||
];
|
||||
|
||||
// In the event that the TALK_SESSION_SECRET is missing but we are testing, then
|
||||
// set the process.env.TALK_SESSION_SECRET.
|
||||
if (process.env.NODE_ENV === 'test' && !process.env.TALK_SESSION_SECRET) {
|
||||
@@ -76,6 +84,9 @@ const UserSchema = new mongoose.Schema({
|
||||
// user.
|
||||
roles: [String],
|
||||
|
||||
// Status provides a string that says in which state the account is.
|
||||
// When the account is banned, the user login is disabled.
|
||||
status: {type: String, enum: USER_STATUS, default: 'active'},
|
||||
// User's settings
|
||||
settings: {
|
||||
bio: {
|
||||
@@ -399,6 +410,47 @@ UserService.removeRoleFromUser = (id, role) => {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Set status of a user.
|
||||
* @param {String} id id of a user
|
||||
* @param {String} status status to set
|
||||
* @param {String} comment_id id of the comment that the user was ban for.
|
||||
* @param {Function} done callback after the operation is complete
|
||||
*/
|
||||
UserService.setStatus = (id, status, comment_id) => {
|
||||
|
||||
// Check to see if the user status is in the allowable set of roles.
|
||||
if (USER_STATUS.indexOf(status) === -1) {
|
||||
|
||||
// User status is not supported! Error out here.
|
||||
return Promise.reject(new Error(`status ${status} is not supported`));
|
||||
}
|
||||
|
||||
// If ban then reject the comment and update status
|
||||
if (status === 'banned') {
|
||||
return UserModel.update({
|
||||
id: id
|
||||
}, {
|
||||
$set: {
|
||||
status: status
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
return Comment.pushStatus(comment_id, 'rejected', id);
|
||||
});
|
||||
}
|
||||
|
||||
if (status === 'active') {
|
||||
return UserModel.update({
|
||||
id: id
|
||||
}, {
|
||||
$set: {
|
||||
status: status
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds a user with the id.
|
||||
* @param {String} id user id (uuid)
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 47 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 47 KiB |
@@ -0,0 +1 @@
|
||||
<mxfile userAgent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.98 Safari/537.36" version="6.0.1.3" editor="www.draw.io" type="device"><diagram name="Pre-Moderation">7VlNc9sgEP01nmkP9ejD+vAxdpP20M6kk0PbI5GIRIqEilFs99d3EWAJSc4krp24nebgEQ9Y4O1bdqVM/GWx+cBRlX9mKaYTz0k3E//9xPOiuQO/EtgqIPAiBWScpApyW+CG/MIa1POymqR4ZQ0UjFFBKhtMWFniRFgY4pyt7WF3jNqrVijDA+AmQXSIfiWpyBUaB06Lf8Qky83KrqN7blHyI+OsLvV6E8+/a/5Ud4GMLT1+laOUrTuQfznxl5wxoZ6KzRJTSa2hTc272tO72zfHpXjShEDNeEC0xmbLzcbE1pCxzonANxVKZHsN/p74i1wUFFouPGoDmAu82bsLd3c2kAxmBRZ8C0PMBKMXLRfXmFi35LuG/LxDvB/MtdO1w7Od7fbQ8KDPPc6BN6AgYUUht+6FFBZapOQBHjP5+KbE67cGB8OdrgFvsAroFRqL0zPo2QR6Ro9dAr0RAnfgnxAYP4dAVFWcPeD0HFmcOTaNvjt7QRrnz6GR43u4+P4OGt3wJdXouiNXmuLojkkyO7SEP2tmOt6tmjR0AQPcqNq0nS2nysptH6gNcM3xuwJyIUeCsLLjmbo/BbCBGcDUBve4EBwibF+tBGc/8JJRxgEpWSn9e0co7UGIkqyEZgIOxIAvpHsJpLsL3VGQNKX7xHEERfi2IOIRPcxH9HAUOQwTGk4h2esm4yJnGSsRvWzRRZPBsbTg2IzjDRHfJDyNAt38rkcBFXzb7ZNt07kSiIsLWZlIN1C0WpHEwFeE7syXqRmkXQeI7pdm7rEQW10uoVowgNoDfGKsGhUGFCFO87frMUVNG/CSk8edCxSymidmlCnLEM+w6GJDEXBMISAebPt/5NNwEOKfO2HnfKkx4P9Q/HhOPA2sEJqHwxByghOFUDRyo/ZjqlVuK++9VD+uSBNkbjfCpoEVY1aAqYA7TMjxiI7jcWd0uB4rRg32ZLnrFa4ZadLSeCkXxD0Pqs3rSd3Svmcn6mXhmd8zpI48MNSoYXfqpwlkWAEOBHL6siOcvepbhDPgYGnKN+dGcIyKf+lGCkOb7PlISndOlNK9YYV3RVGWkTJrvgk0rK/gMWXwUzLpgVuuekWOC/2eLltMQfLVvJtAfkICmQ7cBS/rFW58IF6mkA56IRyOCHq0jnaOIWj//G79qeNE9s3vx6EBrjEncEoZDk1+OCwfeCP5IDijfBAeng9sO6dLB97sHJQzi2Ld7gtjXxVxeBFhPqN1VTP/L5pnieYJXyJft8g8rjzOqcY8WB5+78PtLDqWPqDZfgVXw9v/NPiXvwE=</diagram><diagram name="Post-Moderation">3VlNc6M4EP01rpo9xIXEh/Ex9iYzh52qbOWwu0cNKJgZQB4hYnt+/TRGAiRBQhLbmZ0cUqgltcTr160nPHPX+f4jJ9vNZxbTbIadeD9z/5xhvFg68L82HBqDjxeNIeFp3JhQZ7hPf1BplPOSKo1pqQ0UjGUi3erGiBUFjYRmI5yznT7sgWX6qluSUMtwH5HMtv6TxmLTWEPf6eyfaJps1MrIkT1fSPQt4awq5Hoz7D4c/5runChfcny5ITHb9Uzuzcxdc8ZE85Tv1zSroVWwNfNuR3rbfXNaiCkTcDPhkWQVVTs+7kscFBa7TSro/ZZEdXsH4Z65q43IM2gheJQOKBd0P7oJ1L4aMIaynAp+gCFyAlboSbbgULZ3HfYokLZND3dXBYTIeCet7+6d4UG+9jAErgVBxPK83joOMlhoFaeP8JjUjx8KuvtD2cFxr8vCDVYBukJjdX4EXR3AFqw+gHgAwNb4FgC9lwBItlvOHmn8K6LoYV+D0cOXhDEYSMUGogdWY9lDJfheMdVxVR6r5zUMQIvtvuvsIG28fDENlTLcsVJc5VDDOREpK3qRqcw5YLP8gK3Z4UgIISBCj1UpOPtG1yxjHCwFK+r4PqRZZphIliYFNCMIIAX7qg5vCmX6WnbkaRxnY+Q4ASMCPa+G6tJygA+noMPCToUYzijZZFxsWMIKkt101tXx4KG1A0cHnO5T8W9tni982fxPjgIk+KHfV7dVZykIF9f1gVpHISNlmUbKfJtmrfsiVoNk5MAi+2s3X6kQB3nKk0owMHUv8Bdj20FewNnpHP/aHnUWd/leY6LFtmQVj6iWUbDZhArN9JKzyR2kwFtCG1qZ/rmXfM7fFQX7b5RFAOJcr6zYD+xMcvzzZNLyeY3TI3DH8lGonyamyjU0dzzcT7a5H7qyfUd5Cu9RR0NPwitn7mA9DedhM602mPMmJIFnJwFyXhpBd6kXQk+B2oufP1AIn9JncrE7lh5Pt70ecHVxMPOteTU5qS9sDT+BoSl9z3DUAGI5OpKpBWASvxSc7yuiA9/QgMrFc+LFQyfIMYQsDNZKBDr3glOS/04VLTCEwXJAGDhnEgbIvrBcpp7ptUyTDnrBOhawCaXJPUVpQp4eChxeqDQFry1NC8OPe77K5NnMOIWI7GtI9L+UiRa/dGZim5l4mIWcZqDbHnXvb0pv+x54m5EkgYhgp62p73pLNhIuGNByZ7slo8X7VD/Hqn6vY5YqcFrRC4YBn1jBJlNwuBwZ346C5SvLWmj4OaPgshX9E1+cOP1KI/GLfnFy9XuRf9EPd3iCcL1MLrVKAulKAqHwhVef59Jv4JvASGW/TPaZFytTDEzNPlMGuaY6OV36YVvrX4Q2cKGGzO9TJ3SUEhm9UBvS9PWFe0ASNHVoXJkaV1B34JZwGmVqruSZtXcyi/yRLZ+BRfidWDT5GnNSrnhPUwUZAXTORhVzJdPFZKoEI1t+M1Wg2f0A2gzvfmR2b34C</diagram></mxfile>
|
||||
@@ -144,7 +144,6 @@ router.delete('/:comment_id', authorization.needed('admin'), (req, res, next) =>
|
||||
});
|
||||
|
||||
router.put('/:comment_id/status', authorization.needed('admin'), (req, res, next) => {
|
||||
|
||||
const {
|
||||
status
|
||||
} = req.body;
|
||||
|
||||
@@ -27,7 +27,6 @@ router.get('/', authorization.needed('admin'), (req, res, next) => {
|
||||
User.count()
|
||||
])
|
||||
.then(([result, count]) => {
|
||||
|
||||
res.json({
|
||||
result,
|
||||
limit: Number(limit),
|
||||
@@ -48,6 +47,15 @@ router.post('/:user_id/role', authorization.needed('admin'), (req, res, next) =>
|
||||
.catch(next);
|
||||
});
|
||||
|
||||
router.post('/:user_id/status', (req, res, next) => {
|
||||
User
|
||||
.setStatus(req.params.user_id, req.body.status, req.body.comment_id)
|
||||
.then(status => {
|
||||
res.json(status);
|
||||
})
|
||||
.catch(next);
|
||||
});
|
||||
|
||||
router.post('/', (req, res, next) => {
|
||||
const {email, password, displayName} = req.body;
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
const User = require('../../models/user');
|
||||
const Comment = require('../../models/comment');
|
||||
|
||||
const expect = require('chai').expect;
|
||||
|
||||
describe('models.User', () => {
|
||||
@@ -77,4 +79,90 @@ describe('models.User', () => {
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#setStatus', () => {
|
||||
it('should set the status to active', () => {
|
||||
return User
|
||||
.setStatus(mockUsers[0].id, 'active')
|
||||
.then(() => {
|
||||
User.findById(mockUsers[0].id)
|
||||
.then((user) => {
|
||||
expect(user).to.have.property('status')
|
||||
.and.to.equal('active');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#ban', () => {
|
||||
let mockComment;
|
||||
beforeEach(() => {
|
||||
return Promise.all([
|
||||
Comment.create([{body: 'testing the comment for that user if it is rejected.', id: mockUsers[0].id}])
|
||||
])
|
||||
.then((comment) => {
|
||||
mockComment = comment;
|
||||
});
|
||||
});
|
||||
|
||||
it('should set the status to banned', () => {
|
||||
return User
|
||||
.setStatus(mockUsers[0].id, 'banned', mockComment.id)
|
||||
.then(() => {
|
||||
User.findById(mockUsers[0].id)
|
||||
.then((user) => {
|
||||
expect(user).to.have.property('status')
|
||||
.and.to.equal('banned');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should set the comment to rejected', () => {
|
||||
return User
|
||||
.setStatus(mockUsers[0].id, 'banned', mockComment.id)
|
||||
.then(() => {
|
||||
Comment.findById(mockComment.id)
|
||||
.then((comment) => {
|
||||
expect(comment).to.have.property('status')
|
||||
.and.to.equal('rejected');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should still disable and ban the user if there is no comment', () => {
|
||||
return User
|
||||
.setStatus(mockUsers[0].id, 'banned', '')
|
||||
.then(() => {
|
||||
User.findById(mockUsers[0].id)
|
||||
.then((user) => {
|
||||
expect(user).to.have.property('status')
|
||||
.and.to.equal('banned');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#unban', () => {
|
||||
let mockComment;
|
||||
beforeEach(() => {
|
||||
return Promise.all([
|
||||
Comment.create([{body: 'testing the comment for that user if it is rejected.', id: mockUsers[0].id}])
|
||||
])
|
||||
.then((comment) => {
|
||||
mockComment = comment;
|
||||
});
|
||||
});
|
||||
|
||||
it('should set the status to active', () => {
|
||||
return User
|
||||
.setStatus(mockUsers[0].id, 'active', mockComment.id)
|
||||
.then(() => {
|
||||
User.findById(mockUsers[0].id)
|
||||
.then((user) => {
|
||||
expect(user).to.have.property('status')
|
||||
.and.to.equal('active');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user