mirror of
https://github.com/wassname/talk.git
synced 2026-07-11 20:25:48 +08:00
Merge pull request #283 from coralproject/username-ban
Username banning flow
This commit is contained in:
@@ -15,7 +15,7 @@ export const fetchModerationQueueComments = () => {
|
||||
|
||||
return Promise.all([
|
||||
coralApi('/queue/comments/premod'),
|
||||
coralApi('/queue/users/pending'),
|
||||
coralApi('/queue/users/flagged'),
|
||||
coralApi('/queue/comments/rejected'),
|
||||
coralApi('/queue/comments/flagged')
|
||||
])
|
||||
@@ -46,7 +46,7 @@ export const fetchPendingUsersQueue = () => {
|
||||
return dispatch => {
|
||||
dispatch({type: commentTypes.COMMENTS_MODERATION_QUEUE_FETCH_REQUEST});
|
||||
|
||||
return coralApi('/queue/users/pending')
|
||||
return coralApi('/queue/users/flagged')
|
||||
.then(addUsersCommentsActions.bind(this, dispatch));
|
||||
};
|
||||
};
|
||||
|
||||
@@ -21,3 +21,11 @@ export const sendNotificationEmail = (userId, subject, body) => {
|
||||
.catch(error => dispatch({type: userTypes.USER_EMAIL_FAILURE, error}));
|
||||
};
|
||||
};
|
||||
|
||||
// let a user edit their username
|
||||
export const enableUsernameEdit = (userId) => {
|
||||
return (dispatch) => {
|
||||
return coralApi(`/users/${userId}/username-enable`, {method: 'POST'})
|
||||
.catch(error => dispatch({type: userTypes.USERNAME_ENABLE_FAILURE, error}));
|
||||
};
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@ import styles from './ModerationList.css';
|
||||
import key from 'keymaster';
|
||||
import Hammer from 'hammerjs';
|
||||
import Comment from './Comment';
|
||||
import UserAction from './UserAction';
|
||||
import User from './User';
|
||||
import SuspendUserModal from './SuspendUserModal';
|
||||
|
||||
// Each action has different meaning and configuration
|
||||
@@ -138,7 +138,7 @@ export default class ModerationList extends React.Component {
|
||||
if (menuOption === 'REJECTED') {
|
||||
this.setState({suspendUserModal: action});
|
||||
} else if (menuOption === 'ACCEPTED') {
|
||||
this.props.userStatusUpdate('ACTIVE', action.item_id);
|
||||
this.props.userStatusUpdate('APPROVED', action.item_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -177,7 +177,7 @@ export default class ModerationList extends React.Component {
|
||||
|
||||
// If the item is an action...
|
||||
const user = users[item.item_id];
|
||||
modItem = user && <UserAction
|
||||
modItem = user && <User
|
||||
suspectWords={suspectWords}
|
||||
action={item}
|
||||
user={user}
|
||||
|
||||
@@ -36,7 +36,7 @@ class SuspendUserModal extends Component {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const about = this.props.actionType === 'flag_bio' ? lang.t('suspenduser.bio') : lang.t('suspenduser.username');
|
||||
const about = lang.t('suspenduser.username');
|
||||
this.setState({email: lang.t('suspenduser.email', about)});
|
||||
}
|
||||
|
||||
|
||||
+2
-30
@@ -1,22 +1,16 @@
|
||||
import React from 'react';
|
||||
import Linkify from 'react-linkify';
|
||||
|
||||
import styles from './ModerationList.css';
|
||||
|
||||
import I18n from 'coral-framework/modules/i18n/i18n';
|
||||
import translations from '../translations.json';
|
||||
|
||||
import {Icon} from 'react-mdl';
|
||||
import Highlighter from 'react-highlight-words';
|
||||
import ActionButton from './ActionButton';
|
||||
|
||||
const linkify = new Linkify();
|
||||
|
||||
// Render a single comment for the list
|
||||
const UserAction = props => {
|
||||
const User = props => {
|
||||
const {action, user} = props;
|
||||
let userStatus = user.status;
|
||||
const links = user.settings.bio ? linkify.getMatches(user.settings.bio) : [];
|
||||
|
||||
// Do not display unless the user status is 'pending' or 'banned'.
|
||||
// This means that they have already been reviewed and approved.
|
||||
@@ -27,8 +21,6 @@ const UserAction = props => {
|
||||
<span>{user.displayName}</span>
|
||||
</div>
|
||||
<div className={styles.sideActions}>
|
||||
{links ?
|
||||
<span className={styles.hasLinks}><Icon name='error_outline'/> Contains Link</span> : null}
|
||||
<div className={`actions ${styles.actions}`}>
|
||||
{props.modActions.map(
|
||||
(action, i) =>
|
||||
@@ -48,32 +40,12 @@ const UserAction = props => {
|
||||
<span className={styles.banned}><Icon name='error_outline'/> {lang.t('comment.banned_user')}</span> : null}
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
user.settings.bio &&
|
||||
<div>
|
||||
<div className={styles.itemBody}>
|
||||
<div>{lang.t('user.user_bio')}:</div>
|
||||
<span className={styles.body}>
|
||||
<Linkify component='span' properties={{style: linkStyles}}>
|
||||
<Highlighter
|
||||
searchWords={props.suspectWords}
|
||||
textToHighlight={user.settings.bio} />
|
||||
</Linkify>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div className={styles.flagCount}>
|
||||
{`${action.count} ${action.action_type === 'flag_bio' ? lang.t('user.bio_flags') : lang.t('user.username_flags')}`}
|
||||
</div>
|
||||
</li>;
|
||||
};
|
||||
|
||||
export default UserAction;
|
||||
|
||||
const linkStyles = {
|
||||
backgroundColor: 'rgb(255, 219, 135)',
|
||||
padding: '1px 2px'
|
||||
};
|
||||
export default User;
|
||||
|
||||
const lang = new I18n(translations);
|
||||
@@ -2,4 +2,5 @@ export const UPDATE_STATUS_REQUEST = 'UPDATE_STATUS_REQUEST';
|
||||
export const UPDATE_STATUS_SUCCESS = 'UPDATE_STATUS_SUCCESS';
|
||||
export const UPDATE_STATUS_FAILURE = 'UPDATE_STATUS_FAILURE';
|
||||
export const USER_EMAIL_FAILURE = 'USER_EMAIL_FAILURE';
|
||||
export const USERNAME_ENABLE_FAILURE = 'USERNAME_ENABLE_FAILURE';
|
||||
export const USERS_MODERATION_QUEUE_FETCH_SUCCESS = 'USERS_MODERATION_QUEUE_FETCH_SUCCESS';
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
fetchFlaggedQueue,
|
||||
fetchModerationQueueComments,
|
||||
} from 'actions/comments';
|
||||
import {userStatusUpdate, sendNotificationEmail} from 'actions/users';
|
||||
import {userStatusUpdate, sendNotificationEmail, enableUsernameEdit} from 'actions/users';
|
||||
import {fetchSettings} from 'actions/settings';
|
||||
|
||||
import ModerationQueue from './ModerationQueue';
|
||||
@@ -122,7 +122,8 @@ const mapDispatchToProps = dispatch => {
|
||||
userStatusUpdate: (status, userId, commentId) => dispatch(userStatusUpdate(status, userId, commentId)).then(() => {
|
||||
dispatch(fetchModerationQueueComments());
|
||||
}),
|
||||
suspendUser: (userId, subject, text) => dispatch(userStatusUpdate('suspended', userId))
|
||||
suspendUser: (userId, subject, text) => dispatch(userStatusUpdate('BANNED', userId))
|
||||
.then(() => dispatch(enableUsernameEdit(userId)))
|
||||
.then(() => dispatch(sendNotificationEmail(userId, subject, text)))
|
||||
.then(() => dispatch(fetchModerationQueueComments()))
|
||||
,
|
||||
|
||||
@@ -94,14 +94,7 @@ class Comment extends React.Component {
|
||||
style={{marginLeft: depth * 30}}>
|
||||
<hr aria-hidden={true} />
|
||||
<AuthorName
|
||||
author={comment.user}
|
||||
addNotification={this.props.addNotification}
|
||||
id={comment.id}
|
||||
author_id={comment.user.id}
|
||||
postAction={this.props.postAction}
|
||||
showSignInDialog={this.props.showSignInDialog}
|
||||
deleteAction={this.props.deleteAction}
|
||||
currentUser={currentUser}/>
|
||||
author={comment.user}/>
|
||||
<PubDate created_at={comment.created_at} />
|
||||
<Content body={comment.body} />
|
||||
<div className="commentActionsLeft">
|
||||
|
||||
@@ -11,6 +11,7 @@ const {fetchAssetSuccess} = assetActions;
|
||||
|
||||
import {queryStream} from 'coral-framework/graphql/queries';
|
||||
import {postComment, postAction, deleteAction} from 'coral-framework/graphql/mutations';
|
||||
import {editName} from 'coral-framework/actions/user';
|
||||
import {Notification, notificationActions, authActions, assetActions, pym} from 'coral-framework';
|
||||
|
||||
import Stream from './Stream';
|
||||
@@ -84,6 +85,8 @@ class Embed extends Component {
|
||||
|
||||
const openStream = closedAt === null;
|
||||
|
||||
const banned = user && user.status === 'BANNED';
|
||||
|
||||
const expandForLogin = showSignInDialog ? {
|
||||
minHeight: document.body.scrollHeight + 200
|
||||
} : {};
|
||||
@@ -100,7 +103,7 @@ class Embed extends Component {
|
||||
<Tab>Settings</Tab>
|
||||
<Tab restricted={!isAdmin}>Configure Stream</Tab>
|
||||
</TabBar>
|
||||
{loggedIn && <UserBox user={user} logout={this.props.logout} changeTab={this.changeTab} />}
|
||||
{loggedIn && <UserBox user={user} logout={this.props.logout} changeTab={this.changeTab}/>}
|
||||
<TabContent show={activeTab === 0}>
|
||||
{
|
||||
openStream
|
||||
@@ -109,7 +112,12 @@ class Embed extends Component {
|
||||
content={asset.settings.infoBoxContent}
|
||||
enable={asset.settings.infoBoxEnable}
|
||||
/>
|
||||
<RestrictedContent restricted={false} restrictedComp={<SuspendedAccount />}>
|
||||
<RestrictedContent restricted={banned} restrictedComp={
|
||||
<SuspendedAccount
|
||||
canEditName={user && user.canEditName}
|
||||
editName={this.props.editName}
|
||||
/>
|
||||
}>
|
||||
{
|
||||
user
|
||||
? <CommentBox
|
||||
@@ -122,7 +130,6 @@ class Embed extends Component {
|
||||
premod={asset.settings.moderation}
|
||||
isReply={false}
|
||||
currentUser={this.props.auth.user}
|
||||
banned={false}
|
||||
authorId={user.id}
|
||||
charCount={asset.settings.charCountEnable && asset.settings.charCount} />
|
||||
: null
|
||||
@@ -148,28 +155,28 @@ class Embed extends Component {
|
||||
clearNotification={this.props.clearNotification}
|
||||
notification={{text: null}}
|
||||
/>
|
||||
</TabContent>
|
||||
<TabContent show={activeTab === 1}>
|
||||
</TabContent>
|
||||
<TabContent show={activeTab === 1}>
|
||||
<SettingsContainer
|
||||
loggedIn={loggedIn}
|
||||
userData={this.props.userData}
|
||||
showSignInDialog={this.props.showSignInDialog}
|
||||
/>
|
||||
</TabContent>
|
||||
<TabContent show={activeTab === 2}>
|
||||
</TabContent>
|
||||
<TabContent show={activeTab === 2}>
|
||||
<RestrictedContent restricted={!loggedIn}>
|
||||
<ConfigureStreamContainer
|
||||
status={status}
|
||||
onClick={this.toggleStatus}
|
||||
/>
|
||||
</RestrictedContent>
|
||||
</TabContent>
|
||||
</TabContent>
|
||||
<Notification
|
||||
notifLength={4500}
|
||||
clearNotification={this.props.clearNotification}
|
||||
notification={this.props.notification}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -193,6 +200,7 @@ const mapDispatchToProps = dispatch => ({
|
||||
});
|
||||
},
|
||||
clearNotification: () => dispatch(clearNotification()),
|
||||
editName: (displayName) => dispatch(editName(displayName)),
|
||||
showSignInDialog: (offset) => dispatch(showSignInDialog(offset)),
|
||||
logout: () => dispatch(logout()),
|
||||
dispatch: d => dispatch(d)
|
||||
|
||||
@@ -15,15 +15,15 @@ export const hideCreateDisplayNameDialog = () => ({type: actions.HIDE_CREATEDISP
|
||||
const createDisplayNameSuccess = () => ({type: actions.CREATEDISPLAYNAME_SUCCESS});
|
||||
const createDisplayNameFailure = error => ({type: actions.CREATEDISPLAYNAME_FAILURE, error});
|
||||
|
||||
export const updateDisplayName = displayName => ({type: actions.UPDATE_DISPLAYNAME, displayName});
|
||||
export const updateDisplayName = ({displayName}) => ({type: actions.UPDATE_DISPLAYNAME, displayName});
|
||||
|
||||
export const createDisplayName = (userId, formData) => dispatch => {
|
||||
dispatch(createDisplayNameRequest());
|
||||
coralApi(`/users/${userId}/displayname`, {method: 'POST', body: formData})
|
||||
.then((user) => {
|
||||
coralApi('/account/displayname', {method: 'PUT', body: formData})
|
||||
.then(() => {
|
||||
dispatch(createDisplayNameSuccess());
|
||||
dispatch(hideCreateDisplayNameDialog());
|
||||
dispatch(updateDisplayName(user));
|
||||
dispatch(updateDisplayName(formData));
|
||||
})
|
||||
.catch(error => {
|
||||
dispatch(createDisplayNameFailure(lang.t(`error.${error.message}`)));
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import * as actions from '../constants/user';
|
||||
import {addNotification} from '../actions/notification';
|
||||
import coralApi from '../helpers/response';
|
||||
|
||||
@@ -6,16 +5,9 @@ import I18n from 'coral-framework/modules/i18n/i18n';
|
||||
import translations from './../translations';
|
||||
const lang = new I18n(translations);
|
||||
|
||||
const saveBioRequest = () => ({type: actions.SAVE_BIO_REQUEST});
|
||||
const saveBioSuccess = settings => ({type: actions.SAVE_BIO_SUCCESS, settings});
|
||||
const saveBioFailure = error => ({type: actions.SAVE_BIO_FAILURE, error});
|
||||
|
||||
export const saveBio = (user_id, formData) => dispatch => {
|
||||
dispatch(saveBioRequest());
|
||||
coralApi('/account/settings', {method: 'PUT', body: formData})
|
||||
export const editName = (displayName) => (dispatch) => {
|
||||
return coralApi('/account/displayname', {method: 'PUT', body: {displayName}})
|
||||
.then(() => {
|
||||
dispatch(addNotification('success', lang.t('successBioUpdate')));
|
||||
dispatch(saveBioSuccess(formData));
|
||||
})
|
||||
.catch(error => dispatch(saveBioFailure(error)));
|
||||
dispatch(addNotification('success', lang.t('successNameUpdate')));
|
||||
});
|
||||
};
|
||||
|
||||
@@ -2,3 +2,13 @@
|
||||
background: #D8D8D8;
|
||||
padding: 25px;
|
||||
}
|
||||
|
||||
.editNameInput {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.alert {
|
||||
margin-top: 10px;
|
||||
color: #B71C1C;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,79 @@
|
||||
import React from 'react';
|
||||
import React, {Component, PropTypes} 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';
|
||||
import {Button} from 'coral-ui';
|
||||
import validate from '../helpers/validate';
|
||||
|
||||
export default () => (
|
||||
<div className={styles.message}>
|
||||
<span>{lang.t('suspendedAccountMsg')}</span>
|
||||
</div>
|
||||
);
|
||||
class SuspendedAccount extends Component {
|
||||
|
||||
static propTypes = {
|
||||
canEditName: PropTypes.bool,
|
||||
editName: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
state = {
|
||||
displayName: '',
|
||||
alert: ''
|
||||
}
|
||||
|
||||
onSubmitClick = (e) => {
|
||||
const {editName} = this.props;
|
||||
const {displayName} = this.state;
|
||||
e.preventDefault();
|
||||
if (validate.displayName(displayName)) {
|
||||
editName(displayName)
|
||||
.then(() => location.reload())
|
||||
.catch((error) => {
|
||||
this.setState({alert: lang.t(`error.${error.message}`)});
|
||||
});
|
||||
} else {
|
||||
this.setState({alert: lang.t('editName.error')});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
render () {
|
||||
const {canEditName} = this.props;
|
||||
const {displayName, alert} = this.state;
|
||||
|
||||
return <div className={styles.message}>
|
||||
<span>{
|
||||
canEditName ?
|
||||
lang.t('editName.msg')
|
||||
: lang.t('bannedAccountMsg')
|
||||
}</span>
|
||||
{
|
||||
canEditName ?
|
||||
<div>
|
||||
<div className={styles.alert}>
|
||||
{alert}
|
||||
</div>
|
||||
<label
|
||||
htmlFor='displayName'
|
||||
className="screen-reader-text"
|
||||
aria-hidden={true}>
|
||||
{lang.t('editName.label')}
|
||||
</label>
|
||||
<input
|
||||
type='text'
|
||||
className={styles.editNameInput}
|
||||
value={displayName}
|
||||
placeholder={lang.t('editName.label')}
|
||||
id='displayName'
|
||||
onChange={(e) => this.setState({displayName: e.target.value})}
|
||||
rows={3}/><br/>
|
||||
<Button
|
||||
onClick={this.onSubmitClick}>
|
||||
{
|
||||
lang.t('editName.button')
|
||||
}
|
||||
</Button>
|
||||
</div> : null
|
||||
}
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export default SuspendedAccount;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export const SAVE_BIO_REQUEST = 'SAVE_BIO_REQUEST';
|
||||
export const SAVE_BIO_SUCCESS = 'SAVE_BIO_SUCCESS';
|
||||
export const SAVE_BIO_FAILURE = 'SAVE_BIO_FAILURE';
|
||||
export const EDIT_NAME_REQUEST = 'EDIT_NAME_REQUEST';
|
||||
export const EDIT_NAME_SUCCESS = 'EDIT_NAME_SUCCESS';
|
||||
export const EDIT_NAME_FAILURE = 'EDIT_NAME_FAILURE';
|
||||
export const COMMENTS_BY_USER_REQUEST = 'COMMENTS_BY_USER_REQUEST';
|
||||
export const COMMENTS_BY_USER_SUCCESS = 'COMMENTS_BY_USER_SUCCESS';
|
||||
export const COMMENTS_BY_USER_FAILURE = 'COMMENTS_BY_USER_FAILURE';
|
||||
|
||||
@@ -6,9 +6,6 @@ fragment commentView on Comment {
|
||||
user {
|
||||
id
|
||||
name: displayName
|
||||
settings {
|
||||
bio
|
||||
}
|
||||
}
|
||||
actions {
|
||||
type: action_type
|
||||
|
||||
@@ -11,6 +11,9 @@ function getQueryVariable(variable) {
|
||||
return decodeURIComponent(pair[1]);
|
||||
}
|
||||
}
|
||||
|
||||
// If no query is included, return a default string for development
|
||||
return 'http://dev.default.stream';
|
||||
}
|
||||
|
||||
export const queryStream = graphql(STREAM_QUERY, {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {Map} from 'immutable';
|
||||
import {Map, fromJS} from 'immutable';
|
||||
import * as actions from '../constants/auth';
|
||||
|
||||
const initialState = Map({
|
||||
@@ -21,7 +21,7 @@ const initialState = Map({
|
||||
|
||||
const purge = user => {
|
||||
const {settings, profiles, ...userData} = user; // eslint-disable-line
|
||||
return userData;
|
||||
return fromJS(userData);
|
||||
};
|
||||
|
||||
export default function auth (state = initialState, action) {
|
||||
@@ -131,8 +131,9 @@ export default function auth (state = initialState, action) {
|
||||
.set('passwordRequestFailure', 'There was an error sending your password reset email. Please try again soon!')
|
||||
.set('passwordRequestSuccess', null);
|
||||
case actions.UPDATE_DISPLAYNAME:
|
||||
console.log('Action', action);
|
||||
return state
|
||||
.set('user', purge(action.displayName));
|
||||
.setIn(['user', 'displayName'], action.displayName);
|
||||
case actions.VERIFY_EMAIL_FAILURE:
|
||||
return state
|
||||
.set('emailVerificationFailure', true)
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
{
|
||||
"en": {
|
||||
"successUpdateSettings": "The changes you have made have been applied to the comment stream on this article",
|
||||
"successBioUpdate": "Your Bio has been updated",
|
||||
"successNameUpdate": "Your display name has been updated",
|
||||
"contentNotAvailable": "This content is not available",
|
||||
"suspendedAccountMsg": "Your account is currently suspended. This means that you cannot Like, Flag, or write comments. Please contact moderator@fakeurl.com for more information",
|
||||
"bannedAccountMsg": "Your account is currently suspended. This means that you cannot Like, Flag, or write comments. Please contact moderator@fakeurl.com for more information",
|
||||
"editName": {
|
||||
"msg": "Your account is currently suspended because your display name has been deemed inappropriate. To restore your account, please enter a new username. You may contact moderator@fakeurl.com for more information.",
|
||||
"label": "New Display Name",
|
||||
"button": "Submit",
|
||||
"error": "Display names can contain letters, numbers and _ only"
|
||||
},
|
||||
"error": {
|
||||
"emailNotVerified": "Email address {0} not verified.",
|
||||
"email": "Not a valid E-Mail",
|
||||
@@ -26,7 +32,8 @@
|
||||
"successUpdateSettings": "La configuración de este articulo fue actualizada",
|
||||
"successBioUpdate": "Tu bio fue actualizada",
|
||||
"contentNotAvailable": "El contenido no se encuentra disponible",
|
||||
"suspendedAccountMsg": "Tu cuenta se encuentra suspendida. Esto significa que no puedes dar Like, Marcar o escribir commentarios. Por favor, contacta moderator@fakeurl for more information",
|
||||
"bannedAccountMsg": "Tu cuenta se encuentra suspendida. Esto significa que no puedes dar Like, Marcar o escribir commentarios. Por favor, contacta moderator@fakeurl for more information",
|
||||
"editNameMsg": "",
|
||||
"error": {
|
||||
"emailNotVerified": "Dirección de correo electrónico {0} no verificada.",
|
||||
"email": "No es un email válido",
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import React, {Component} from 'react';
|
||||
import {Tooltip} from 'coral-ui';
|
||||
import FlagBio from 'coral-plugin-flags/FlagBio';
|
||||
const packagename = 'coral-plugin-author-name';
|
||||
import styles from './styles.css';
|
||||
|
||||
@@ -24,24 +22,11 @@ export default class AuthorName extends Component {
|
||||
|
||||
render () {
|
||||
const {author} = this.props;
|
||||
const {showTooltip} = this.state;
|
||||
return (
|
||||
<div className={`${packagename}-text ${styles.container}`} onClick={this.handleClick} onMouseLeave={this.handleMouseLeave}>
|
||||
<a className={`${styles.authorName} ${author.settings.bio ? styles.hasBio : ''}`}>
|
||||
{author && author.name}
|
||||
{author.settings.bio ? <span className={`${styles.arrowDown} ${showTooltip ? styles.arrowUp : ''}`} /> : null}
|
||||
</a>
|
||||
{showTooltip && author.settings.bio
|
||||
&& (
|
||||
<Tooltip>
|
||||
<div className={`${packagename}-bio`}>
|
||||
{author.settings.bio}
|
||||
</div>
|
||||
<div className={`${packagename}-bio-flag`}>
|
||||
<FlagBio {...this.props}/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
<div
|
||||
className={`${packagename}-text`}
|
||||
className={`${styles.authorName}`}>
|
||||
{author && author.name}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ export default ({showSignInDialog}) => (
|
||||
From the Settings Page you can
|
||||
<ul>
|
||||
<li>See your comment history</li>
|
||||
<li>Write a bio about yourself to display to the community</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,12 +4,10 @@ import React, {Component} from 'react';
|
||||
import I18n from 'coral-framework/modules/i18n/i18n';
|
||||
|
||||
import {myCommentHistory} from 'coral-framework/graphql/queries';
|
||||
import {saveBio} from 'coral-framework/actions/user';
|
||||
|
||||
import BioContainer from './BioContainer';
|
||||
import {link} from 'coral-framework/PymConnection';
|
||||
import NotLoggedIn from '../components/NotLoggedIn';
|
||||
import {TabBar, Tab, TabContent, Spinner} from 'coral-ui';
|
||||
import {Spinner} from 'coral-ui';
|
||||
import SettingsHeader from '../components/SettingsHeader';
|
||||
import CommentHistory from 'coral-plugin-history/CommentHistory';
|
||||
|
||||
@@ -33,8 +31,7 @@ class SettingsContainer extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const {loggedIn, userData, asset, showSignInDialog, data, user} = this.props;
|
||||
const {activeTab} = this.state;
|
||||
const {loggedIn, asset, showSignInDialog, data} = this.props;
|
||||
const {me} = this.props.data;
|
||||
|
||||
if (!loggedIn || !me) {
|
||||
@@ -48,25 +45,30 @@ class SettingsContainer extends Component {
|
||||
return (
|
||||
<div>
|
||||
<SettingsHeader {...this.props} />
|
||||
<TabBar onChange={this.handleTabChange} activeTab={activeTab} cStyle='material'>
|
||||
<Tab>{lang.t('allComments')} ({user.myComments.length})</Tab>
|
||||
<Tab>{lang.t('profileSettings')}</Tab>
|
||||
</TabBar>
|
||||
<TabContent show={activeTab === 0}>
|
||||
{
|
||||
me.comments.length ?
|
||||
<CommentHistory
|
||||
comments={me.comments}
|
||||
asset={asset}
|
||||
link={link}
|
||||
/>
|
||||
:
|
||||
<p>{lang.t('userNoComment')}</p>
|
||||
}
|
||||
</TabContent>
|
||||
<TabContent show={activeTab === 1}>
|
||||
<BioContainer bio={userData.settings.bio} handleSave={this.handleSave} {...this.props} />
|
||||
</TabContent>
|
||||
{
|
||||
|
||||
// Hiding bio until moderation can get figured out
|
||||
/* <TabBar onChange={this.handleTabChange} activeTab={activeTab} cStyle='material'>
|
||||
<Tab>{lang.t('allComments')} ({user.myComments.length})</Tab>
|
||||
<Tab>{lang.t('profileSettings')}</Tab>
|
||||
</TabBar>
|
||||
<TabContent show={activeTab === 0}> */
|
||||
me.comments.length ?
|
||||
<CommentHistory
|
||||
comments={me.comments}
|
||||
asset={asset}
|
||||
link={link}
|
||||
/>
|
||||
:
|
||||
<p>{lang.t('userNoComment')}</p>
|
||||
|
||||
// Hiding user bio pending effective moderation system.
|
||||
/* </TabContent>
|
||||
<TabContent show={activeTab === 1}>
|
||||
<BioContainer bio={userData.settings.bio} handleSave={this.handleSave} {...this.props} />
|
||||
</TabContent> */
|
||||
}
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -78,8 +80,9 @@ const mapStateToProps = state => ({
|
||||
auth: state.auth.toJS()
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
saveBio: (user_id, formData) => dispatch(saveBio(user_id, formData))
|
||||
const mapDispatchToProps = () => ({
|
||||
|
||||
// saveBio: (user_id, formData) => dispatch(saveBio(user_id, formData))
|
||||
});
|
||||
|
||||
export default compose(
|
||||
|
||||
@@ -2,7 +2,6 @@ const _ = require('lodash');
|
||||
|
||||
const Comment = require('./comment');
|
||||
const Action = require('./action');
|
||||
const User = require('./user');
|
||||
|
||||
module.exports = (context) => {
|
||||
|
||||
@@ -10,7 +9,6 @@ module.exports = (context) => {
|
||||
return _.merge(...[
|
||||
Comment,
|
||||
Action,
|
||||
User,
|
||||
].map((mutators) => {
|
||||
|
||||
// Each set of mutators is a function which takes the context.
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
const UsersService = require('../../services/users');
|
||||
|
||||
/**
|
||||
* Updates a users settings.
|
||||
* @param {Object} user the user performing the request
|
||||
* @param {String} bio the new user bio
|
||||
* @return {Promise}
|
||||
*/
|
||||
const updateUserSettings = ({user}, {bio}) => {
|
||||
return UsersService.updateSettings(user.id, {bio});
|
||||
};
|
||||
|
||||
module.exports = (context) => {
|
||||
|
||||
// TODO: refactor to something that'll return an error in the event an attempt
|
||||
// is made to mutate state while not logged in. There's got to be a better way
|
||||
// to do this.
|
||||
if (context.user && context.user.can('mutation:updateUserSettings')) {
|
||||
return {
|
||||
User: {
|
||||
updateSettings: (settings) => updateUserSettings(context, settings)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
User: {
|
||||
updateSettings: () => {}
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -8,9 +8,6 @@ const RootMutation = {
|
||||
deleteAction(_, {id}, {mutators: {Action}}) {
|
||||
return Action.delete({id});
|
||||
},
|
||||
updateUserSettings(_, {settings}, {mutators: {User}}) {
|
||||
return User.updateSettings(settings);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = RootMutation;
|
||||
|
||||
+4
-16
@@ -10,11 +10,6 @@ enum SORT_ORDER {
|
||||
# Date represented as an ISO8601 string.
|
||||
scalar Date
|
||||
|
||||
type UserSettings {
|
||||
# bio of the user.
|
||||
bio: String
|
||||
}
|
||||
|
||||
input CommentsQuery {
|
||||
# current status of a comment.
|
||||
statuses: [COMMENT_STATUS]
|
||||
@@ -22,7 +17,7 @@ input CommentsQuery {
|
||||
# asset that a comment is on.
|
||||
asset_id: ID
|
||||
|
||||
# the parent of the comment that we want to retrive.
|
||||
# the parent of the comment that we want to retrieve.
|
||||
parent_id: ID
|
||||
|
||||
# comments returned will only be ones which have at least one action of this
|
||||
@@ -61,8 +56,8 @@ type User {
|
||||
# the current roles of the user.
|
||||
roles: [USER_ROLES]
|
||||
|
||||
# settings for a user.
|
||||
settings: UserSettings
|
||||
# determines whether the user can edit their username
|
||||
canEditName: Boolean
|
||||
|
||||
# returns all comments based on a query.
|
||||
comments(query: CommentsQuery): [Comment]
|
||||
@@ -150,7 +145,7 @@ type Asset {
|
||||
# The scraped title of the asset.
|
||||
title: String
|
||||
|
||||
# The URL that the asset is locaed on.
|
||||
# The URL that the asset is located on.
|
||||
url: String
|
||||
|
||||
# The top level comments that are attached to the asset.
|
||||
@@ -205,11 +200,6 @@ input CreateActionInput {
|
||||
item_id: ID!
|
||||
}
|
||||
|
||||
input UpdateUserSettingsInput {
|
||||
# user bio
|
||||
bio: String!
|
||||
}
|
||||
|
||||
type RootMutation {
|
||||
# creates a comment on the asset.
|
||||
createComment(asset_id: ID!, parent_id: ID, body: String!): Comment
|
||||
@@ -220,8 +210,6 @@ type RootMutation {
|
||||
# delete an action based on the action id.
|
||||
deleteAction(id: ID!): Boolean
|
||||
|
||||
# updates a user's settings, it will return if the query was successful.
|
||||
updateUserSettings(settings: UpdateUserSettingsInput!): Boolean
|
||||
}
|
||||
|
||||
schema {
|
||||
|
||||
+8
-2
@@ -12,7 +12,7 @@ const USER_STATUS = [
|
||||
'ACTIVE',
|
||||
'BANNED',
|
||||
'PENDING',
|
||||
'SUSPENDED',
|
||||
'APPROVED' // Indicates that the users' displayname has been approved
|
||||
];
|
||||
|
||||
// ProfileSchema is the mongoose schema defined as the representation of a
|
||||
@@ -97,6 +97,12 @@ const UserSchema = new mongoose.Schema({
|
||||
default: 'ACTIVE'
|
||||
},
|
||||
|
||||
// Determines whether the user can edit their username.
|
||||
canEditName: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
// User's settings
|
||||
settings: {
|
||||
bio: {
|
||||
@@ -141,7 +147,7 @@ const USER_GRAPH_OPERATIONS = [
|
||||
'mutation:createComment',
|
||||
'mutation:createAction',
|
||||
'mutation:deleteAction',
|
||||
'mutation:updateUserSettings'
|
||||
'mutation:editName'
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -115,19 +115,18 @@ router.put('/password/reset', (req, res, next) => {
|
||||
});
|
||||
});
|
||||
|
||||
router.put('/settings', authorization.needed(), (req, res, next) => {
|
||||
|
||||
const {
|
||||
bio
|
||||
} = req.body;
|
||||
|
||||
router.put('/displayname', authorization.needed(), (req, res, next) => {
|
||||
UsersService
|
||||
.updateSettings(req.user.id, {bio})
|
||||
.editName(req.user.id, req.body.displayName)
|
||||
.then(() => {
|
||||
res.status(204).end();
|
||||
})
|
||||
.catch((err) => {
|
||||
next(err);
|
||||
.catch(error => {
|
||||
if (error.code === 11000) {
|
||||
next(errors.ErrDisplayTaken);
|
||||
} else {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ const express = require('express');
|
||||
const passport = require('../../../services/passport');
|
||||
const authorization = require('../../../middleware/authorization');
|
||||
const errors = require('../../../errors');
|
||||
const UsersService = require('../../../services/users');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@@ -69,15 +70,20 @@ const HandleAuthPopupCallback = (req, res, next) => (err, user) => {
|
||||
return res.render('auth-callback', {err: JSON.stringify(errors.ErrNotAuthorized), data: null});
|
||||
}
|
||||
|
||||
// Perform the login of the user!
|
||||
req.logIn(user, (err) => {
|
||||
if (err) {
|
||||
return res.render('auth-callback', {err: JSON.stringify(err), data: null});
|
||||
}
|
||||
// Authorize the user to edit their displayName.
|
||||
UsersService.toggleNameEdit(user.id, true)
|
||||
.then(() => {
|
||||
|
||||
// Perform the login of the user!
|
||||
req.logIn(user, (err) => {
|
||||
if (err) {
|
||||
return res.render('auth-callback', {err: JSON.stringify(err), data: null});
|
||||
}
|
||||
|
||||
// We logged in the user! Let's send back the user data.
|
||||
res.render('auth-callback', {err: null, data: JSON.stringify(user)});
|
||||
});
|
||||
// We logged in the user! Let's send back the user data.
|
||||
res.render('auth-callback', {err: null, data: JSON.stringify(user)});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -79,7 +79,7 @@ router.get('/comments/flagged', authorization.needed('ADMIN'), (req, res, next)
|
||||
});
|
||||
|
||||
// Returns back all the users that are in the moderation queue.
|
||||
router.get('/users/pending', (req, res, next) => {
|
||||
router.get('/users/flagged', (req, res, next) => {
|
||||
UsersService.moderationQueue()
|
||||
.then((users) => {
|
||||
return Promise.all([
|
||||
|
||||
@@ -60,15 +60,16 @@ router.post('/:user_id/status', authorization.needed('ADMIN'), (req, res, next)
|
||||
.catch(next);
|
||||
});
|
||||
|
||||
router.post('/:user_id/displayname', authorization.needed(), (req, res, next) => {
|
||||
UsersService.setDisplayName(req.params.user_id, req.body.displayName)
|
||||
.then((user) => {
|
||||
res.status(201).json(user);
|
||||
})
|
||||
.catch(next);
|
||||
router.post('/:user_id/username-enable', authorization.needed('ADMIN'), (req, res, next) => {
|
||||
UsersService
|
||||
.toggleNameEdit(req.params.user_id, true)
|
||||
.then(() => {
|
||||
res.status(204).end();
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/:user_id/email', authorization.needed('admin'), (req, res, next) => {
|
||||
router.post('/:user_id/email', authorization.needed('ADMIN'), (req, res, next) => {
|
||||
|
||||
UsersService.findById(req.params.user_id)
|
||||
.then(user => {
|
||||
let localProfile = user.profiles.find((profile) => profile.provider === 'local');
|
||||
@@ -170,7 +171,7 @@ router.post('/:user_id/actions', authorization.needed(), (req, res, next) => {
|
||||
|
||||
// Set the user status to "pending" for review by moderators
|
||||
if (action_type.slice(0, 4) === 'FLAG') {
|
||||
return UsersService.setStatus(req.user.id, 'PENDING')
|
||||
return UsersService.setStatus(req.params.user_id, 'PENDING')
|
||||
.then(() => action);
|
||||
} else {
|
||||
return action;
|
||||
|
||||
+43
-20
@@ -350,26 +350,16 @@ module.exports = class UsersService {
|
||||
return Promise.reject(new Error(`status ${status} is not supported`));
|
||||
}
|
||||
|
||||
return UserModel.update({id}, {$set: {status}});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the display name of a user.
|
||||
* @param {String} id id of a user
|
||||
* @param {String} displayName display name to set
|
||||
* @param {Function} done callback after the operation is complete
|
||||
*/
|
||||
static setDisplayName(id, displayName) {
|
||||
|
||||
return UsersService.isValidDisplayName(displayName)
|
||||
.then(() => { // displayName is valid
|
||||
return UserModel.update(
|
||||
{id},
|
||||
{$set: {'displayName': displayName}})
|
||||
.then(() => {
|
||||
return UserModel.findOne({'id': id});
|
||||
});
|
||||
});
|
||||
return UserModel.update({
|
||||
id,
|
||||
status: {
|
||||
$ne: 'APPROVED'
|
||||
}
|
||||
}, {
|
||||
$set: {
|
||||
status
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -644,4 +634,37 @@ module.exports = class UsersService {
|
||||
return UserModel.find({status: 'PENDING'});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives the user the ability to edit their username.
|
||||
* @param {String} id the id of the user to be toggled.
|
||||
* @param {Boolean} canEditName sets whether the user can edit their name.
|
||||
* @return {Promise}
|
||||
*/
|
||||
static toggleNameEdit(id, canEditName) {
|
||||
return UserModel.update({id}, {
|
||||
$set: {canEditName}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the user's displayName.
|
||||
* @param {String} id the id of the user to be enabled.
|
||||
* @param {String} displayName The new displayname for the user.
|
||||
* @return {Promise}
|
||||
*/
|
||||
static editName(id, displayName) {
|
||||
return UserModel.update({
|
||||
id,
|
||||
canEditName: true
|
||||
}, {
|
||||
$set: {
|
||||
displayName: displayName.toLowerCase(),
|
||||
canEditName: false,
|
||||
status: 'PENDING'
|
||||
}
|
||||
}).then((result) => {
|
||||
return result.nModified > 0 ? result :
|
||||
Promise.reject(new Error('You do not have permission to update your username.'));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -10,8 +10,7 @@ const embedStreamCommands = {
|
||||
return this
|
||||
.waitForElementVisible('@moderationList')
|
||||
.waitForElementVisible('@approveButton')
|
||||
.click('@approveButton')
|
||||
.waitForElementNotPresent('@approveButton');
|
||||
.click('@approveButton');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
const passport = require('../../../passport');
|
||||
|
||||
const app = require('../../../../app');
|
||||
const chai = require('chai');
|
||||
const expect = chai.expect;
|
||||
|
||||
const SettingsService = require('../../../../services/settings');
|
||||
const settings = {id: '1', moderation: 'PRE', wordlist: {banned: ['bad words'], suspect: ['suspect words']}};
|
||||
|
||||
// Setup chai.
|
||||
chai.should();
|
||||
chai.use(require('chai-http'));
|
||||
|
||||
const UsersService = require('../../../../services/users');
|
||||
|
||||
describe('/api/v1/account/displayname', () => {
|
||||
let mockUser;
|
||||
|
||||
beforeEach(() => SettingsService.init(settings).then(() => {
|
||||
return UsersService.createLocalUser('ana@gmail.com', '123321123', 'Ana');
|
||||
})
|
||||
.then((user) => {
|
||||
mockUser = user;
|
||||
}));
|
||||
|
||||
describe('#put', () => {
|
||||
it('it should enable a user to edit their username if canEditName is enabled', () => {
|
||||
return chai.request(app)
|
||||
.post(`/api/v1/users/${mockUser.id}/username-enable`)
|
||||
.set(passport.inject({id: '456', roles: ['ADMIN']}))
|
||||
.then(() => chai.request(app)
|
||||
.put('/api/v1/account/displayname')
|
||||
.set(passport.inject({id: mockUser.id, roles: []}))
|
||||
.send({displayName: 'MojoJojo'}))
|
||||
.then((res) => {
|
||||
expect(res).to.have.status(204);
|
||||
});
|
||||
});
|
||||
|
||||
it('it should return an error if the wrong user tries to edit a username', (done) => {
|
||||
chai.request(app)
|
||||
.post(`/api/v1/users/${mockUser.id}/username-enable`)
|
||||
.set(passport.inject({id: '456', roles: ['ADMIN']}))
|
||||
.then(() => chai.request(app)
|
||||
.put('/api/v1/account/displayname')
|
||||
.set(passport.inject({id: 'wrongid', roles: []}))
|
||||
.send({displayName: 'MojoJojo'}))
|
||||
.then(() => {
|
||||
done(new Error('Exected Error'));
|
||||
})
|
||||
.catch((err) => {
|
||||
expect(err).to.be.truthy;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('it should return an error when the user tries to edit their username if canEditName is disabled', (done) => {
|
||||
chai.request(app)
|
||||
.put('/api/v1/account/displayname')
|
||||
.set(passport.inject({id: mockUser.id, roles: []}))
|
||||
.send({username: 'MojoJojo'})
|
||||
.then(() => {
|
||||
done(new Error('Exected Error'));
|
||||
})
|
||||
.catch((err) => {
|
||||
expect(err).to.be.truthy;
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -110,7 +110,7 @@ describe('/api/v1/queue', () => {
|
||||
|
||||
it('should return all pending users and actions', function(done){
|
||||
chai.request(app)
|
||||
.get('/api/v1/queue/users/pending')
|
||||
.get('/api/v1/queue/users/flagged')
|
||||
.set(passport.inject({roles: ['ADMIN']}))
|
||||
.end(function(err, res){
|
||||
expect(err).to.be.null;
|
||||
|
||||
@@ -52,33 +52,48 @@ describe('/api/v1/users/:user_id/email/confirm', () => {
|
||||
|
||||
describe('/api/v1/users/:user_id/actions', () => {
|
||||
|
||||
const users = [{
|
||||
displayName: 'Ana',
|
||||
email: 'ana@gmail.com',
|
||||
password: '123456789'
|
||||
}, {
|
||||
displayName: 'Maria',
|
||||
email: 'maria@gmail.com',
|
||||
password: '123456789'
|
||||
}];
|
||||
let mockUser;
|
||||
|
||||
beforeEach(() => {
|
||||
return SettingsService.init(settings).then(() => {
|
||||
return UsersService.createLocalUsers(users);
|
||||
});
|
||||
});
|
||||
beforeEach(() => SettingsService.init(settings).then(() => {
|
||||
return UsersService.createLocalUser('ana@gmail.com', '123321123', 'Ana');
|
||||
})
|
||||
.then((user) => {
|
||||
mockUser = user;
|
||||
}));
|
||||
|
||||
describe('#post', () => {
|
||||
it('it should update actions', () => {
|
||||
return chai.request(app)
|
||||
.post('/api/v1/users/abc/actions')
|
||||
.post(`/api/v1/users/${mockUser.id}/actions`)
|
||||
.set(passport.inject({id: '456', roles: ['ADMIN']}))
|
||||
.send({'action_type': 'FLAG', metadata: {reason: 'Bio is too awesome.'}})
|
||||
.then((res) => {
|
||||
expect(res).to.have.status(201);
|
||||
expect(res).to.have.body;
|
||||
expect(res.body).to.have.property('action_type', 'FLAG');
|
||||
expect(res.body).to.have.property('item_id', 'abc');
|
||||
expect(res.body).to.have.property('item_id', mockUser.id);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('/api/v1/users/:user_id/username-enable', () => {
|
||||
let mockUser;
|
||||
|
||||
beforeEach(() => SettingsService.init(settings).then(() => {
|
||||
return UsersService.createLocalUser('ana@gmail.com', '123321123', 'Ana');
|
||||
})
|
||||
.then((user) => {
|
||||
mockUser = user;
|
||||
}));
|
||||
|
||||
describe('#post', () => {
|
||||
it('it should enable a user to edit their username', () => {
|
||||
return chai.request(app)
|
||||
.post(`/api/v1/users/${mockUser.id}/username-enable`)
|
||||
.set(passport.inject({id: '456', roles: ['ADMIN']}))
|
||||
.then((res) => {
|
||||
expect(res).to.have.status(204);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+52
-19
@@ -178,25 +178,6 @@ describe('services.UsersService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('#setDisplayName', () => {
|
||||
it('should set the display name to a new unique one', () => {
|
||||
return UsersService
|
||||
.setDisplayName(mockUsers[0].id, 'maria')
|
||||
.then(() => UsersService.findById(mockUsers[0].id))
|
||||
.then((user) => {
|
||||
expect(user).to.have.property('displayName', 'maria');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an error when the displayName is not unique', () => {
|
||||
return UsersService
|
||||
.setDisplayName(mockUsers[0].id, 'marvel')
|
||||
.catch((error) => {
|
||||
expect(error).to.not.be.null;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#ban', () => {
|
||||
it('should set the status to banned', () => {
|
||||
return UsersService
|
||||
@@ -227,4 +208,56 @@ describe('services.UsersService', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#toggleNameEdit', () => {
|
||||
it('should toggle the canEditName field', () => {
|
||||
return UsersService
|
||||
.toggleNameEdit(mockUsers[0].id, true)
|
||||
.then(() => UsersService.findById(mockUsers[0].id))
|
||||
.then((user) => {
|
||||
expect(user).to.have.property('canEditName', true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#editName', () => {
|
||||
it('should let the user edit their username if the proper toggle is set', () => {
|
||||
return UsersService
|
||||
.toggleNameEdit(mockUsers[0].id, true)
|
||||
.then(() => UsersService.editName(mockUsers[0].id, 'Jojo'))
|
||||
.then(() => UsersService.findById(mockUsers[0].id))
|
||||
.then((user) => {
|
||||
expect(user).to.have.property('displayName', 'jojo');
|
||||
expect(user).to.have.property('canEditName', false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an error if canEditName is false', (done) => {
|
||||
UsersService
|
||||
.editName(mockUsers[0].id, 'Jojo')
|
||||
.then(() => UsersService.findById(mockUsers[0].id))
|
||||
.then(() => {
|
||||
done(new Error('Error expected'));
|
||||
})
|
||||
.catch((err) => {
|
||||
expect(err).to.be.truthy;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an error if the username is already taken', (done) => {
|
||||
UsersService
|
||||
.toggleNameEdit(mockUsers[0].id, true)
|
||||
.then(() => UsersService.editName(mockUsers[0].id, 'Marvel'))
|
||||
.then(() => UsersService.findById(mockUsers[0].id))
|
||||
.then(() => {
|
||||
done(new Error('Error expected'));
|
||||
})
|
||||
.catch((err) => {
|
||||
expect(err).to.be.truthy;
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user