Merged master into my-comments
@@ -3,10 +3,10 @@ const bodyParser = require('body-parser');
|
||||
const morgan = require('morgan');
|
||||
const path = require('path');
|
||||
const helmet = require('helmet');
|
||||
const passport = require('./passport');
|
||||
const passport = require('./services/passport');
|
||||
const session = require('express-session');
|
||||
const RedisStore = require('connect-redis')(session);
|
||||
const redis = require('./redis');
|
||||
const redis = require('./services/redis');
|
||||
|
||||
const app = express();
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ const pkg = require('../package.json');
|
||||
const parseDuration = require('parse-duration');
|
||||
const Table = require('cli-table');
|
||||
const Asset = require('../models/asset');
|
||||
const mongoose = require('../mongoose');
|
||||
const mongoose = require('../services/mongoose');
|
||||
const scraper = require('../services/scraper');
|
||||
const util = require('../util');
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@ process.env.DEBUG = process.env.TALK_DEBUG;
|
||||
const program = require('commander');
|
||||
const scraper = require('../services/scraper');
|
||||
const util = require('../util');
|
||||
const mongoose = require('../mongoose');
|
||||
const kue = require('../kue');
|
||||
const mongoose = require('../services/mongoose');
|
||||
const kue = require('../services/kue');
|
||||
|
||||
util.onshutdown([
|
||||
() => mongoose.disconnect()
|
||||
|
||||
@@ -11,7 +11,7 @@ const debug = require('debug')('talk:server');
|
||||
const http = require('http');
|
||||
const init = require('../init');
|
||||
const scraper = require('../services/scraper');
|
||||
const mongoose = require('../mongoose');
|
||||
const mongoose = require('../services/mongoose');
|
||||
const util = require('../util');
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,7 +11,7 @@ process.env.DEBUG = process.env.TALK_DEBUG;
|
||||
*/
|
||||
|
||||
const program = require('commander');
|
||||
const mongoose = require('../mongoose');
|
||||
const mongoose = require('../services/mongoose');
|
||||
const Setting = require('../models/setting');
|
||||
const util = require('../util');
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ const program = require('commander');
|
||||
const pkg = require('../package.json');
|
||||
const prompt = require('prompt');
|
||||
const User = require('../models/user');
|
||||
const mongoose = require('../mongoose');
|
||||
const mongoose = require('../services/mongoose');
|
||||
const util = require('../util');
|
||||
const Table = require('cli-table');
|
||||
|
||||
|
||||
@@ -26,7 +26,10 @@ export const updateSettings = settings => {
|
||||
};
|
||||
|
||||
export const saveSettingsToServer = () => (dispatch, getState) => {
|
||||
const settings = getState().settings.toJS().settings;
|
||||
let settings = getState().settings.toJS().settings;
|
||||
if (settings.charCount) {
|
||||
settings.charCount = parseInt(settings.charCount);
|
||||
}
|
||||
dispatch({type: SAVE_SETTINGS_LOADING});
|
||||
coralApi('/settings', {method: 'PUT', body: settings})
|
||||
.then(() => {
|
||||
|
||||
@@ -14,18 +14,18 @@ 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'));
|
||||
let authorStatus = author.status;
|
||||
const links = linkify.getMatches(comment.body);
|
||||
|
||||
return (
|
||||
<li tabIndex={props.index} className={`${styles.listItem} ${props.isActive && !props.hideActive ? styles.activeItem : ''}`}>
|
||||
<div className={styles.itemHeader}>
|
||||
<div className={styles.author}>
|
||||
<i className={`material-icons ${styles.avatar}`}>person</i>
|
||||
<span>{author.get('displayName') || lang.t('comment.anon')}</span>
|
||||
<span className={styles.created}>{timeago().format(comment.get('createdAt') || (Date.now() - props.index * 60 * 1000), lang.getLocale().replace('-', '_'))}</span>
|
||||
{comment.get('flagged') ? <p className={styles.flagged}>{lang.t('comment.flagged')}</p> : null}
|
||||
<span>{author.displayName || lang.t('comment.anon')}</span>
|
||||
<span className={styles.created}>{timeago().format(comment.createdAt || (Date.now() - props.index * 60 * 1000), lang.getLocale().replace('-', '_'))}</span>
|
||||
{comment.flagged ? <p className={styles.flagged}>{lang.t('comment.flagged')}</p> : null}
|
||||
</div>
|
||||
<div>
|
||||
{links ?
|
||||
@@ -42,7 +42,7 @@ export default props => {
|
||||
<div className={styles.itemBody}>
|
||||
<span className={styles.body}>
|
||||
<Linkify component='span' properties={{style: linkStyles}}>
|
||||
{comment.get('body')}
|
||||
{comment.body}
|
||||
</Linkify>
|
||||
</span>
|
||||
</div>
|
||||
@@ -52,9 +52,10 @@ export default props => {
|
||||
|
||||
// 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');
|
||||
const {comment, author} = props;
|
||||
const status = comment.status;
|
||||
const flagged = comment.flagged;
|
||||
const banned = (author.status === 'banned');
|
||||
|
||||
if (action === 'flag' && (status || flagged === true)) {
|
||||
return null;
|
||||
@@ -64,17 +65,19 @@ const getActionButton = (action, i, props) => {
|
||||
<Button
|
||||
disabled={banned ? 'disabled' : ''}
|
||||
cStyle='black'
|
||||
onClick={() => props.onClickShowBanDialog(props.author.get('id'), props.author.get('displayName'), props.comment.get('id'))}
|
||||
onClick={() => props.onClickShowBanDialog(author.id, author.displayName, comment.id)}
|
||||
key={i} >
|
||||
{lang.t('comment.ban_user')}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<FabButton icon={props.actionsMap[action].icon} className={styles.actionButton}
|
||||
<FabButton
|
||||
className={styles.actionButton}
|
||||
icon={props.actionsMap[action].icon}
|
||||
cStyle={action}
|
||||
key={i}
|
||||
onClick={() => props.onClickAction(props.actionsMap[action].status, props.comment.get('id'))}
|
||||
onClick={() => props.onClickAction(props.actionsMap[action].status, comment.id)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -37,7 +37,7 @@ export default class CommentList extends React.Component {
|
||||
// If entering to singleview and no active, active is the first eleement
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (nextProps.singleView && !this.state.active) {
|
||||
this.setState({active: nextProps.commentIds.get(0)});
|
||||
this.setState({active: nextProps.commentIds[0]});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,12 +81,12 @@ export default class CommentList extends React.Component {
|
||||
const {commentIds} = this.props;
|
||||
const {active} = this.state;
|
||||
// check boundaries
|
||||
if (active === null || !commentIds.size) {
|
||||
this.setState({active: commentIds.get(0)});
|
||||
} else if (direction === 'up' && active !== commentIds.first()) {
|
||||
this.setState({active: commentIds.get(commentIds.indexOf(active) - 1)});
|
||||
} else if (direction === 'down' && active !== commentIds.last()) {
|
||||
this.setState({active: commentIds.get(commentIds.indexOf(active) + 1)});
|
||||
if (active === null || !commentIds.length) {
|
||||
this.setState({active: commentIds[0]});
|
||||
} else if (direction === 'up' && active !== commentIds[0]) {
|
||||
this.setState({active: commentIds[commentIds.indexOf(active) - 1]});
|
||||
} else if (direction === 'down' && active !== commentIds[commentIds.length - 1]) {
|
||||
this.setState({active: commentIds[commentIds.indexOf(active) + 1]});
|
||||
}
|
||||
|
||||
// scroll to the position
|
||||
@@ -105,10 +105,10 @@ export default class CommentList extends React.Component {
|
||||
// activate the next comment
|
||||
if (id === this.state.active) {
|
||||
const {commentIds} = this.props;
|
||||
if (commentIds.last() === this.state.active) {
|
||||
this.setState({active: commentIds.get(commentIds.size - 2)});
|
||||
if (commentIds[commentIds.length - 1] === this.state.active) {
|
||||
this.setState({active: commentIds[commentIds.length - 2]});
|
||||
} else {
|
||||
this.setState({active: commentIds.get(Math.min(commentIds.indexOf(this.state.active) + 1, commentIds.size - 1))});
|
||||
this.setState({active: commentIds[Math.min(commentIds.indexOf(this.state.active) + 1, commentIds.length - 1)]});
|
||||
}
|
||||
}
|
||||
this.props.onClickAction(action, id, author_id);
|
||||
@@ -125,10 +125,10 @@ export default class CommentList extends React.Component {
|
||||
return (
|
||||
<ul className={`${styles.list} ${singleView ? styles.singleView : ''}`} {...key}>
|
||||
{commentIds.map((commentId, index) => {
|
||||
const comment = comments.get(commentId);
|
||||
const comment = comments[commentId];
|
||||
const author = users[comment.author_id];
|
||||
return <Comment comment={comment}
|
||||
author={users.get(comment.get('author_id'))}
|
||||
ref={el => { if (el && commentId === active) { this._active = el; } }}
|
||||
author={author}
|
||||
key={index}
|
||||
index={index}
|
||||
onClickAction={this.onClickAction}
|
||||
@@ -137,7 +137,7 @@ export default class CommentList extends React.Component {
|
||||
actionsMap={actions}
|
||||
isActive={commentId === active}
|
||||
hideActive={hideActive} />;
|
||||
}).toArray()}
|
||||
})}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -46,9 +46,9 @@ class CommentStream extends React.Component {
|
||||
<CommentBox onSubmit={this.onSubmit} />
|
||||
<CommentList isActive hideActive
|
||||
singleView={false}
|
||||
commentIds={comments.get('ids')}
|
||||
comments={comments.get('byId')}
|
||||
users={users.get('byId')}
|
||||
commentIds={comments.ids}
|
||||
comments={comments.byId}
|
||||
users={users.byId}
|
||||
onClickAction={this.onClickAction}
|
||||
actions={['flag']}
|
||||
loading={comments.loading} />
|
||||
@@ -58,4 +58,9 @@ class CommentStream extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(({comments, users}) => ({comments, users}))(CommentStream);
|
||||
const mapStateToProps = state => ({
|
||||
comments: state.comments.toJS(),
|
||||
users: state.users.toJS()
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(CommentStream);
|
||||
|
||||
@@ -8,9 +8,25 @@ import {
|
||||
ListItemContent,
|
||||
ListItemAction,
|
||||
Textfield,
|
||||
Checkbox
|
||||
Checkbox,
|
||||
Icon
|
||||
} from 'react-mdl';
|
||||
|
||||
const updateCharCountEnable = (updateSettings, charCountChecked) => () => {
|
||||
const charCountEnable = !charCountChecked;
|
||||
updateSettings({charCountEnable});
|
||||
};
|
||||
|
||||
const updateCharCount = (updateSettings, settingsError) => (event) => {
|
||||
const charCount = event.target.value;
|
||||
if (charCount.match(/[^0-9]/) || charCount.length === 0) {
|
||||
settingsError('charCount', true);
|
||||
} else {
|
||||
settingsError('charCount', false);
|
||||
}
|
||||
updateSettings({charCount: charCount});
|
||||
};
|
||||
|
||||
const updateModeration = (updateSettings, mod) => () => {
|
||||
const moderation = mod === 'pre' ? 'post' : 'pre';
|
||||
updateSettings({moderation});
|
||||
@@ -31,48 +47,80 @@ const updateClosedMessage = (updateSettings) => (event) => {
|
||||
updateSettings({closedMessage});
|
||||
};
|
||||
|
||||
const CommentSettings = (props) => <List>
|
||||
<ListItem className={styles.configSetting}>
|
||||
<ListItemAction>
|
||||
<Checkbox
|
||||
onClick={updateModeration(props.updateSettings, props.settings.moderation)}
|
||||
checked={props.settings.moderation === 'pre'} />
|
||||
</ListItemAction>
|
||||
{lang.t('configure.enable-pre-moderation')}
|
||||
</ListItem>
|
||||
<ListItem threeLine className={styles.configSettingInfoBox}>
|
||||
<ListItemAction>
|
||||
<Checkbox
|
||||
onClick={updateInfoBoxEnable(props.updateSettings, props.settings.infoBoxEnable)}
|
||||
checked={props.settings.infoBoxEnable} />
|
||||
</ListItemAction>
|
||||
<ListItemContent>
|
||||
{lang.t('configure.include-comment-stream')}
|
||||
<p>
|
||||
{lang.t('configure.include-comment-stream-desc')}
|
||||
const CommentSettings = ({updateSettings, settingsError, settings, errors}) => <List>
|
||||
<ListItem className={`${styles.configSetting} ${settings.moderation === 'pre' ? styles.enabledSetting : styles.disabledSetting}`}>
|
||||
<ListItemAction>
|
||||
<Checkbox
|
||||
onClick={updateModeration(updateSettings, settings.moderation)}
|
||||
checked={settings.moderation === 'pre'} />
|
||||
</ListItemAction>
|
||||
<ListItemContent>
|
||||
<div className={styles.settingsHeader}>{lang.t('configure.enable-pre-moderation')}</div>
|
||||
<p className={settings.moderation === 'pre' ? '' : styles.disabledSettingText}>
|
||||
{lang.t('configure.enable-pre-moderation-text')}
|
||||
</p>
|
||||
</ListItemContent>
|
||||
</ListItem>
|
||||
<ListItem className={`${styles.configSettingInfoBox} ${props.settings.infoBoxEnable ? null : styles.hidden}`} >
|
||||
<ListItemContent>
|
||||
<Textfield
|
||||
onChange={updateInfoBoxContent(props.updateSettings)}
|
||||
value={props.settings.infoBoxContent}
|
||||
label={lang.t('configure.include-text')}
|
||||
rows={3}/>
|
||||
</ListItemContent>
|
||||
</ListItem>
|
||||
<ListItem className={styles.configSettingInfoBox}>
|
||||
<ListItemContent>
|
||||
{lang.t('configure.closed-comments-desc')}
|
||||
<Textfield
|
||||
onChange={updateClosedMessage(props.updateSettings)}
|
||||
value={props.settings.closedMessage}
|
||||
label={lang.t('configure.closed-comments-label')}
|
||||
rows={3}/>
|
||||
</ListItemContent>
|
||||
</ListItem>
|
||||
</List>;
|
||||
</ListItem>
|
||||
<ListItem className={`${styles.configSetting} ${settings.charCountEnable ? styles.enabledSetting : styles.disabledSetting}`}>
|
||||
<ListItemAction>
|
||||
<Checkbox
|
||||
onClick={updateCharCountEnable(updateSettings, settings.charCountEnable)}
|
||||
checked={settings.charCountEnable} />
|
||||
</ListItemAction>
|
||||
<ListItemContent>
|
||||
<div className={styles.settingsHeader}>{lang.t('configure.comment-count-header')}</div>
|
||||
<p className={settings.charCountEnable ? '' : styles.disabledSettingText}>
|
||||
<span>{lang.t('configure.comment-count-text-pre')}</span>
|
||||
<input type='text'
|
||||
className={`${styles.charCountTexfield} ${settings.charCountEnable && styles.charCountTexfieldEnabled}`}
|
||||
htmlFor='charCount'
|
||||
onChange={updateCharCount(updateSettings, settingsError)}
|
||||
value={settings.charCount}/>
|
||||
<span>{lang.t('configure.comment-count-text-post')}</span>
|
||||
{
|
||||
errors.charCount &&
|
||||
<span className={styles.settingsError}>
|
||||
<br/>
|
||||
<Icon name="error_outline"/>
|
||||
{lang.t('configure.comment-count-error')}
|
||||
</span>
|
||||
}
|
||||
</p>
|
||||
</ListItemContent>
|
||||
</ListItem>
|
||||
<ListItem threeLine className={`${styles.configSettingInfoBox} ${settings.infoBoxEnable ? styles.enabledSetting : styles.disabledSetting}`}>
|
||||
<ListItemAction>
|
||||
<Checkbox
|
||||
onClick={updateInfoBoxEnable(updateSettings, settings.infoBoxEnable)}
|
||||
checked={settings.infoBoxEnable} />
|
||||
</ListItemAction>
|
||||
<ListItemContent>
|
||||
{lang.t('configure.include-comment-stream')}
|
||||
<p>
|
||||
{lang.t('configure.include-comment-stream-desc')}
|
||||
</p>
|
||||
</ListItemContent>
|
||||
</ListItem>
|
||||
<ListItem className={`${styles.configSettingInfoBox} ${settings.infoBoxEnable ? null : styles.hidden}`} >
|
||||
<ListItemContent>
|
||||
<Textfield
|
||||
onChange={updateInfoBoxContent(updateSettings)}
|
||||
value={settings.infoBoxContent}
|
||||
label={lang.t('configure.include-text')}
|
||||
rows={3}/>
|
||||
</ListItemContent>
|
||||
</ListItem>
|
||||
<ListItem className={styles.configSettingInfoBox}>
|
||||
<ListItemContent>
|
||||
{lang.t('configure.closed-comments-desc')}
|
||||
<Textfield
|
||||
onChange={updateClosedMessage(updateSettings)}
|
||||
value={settings.closedMessage}
|
||||
label={lang.t('configure.closed-comments-label')}
|
||||
rows={3}/>
|
||||
</ListItemContent>
|
||||
</ListItem>
|
||||
</List>;
|
||||
|
||||
export default CommentSettings;
|
||||
|
||||
|
||||
@@ -18,9 +18,27 @@
|
||||
.configSetting {
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
height: 90px;
|
||||
height: 95px;
|
||||
margin-bottom: 10px;
|
||||
cursor: pointer;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.settingsError {
|
||||
color: #d50000;
|
||||
}
|
||||
|
||||
.settingsError i {
|
||||
font-size: 14px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
.settingsHeader {
|
||||
margin-top: 3px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.disabledSettingText {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.configSettingInfoBox {
|
||||
@@ -45,6 +63,22 @@
|
||||
display: block;
|
||||
}
|
||||
|
||||
.charCountTexfield {
|
||||
width: 4em;
|
||||
padding: 0px;
|
||||
border-color: #ccc;
|
||||
border-style: solid;
|
||||
border-width: 0px 0px 1px 0px;
|
||||
}
|
||||
|
||||
.charCountTexfieldEnabled {
|
||||
border-color: #4caf50;
|
||||
}
|
||||
|
||||
.charCountTexfield:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.changedSave {
|
||||
background-color:#4caf50;
|
||||
}
|
||||
@@ -84,7 +118,15 @@
|
||||
margin-bottom:3px;
|
||||
}
|
||||
|
||||
.enabledSetting {
|
||||
border-left-color: #4caf50;
|
||||
border-left-style: solid;
|
||||
border-left-width: 7px;
|
||||
}
|
||||
|
||||
.disabledSetting {
|
||||
padding-left: 22px;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
|
||||
@@ -22,7 +22,8 @@ class Configure extends React.Component {
|
||||
this.state = {
|
||||
activeSection: 'comments',
|
||||
wordlist: [],
|
||||
changed: false
|
||||
changed: false,
|
||||
errors: {}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -64,12 +65,23 @@ class Configure extends React.Component {
|
||||
this.props.dispatch(updateSettings(setting));
|
||||
}
|
||||
|
||||
// Sets an arbitrary error string and a boolean state.
|
||||
// This allows the system to track multiple errors.
|
||||
onSettingError = (error, state) => {
|
||||
this.setState((prevState) => {
|
||||
prevState.errors[error] = state;
|
||||
return prevState;
|
||||
});
|
||||
}
|
||||
|
||||
getSection = (section) => {
|
||||
switch(section){
|
||||
case 'comments':
|
||||
return <CommentSettings
|
||||
settings={this.props.settings}
|
||||
updateSettings={this.onSettingUpdate}/>;
|
||||
updateSettings={this.onSettingUpdate}
|
||||
errors={this.state.errors}
|
||||
settingsError={this.onSettingError}/>;
|
||||
case 'embed':
|
||||
return <EmbedLink/>;
|
||||
case 'wordlist':
|
||||
@@ -94,6 +106,9 @@ class Configure extends React.Component {
|
||||
let pageTitle = this.getPageTitle(this.state.activeSection);
|
||||
const section = this.getSection(this.state.activeSection);
|
||||
|
||||
const showSave = Object.keys(this.state.errors).reduce(
|
||||
(bool, error) => this.state.errors[error] ? false : bool, this.state.changed);
|
||||
|
||||
if (this.props.fetchingSettings) {
|
||||
pageTitle += ' - Loading...';
|
||||
}
|
||||
@@ -119,7 +134,7 @@ class Configure extends React.Component {
|
||||
</ListItem>
|
||||
</List>
|
||||
{
|
||||
this.state.changed ?
|
||||
showSave ?
|
||||
<Button
|
||||
raised
|
||||
onClick={this.saveSettings}
|
||||
|
||||
@@ -79,6 +79,10 @@ class ModerationQueue extends React.Component {
|
||||
const {comments, users} = this.props;
|
||||
const {activeTab, singleView, modalOpen} = this.state;
|
||||
|
||||
const premodIds = comments.ids.filter(id => comments.byId[id].status === 'premod');
|
||||
const rejectedIds = comments.ids.filter(id => comments.byId[id].status === 'rejected');
|
||||
const flaggedIds = comments.ids.filter(id => comments.byId[id].flagged === true);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='mdl-tabs mdl-js-tabs mdl-js-ripple-effect'>
|
||||
@@ -94,41 +98,26 @@ class ModerationQueue extends React.Component {
|
||||
<CommentList
|
||||
isActive={activeTab === 'pending'}
|
||||
singleView={singleView}
|
||||
commentIds={
|
||||
comments.get('ids')
|
||||
.filter(id =>
|
||||
comments
|
||||
.get('byId')
|
||||
.get(id)
|
||||
.get('status') === 'premod')
|
||||
}
|
||||
comments={comments.get('byId')}
|
||||
users={users.get('byId')}
|
||||
commentIds={premodIds}
|
||||
comments={comments.byId}
|
||||
users={users.byId}
|
||||
onClickAction={(action, commentId) => this.onCommentAction(action, commentId)}
|
||||
onClickShowBanDialog={(userId, userName, commentId) => this.showBanUserDialog(userId, userName, commentId)}
|
||||
actions={['reject', 'approve', 'ban']}
|
||||
loading={comments.loading} />
|
||||
<BanUserDialog
|
||||
open={comments.get('showBanUserDialog')}
|
||||
open={comments.showBanUserDialog}
|
||||
handleClose={() => this.hideBanUserDialog()}
|
||||
onClickBanUser={(userId, commentId) => this.banUser(userId, commentId)}
|
||||
user={comments.get('banUser')}/>
|
||||
user={comments.banUser}/>
|
||||
</div>
|
||||
<div className={`mdl-tabs__panel ${styles.listContainer}`} id='rejected'>
|
||||
<CommentList
|
||||
isActive={activeTab === 'rejected'}
|
||||
singleView={singleView}
|
||||
commentIds={
|
||||
comments
|
||||
.get('ids')
|
||||
.filter(id =>
|
||||
comments
|
||||
.get('byId')
|
||||
.get(id)
|
||||
.get('status') === 'rejected')
|
||||
}
|
||||
comments={comments.get('byId')}
|
||||
users={users.get('byId')}
|
||||
commentIds={rejectedIds}
|
||||
comments={comments.byId}
|
||||
users={users.byId}
|
||||
onClickAction={(action, id) => this.onCommentAction(action, id)}
|
||||
actions={['approve']}
|
||||
loading={comments.loading} />
|
||||
@@ -137,12 +126,9 @@ class ModerationQueue extends React.Component {
|
||||
<CommentList
|
||||
isActive={activeTab === 'rejected'}
|
||||
singleView={singleView}
|
||||
commentIds={comments.get('ids').filter(id => {
|
||||
const data = comments.get('byId').get(id);
|
||||
return !data.get('status') && data.get('flagged') === true;
|
||||
})}
|
||||
comments={comments.get('byId')}
|
||||
users={users.get('byId')}
|
||||
commentIds={flaggedIds}
|
||||
comments={comments.byId}
|
||||
users={users.byId}
|
||||
onClickAction={(action, id) => this.onCommentAction(action, id)}
|
||||
actions={['reject', 'approve']}
|
||||
loading={comments.loading} />
|
||||
@@ -155,6 +141,11 @@ class ModerationQueue extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(({comments, users}) => ({comments, users}))(ModerationQueue);
|
||||
const mapStateToProps = state => ({
|
||||
comments: state.comments.toJS(),
|
||||
users: state.users.toJS()
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(ModerationQueue);
|
||||
|
||||
const lang = new I18n(translations);
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
},
|
||||
"configure": {
|
||||
"enable-pre-moderation": "Enable pre-moderation",
|
||||
"enable-pre-moderation-text": "Moderators must approve any comment before it is published.",
|
||||
"include-comment-stream": "Include Comment Stream Description for Readers.",
|
||||
"include-comment-stream-desc": "Write a message to be added to the top of your comment stream. Pose a topic, include community guidelines, etc.",
|
||||
"include-text": "Include your text here.",
|
||||
@@ -55,7 +56,11 @@
|
||||
"configure": "Configure",
|
||||
"community": "Community",
|
||||
"closed-comments-desc": "Write a message for closed threads",
|
||||
"closed-comments-label": "Write a message..."
|
||||
"closed-comments-label": "Write a message...",
|
||||
"comment-count-header": "Limit Comment Length",
|
||||
"comment-count-text-pre": "Comments will be limited to ",
|
||||
"comment-count-text-post": " characters.",
|
||||
"comment-count-error": "Please enter a valid number."
|
||||
},
|
||||
"bandialog": {
|
||||
"ban_user": "Ban User?",
|
||||
@@ -96,6 +101,7 @@
|
||||
},
|
||||
"configure": {
|
||||
"enable-pre-moderation": "Habilitar pre-moderación",
|
||||
"enable-pre-moderation-text": "Los moderadores deben aprobar cada comentario antes de que sea publicado.",
|
||||
"include-comment-stream": "Incluir la Descripción a un Hilo de Comentario para los y las Lectoras.",
|
||||
"include-comment-stream-desc": "Escribir un mensaje que será agregado a la parte de arriba del tu hilo de comentarios. Por ejemplo, un tema, guias de comunidad, etc.",
|
||||
"include-text": "Incluir tu texto aqui.",
|
||||
@@ -110,7 +116,11 @@
|
||||
"configure": "Configurar",
|
||||
"community": "Comunidad",
|
||||
"closed-comments-desc": "Escribe un mensaje para cuando los comentarios se encuentran cerrados",
|
||||
"closed-comments-label": "Escribe un mensaje..."
|
||||
"closed-comments-label": "Escribe un mensaje...",
|
||||
"comment-count-header": "Limitar el largo del comentario",
|
||||
"comment-count-text-pre": "El largo de comentarios será ",
|
||||
"comment-count-text-post": " caracteres",
|
||||
"comment-count-error": "Por favor escribe un número válido."
|
||||
},
|
||||
"bandialog": {
|
||||
"ban_user": "Quieres suspender el Usuario?",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"configureCommentStream": {
|
||||
"apply": "Apply",
|
||||
"title": "Configure Comment Stream",
|
||||
"description": "As an admin you may customize the settings for the comment stream for this article",
|
||||
"description": "As an admin you may customize the settings for the comment stream for this asset",
|
||||
"enablePremod": "Enable Premoderation",
|
||||
"enablePremodDescription": "Moderators must approve any comment before its published.",
|
||||
"enablePremodLinks": "Pre-Moderate Comments Containing Links",
|
||||
|
||||
@@ -91,7 +91,7 @@ class CommentStream extends Component {
|
||||
const rootItem = this.props.items.assets && this.props.items.assets[rootItemId];
|
||||
const {actions, users, comments} = this.props.items;
|
||||
const {status, moderation, closedMessage} = this.props.config;
|
||||
const {loggedIn, user, showSignInDialog, signInOffset} = this.props.auth;
|
||||
const {loggedIn, isAdmin, user, showSignInDialog, signInOffset} = this.props.auth;
|
||||
const {activeTab} = this.state;
|
||||
const banned = (this.props.userData.status === 'banned');
|
||||
|
||||
@@ -105,7 +105,7 @@ class CommentStream extends Component {
|
||||
<TabBar onChange={this.changeTab} activeTab={activeTab}>
|
||||
<Tab><Count id={rootItemId} items={this.props.items}/></Tab>
|
||||
<Tab>Settings</Tab>
|
||||
<Tab>Configure Stream</Tab>
|
||||
<Tab restricted={!isAdmin}>Configure Stream</Tab>
|
||||
</TabBar>
|
||||
{loggedIn && <UserBox user={user} logout={this.props.logout} />}
|
||||
<TabContent show={activeTab === 0}>
|
||||
|
||||
@@ -20,15 +20,16 @@ export const cleanState = () => ({type: actions.CLEAN_STATE});
|
||||
// Sign In Actions
|
||||
|
||||
const signInRequest = () => ({type: actions.FETCH_SIGNIN_REQUEST});
|
||||
const signInSuccess = user => ({type: actions.FETCH_SIGNIN_SUCCESS, user});
|
||||
const signInSuccess = (user, isAdmin) => ({type: actions.FETCH_SIGNIN_SUCCESS, user, isAdmin});
|
||||
const signInFailure = error => ({type: actions.FETCH_SIGNIN_FAILURE, error});
|
||||
|
||||
export const fetchSignIn = (formData) => dispatch => {
|
||||
dispatch(signInRequest());
|
||||
coralApi('/auth/local', {method: 'POST', body: formData})
|
||||
.then(({user}) => {
|
||||
const isAdmin = !!user.roles.filter(i => i === 'admin').length;
|
||||
dispatch(signInSuccess(user, isAdmin));
|
||||
dispatch(hideSignInDialog());
|
||||
dispatch(signInSuccess(user));
|
||||
dispatch(addItem(user, 'users'));
|
||||
})
|
||||
.catch(() => dispatch(signInFailure(lang.t('error.emailPasswordError'))));
|
||||
@@ -73,7 +74,7 @@ const signUpFailure = error => ({type: actions.FETCH_SIGNUP_FAILURE, error});
|
||||
|
||||
export const fetchSignUp = formData => dispatch => {
|
||||
dispatch(signUpRequest());
|
||||
coralApi('/user', {method: 'POST', body: formData})
|
||||
coralApi('/users', {method: 'POST', body: formData})
|
||||
.then(({user}) => {
|
||||
dispatch(signUpSuccess(user));
|
||||
setTimeout(() =>{
|
||||
@@ -117,7 +118,7 @@ export const invalidForm = error => ({type: actions.INVALID_FORM, error});
|
||||
// Check Login
|
||||
|
||||
const checkLoginRequest = () => ({type: actions.CHECK_LOGIN_REQUEST});
|
||||
const checkLoginSuccess = user => ({type: actions.CHECK_LOGIN_SUCCESS, user});
|
||||
const checkLoginSuccess = (user, isAdmin) => ({type: actions.CHECK_LOGIN_SUCCESS, user, isAdmin});
|
||||
const checkLoginFailure = error => ({type: actions.CHECK_LOGIN_FAILURE, error});
|
||||
|
||||
export const checkLogin = () => dispatch => {
|
||||
@@ -125,10 +126,11 @@ export const checkLogin = () => dispatch => {
|
||||
coralApi('/auth')
|
||||
.then(user => {
|
||||
if (!user) {
|
||||
throw new Error('not logged in');
|
||||
throw new Error('Not logged in');
|
||||
}
|
||||
|
||||
dispatch(checkLoginSuccess(user));
|
||||
const isAdmin = !!user.roles.filter(i => i === 'admin').length;
|
||||
dispatch(checkLoginSuccess(user, isAdmin));
|
||||
})
|
||||
.catch(error => dispatch(checkLoginFailure(error)));
|
||||
};
|
||||
|
||||
@@ -6,14 +6,6 @@ import I18n from 'coral-framework/modules/i18n/i18n';
|
||||
import translations from './../translations';
|
||||
const lang = new I18n(translations);
|
||||
|
||||
export const updateOpenStatus = status => (dispatch, getState) => {
|
||||
const assetId = getState().items.get('assets')
|
||||
.keySeq()
|
||||
.toArray()[0];
|
||||
return coralApi(`/asset/${assetId}/status?status=${status}`, {method: 'PUT'})
|
||||
.then(() => dispatch({type: status === 'open' ? actions.OPEN_COMMENTS : actions.CLOSE_COMMENTS}));
|
||||
};
|
||||
|
||||
const updateConfigRequest = () => ({type: actions.UPDATE_CONFIG_REQUEST});
|
||||
const updateConfigSuccess = config => ({type: actions.UPDATE_CONFIG_SUCCESS, config});
|
||||
const updateConfigFailure = () => ({type: actions.UPDATE_CONFIG_FAILURE});
|
||||
@@ -24,10 +16,38 @@ export const updateConfiguration = newConfig => (dispatch, getState) => {
|
||||
.toArray()[0];
|
||||
|
||||
dispatch(updateConfigRequest());
|
||||
coralApi(`/asset/${assetId}/settings`, {method: 'PUT', body: newConfig})
|
||||
coralApi(`/assets/${assetId}/settings`, {method: 'PUT', body: newConfig})
|
||||
.then(() => {
|
||||
dispatch(addNotification('success', lang.t('successUpdateSettings')));
|
||||
dispatch(updateConfigSuccess(newConfig));
|
||||
})
|
||||
.catch(error => dispatch(updateConfigFailure(error)));
|
||||
};
|
||||
|
||||
export const updateOpenStream = closedBody => (dispatch, getState) => {
|
||||
const assetId = getState().items.get('assets')
|
||||
.keySeq()
|
||||
.toArray()[0];
|
||||
|
||||
dispatch(updateConfigRequest());
|
||||
|
||||
coralApi(`/assets/${assetId}/status`, {method: 'PUT', body: closedBody})
|
||||
.then(() => {
|
||||
dispatch(addNotification('success', lang.t('successUpdateSettings')));
|
||||
dispatch(updateConfigSuccess(closedBody));
|
||||
})
|
||||
.catch(error => dispatch(updateConfigFailure(error)));
|
||||
};
|
||||
|
||||
const openStream = () => ({type: actions.OPEN_COMMENTS});
|
||||
const closeStream = () => ({type: actions.CLOSE_COMMENTS});
|
||||
|
||||
export const updateOpenStatus = status => dispatch => {
|
||||
if (status === 'open') {
|
||||
dispatch(openStream());
|
||||
dispatch(updateOpenStream({closedAt: null}));
|
||||
} else {
|
||||
dispatch(closeStream());
|
||||
dispatch(updateOpenStream({closedAt: new Date().getTime()}));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@ import * as actions from '../constants/auth';
|
||||
const initialState = Map({
|
||||
isLoading: false,
|
||||
loggedIn: false,
|
||||
isAdmin: false,
|
||||
user: null,
|
||||
showSignInDialog: false,
|
||||
view: 'SIGNIN',
|
||||
@@ -50,10 +51,12 @@ export default function auth (state = initialState, action) {
|
||||
case actions.CHECK_LOGIN_SUCCESS:
|
||||
return state
|
||||
.set('loggedIn', true)
|
||||
.set('isAdmin', action.isAdmin)
|
||||
.set('user', purge(action.user));
|
||||
case actions.FETCH_SIGNIN_SUCCESS:
|
||||
return state
|
||||
.set('loggedIn', true)
|
||||
.set('isAdmin', action.isAdmin)
|
||||
.set('user', purge(action.user));
|
||||
case actions.FETCH_SIGNIN_FAILURE:
|
||||
return state
|
||||
@@ -80,9 +83,7 @@ export default function auth (state = initialState, action) {
|
||||
.set('isLoading', false)
|
||||
.set('successSignUp', true);
|
||||
case actions.LOGOUT_SUCCESS:
|
||||
return state
|
||||
.set('loggedIn', false)
|
||||
.set('user', null);
|
||||
return initialState;
|
||||
case actions.INVALID_FORM:
|
||||
return state
|
||||
.set('error', action.error);
|
||||
|
||||
@@ -22,7 +22,7 @@ export default (state = initialState, action) => {
|
||||
return state
|
||||
.set('status', 'closed');
|
||||
case actions.ADD_ITEM:
|
||||
return action.item_type === 'assets' ? state.set('status', action.item.status) : state;
|
||||
return action.item_type === 'assets' ? state.set('status', (action.item && action.item.closedAt && new Date(action.item.closedAt).getTime() <= new Date().getTime()) ? 'closed' : 'open') : state;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -45,8 +45,7 @@ class CommentBox extends Component {
|
||||
postItem(comment, 'comments')
|
||||
.then((postedComment) => {
|
||||
const commentId = postedComment.id;
|
||||
const status = postedComment.status;
|
||||
if (status[0] && status[0].type === 'rejected') {
|
||||
if (postedComment.status === 'rejected') {
|
||||
addNotification('error', lang.t('comment-post-banned-word'));
|
||||
} else if (premod === 'pre') {
|
||||
addNotification('success', lang.t('comment-post-notif-premod'));
|
||||
|
||||
@@ -18,14 +18,16 @@ export class TabBar extends React.Component {
|
||||
return (
|
||||
<div>
|
||||
<ul className={`${styles.base} ${cStyle ? styles[cStyle] : ''}`}>
|
||||
{React.Children.map(children, (child, tabId) =>
|
||||
React.cloneElement(child, {
|
||||
tabId,
|
||||
active: tabId === activeTab,
|
||||
onTabClick: this.handleClickTab,
|
||||
cStyle
|
||||
})
|
||||
)}
|
||||
{React.Children.toArray(children)
|
||||
.filter(child => !child.props.restricted)
|
||||
.map((child, tabId) =>
|
||||
React.cloneElement(child, {
|
||||
tabId,
|
||||
active: tabId === activeTab,
|
||||
onTabClick: this.handleClickTab,
|
||||
cStyle
|
||||
})
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
|
||||
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
@@ -256,7 +256,7 @@ paths:
|
||||
description: An error occured.
|
||||
schema:
|
||||
$ref: '#/definitions/Error'
|
||||
/asset:
|
||||
/assets:
|
||||
get:
|
||||
parameters:
|
||||
- name: limit
|
||||
@@ -294,7 +294,7 @@ paths:
|
||||
items:
|
||||
$ref: '#/definitions/Asset'
|
||||
|
||||
/asset/{asset_id}:
|
||||
/assets/{asset_id}:
|
||||
get:
|
||||
parameters:
|
||||
- name: asset_id
|
||||
@@ -315,7 +315,7 @@ paths:
|
||||
$ref: '#/definitions/Error'
|
||||
|
||||
|
||||
/asset/{asset_id}/scrape:
|
||||
/assets/{asset_id}/scrape:
|
||||
post:
|
||||
parameters:
|
||||
- name: asset_id
|
||||
@@ -335,7 +335,7 @@ paths:
|
||||
schema:
|
||||
$ref: '#/definitions/Error'
|
||||
|
||||
/asset/{asset_id}/settings:
|
||||
/assets/{asset_id}/settings:
|
||||
put:
|
||||
parameters:
|
||||
- name: asset_id
|
||||
@@ -1,4 +1,4 @@
|
||||
const mongoose = require('../mongoose');
|
||||
const mongoose = require('../services/mongoose');
|
||||
const uuid = require('uuid');
|
||||
const _ = require('lodash');
|
||||
const Schema = mongoose.Schema;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const mongoose = require('../mongoose');
|
||||
const mongoose = require('../services/mongoose');
|
||||
const Schema = mongoose.Schema;
|
||||
|
||||
const Setting = require('./setting');
|
||||
@@ -19,7 +19,7 @@ const AssetSchema = new Schema({
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'article'
|
||||
default: 'assets'
|
||||
},
|
||||
scraped: {
|
||||
type: Date,
|
||||
@@ -64,13 +64,6 @@ AssetSchema.index({
|
||||
background: true
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns true if the asset is closed, false else.
|
||||
*/
|
||||
AssetSchema.virtual('isClosed').get(function() {
|
||||
return this.closedAt && this.closedAt.getTime() <= new Date().getTime();
|
||||
});
|
||||
|
||||
/**
|
||||
* Finds an asset by its id.
|
||||
* @param {String} id identifier of the asset (uuid).
|
||||
@@ -83,6 +76,13 @@ AssetSchema.statics.findById = (id) => Asset.findOne({id});
|
||||
*/
|
||||
AssetSchema.statics.findByUrl = (url) => Asset.findOne({url});
|
||||
|
||||
/**
|
||||
* Returns true if the asset is closed, false else.
|
||||
*/
|
||||
AssetSchema.virtual('isClosed').get(function() {
|
||||
return this.closedAt && this.closedAt.getTime() <= new Date().getTime();
|
||||
});
|
||||
|
||||
/**
|
||||
* Retrieves the settings given an asset query and rectifies it against the
|
||||
* global settings.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const mongoose = require('../mongoose');
|
||||
const mongoose = require('../services/mongoose');
|
||||
const Schema = mongoose.Schema;
|
||||
const _ = require('lodash');
|
||||
const uuid = require('uuid');
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const mongoose = require('../mongoose');
|
||||
const mongoose = require('../services/mongoose');
|
||||
const Schema = mongoose.Schema;
|
||||
const _ = require('lodash');
|
||||
const cache = require('../cache');
|
||||
const cache = require('../services/cache');
|
||||
|
||||
/**
|
||||
* SettingSchema manages application settings that get used on front and backend.
|
||||
@@ -38,7 +38,15 @@ const SettingSchema = new Schema({
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
wordlist: [String]
|
||||
wordlist: [String],
|
||||
charCount: {
|
||||
type: Number,
|
||||
default: 5000
|
||||
},
|
||||
charCountEnable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
}, {
|
||||
timestamps: {
|
||||
createdAt: 'created_at',
|
||||
@@ -98,6 +106,11 @@ const SettingService = module.exports = {};
|
||||
*/
|
||||
const selector = {id: '1'};
|
||||
|
||||
/**
|
||||
* The list of settings that can be viewed publicly.
|
||||
*/
|
||||
const publicSettings = 'moderation infoBoxEnable infoBoxContent closeTimeout closedMessage charCountEnable charCount';
|
||||
|
||||
/**
|
||||
* Cache expiry time in seconds for when the cached entry of the settings object
|
||||
* expires. 2 minutes.
|
||||
@@ -112,6 +125,14 @@ SettingService.retrieve = () => cache.wrap('settings', EXPIRY_TIME, () => {
|
||||
return Setting.findOne(selector);
|
||||
}).then((setting) => new Setting(setting));
|
||||
|
||||
/**
|
||||
* Gets publicly available settings records
|
||||
* @return {Promise} settings the publicly viewable settings record
|
||||
*/
|
||||
SettingService.public = () => cache.wrap('publicSettings', EXPIRY_TIME, () => {
|
||||
return Setting.findOne(selector, publicSettings);
|
||||
}).then((setting) => new Setting(setting));
|
||||
|
||||
/**
|
||||
* This will update the settings object with whatever you pass in
|
||||
* @param {object} setting a hash of whatever settings you want to update
|
||||
@@ -126,9 +147,11 @@ SettingService.update = (settings) => Setting.findOneAndUpdate(selector, {
|
||||
}).then((settings) => {
|
||||
|
||||
// Invalidate the settings cache.
|
||||
return cache
|
||||
.set('settings', settings, EXPIRY_TIME)
|
||||
.then(() => settings);
|
||||
return Promise.all([
|
||||
cache.set('settings', settings, EXPIRY_TIME),
|
||||
cache.invalidate('publicSettings')
|
||||
])
|
||||
.then(() => settings);
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const mongoose = require('../mongoose');
|
||||
const mongoose = require('../services/mongoose');
|
||||
const uuid = require('uuid');
|
||||
const _ = require('lodash');
|
||||
const bcrypt = require('bcrypt');
|
||||
|
||||
@@ -5,21 +5,26 @@
|
||||
"main": "app.js",
|
||||
"scripts": {
|
||||
"start": "./bin/cli serve --jobs",
|
||||
"build": "NODE_ENV=production webpack --config webpack.config.js --bail",
|
||||
"build-watch": "NODE_ENV=development webpack --config webpack.config.dev.js --watch",
|
||||
"lint": "eslint bin/* .",
|
||||
"lint-fix": "eslint . --fix",
|
||||
"test": "NODE_ENV=test mocha --compilers js:babel-core/register --recursive tests",
|
||||
"test-watch": "NODE_ENV=test mocha --compilers js:babel-core/register --recursive -w tests",
|
||||
"pree2e": "NODE_ENV=test ./pree2e.sh",
|
||||
"e2e": "NODE_ENV=test node_modules/.bin/nightwatch",
|
||||
"build": "NODE_ENV=production ./node_modules/.bin/webpack --config webpack.config.js --bail",
|
||||
"build-watch": "NODE_ENV=development ./node_modules/.bin/webpack --config webpack.config.dev.js --watch",
|
||||
"lint": "./node_modules/.bin/eslint bin/* .",
|
||||
"lint-fix": "./node_modules/.bin/eslint bin/* . --fix",
|
||||
"test": "NODE_ENV=test ./node_modules/.bin/mocha --compilers js:babel-core/register --recursive tests",
|
||||
"test-watch": "NODE_ENV=test ./node_modules/.bin/mocha --compilers js:babel-core/register --recursive -w tests",
|
||||
"pree2e": "NODE_ENV=test ./scripts/pree2e.sh",
|
||||
"e2e": "NODE_ENV=test ./node_modules/.bin/nightwatch",
|
||||
"embed-start": "NODE_ENV=development npm run build && ./bin/cli serve --jobs"
|
||||
},
|
||||
"config": {
|
||||
"pre-git": {
|
||||
"commit-msg": [],
|
||||
"pre-commit": ["npm run lint", "npm test"],
|
||||
"pre-push": ["npm test"],
|
||||
"pre-commit": [
|
||||
"npm run lint",
|
||||
"npm test"
|
||||
],
|
||||
"pre-push": [
|
||||
"npm test"
|
||||
],
|
||||
"post-commit": [],
|
||||
"post-merge": []
|
||||
}
|
||||
@@ -28,7 +33,12 @@
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/coralproject/talk.git"
|
||||
},
|
||||
"keywords": ["talk", "coral", "coralproject", "ask"],
|
||||
"keywords": [
|
||||
"talk",
|
||||
"coral",
|
||||
"coralproject",
|
||||
"ask"
|
||||
],
|
||||
"author": "",
|
||||
"license": "Apache-2.0",
|
||||
"bugs": {
|
||||
@@ -59,7 +69,6 @@
|
||||
"passport-facebook": "^2.1.1",
|
||||
"passport-local": "^1.0.0",
|
||||
"prompt": "^1.0.0",
|
||||
"react-linkify": "^0.1.3",
|
||||
"redis": "^2.6.3",
|
||||
"uuid": "^2.0.3"
|
||||
},
|
||||
@@ -84,9 +93,10 @@
|
||||
"copy-webpack-plugin": "^4.0.0",
|
||||
"css-loader": "^0.25.0",
|
||||
"dialog-polyfill": "^0.4.4",
|
||||
"eslint": "^3.9.1",
|
||||
"eslint": "^3.12.1",
|
||||
"eslint-config-postcss": "^2.0.2",
|
||||
"eslint-config-standard": "^6.2.1",
|
||||
"eslint-module-utils": "^2.0.0",
|
||||
"eslint-plugin-flowtype": "^2.25.0",
|
||||
"eslint-plugin-import": "^2.2.0",
|
||||
"eslint-plugin-mocha": "^4.7.0",
|
||||
@@ -113,6 +123,7 @@
|
||||
"pym.js": "^1.1.1",
|
||||
"react": "15.3.2",
|
||||
"react-dom": "15.3.2",
|
||||
"react-linkify": "^0.1.3",
|
||||
"react-mdl": "^1.7.2",
|
||||
"react-mdl-selectfield": "^0.2.0",
|
||||
"react-onclickoutside": "^5.7.1",
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
node_modules/selenium-standalone/bin/selenium-standalone install
|
||||
npm start &
|
||||
@@ -104,11 +104,28 @@ router.put('/:asset_id/settings', (req, res, next) => {
|
||||
});
|
||||
|
||||
router.put('/:asset_id/status', (req, res, next) => {
|
||||
// Update the asset status
|
||||
|
||||
const id = req.params.asset_id;
|
||||
|
||||
const {
|
||||
closedAt,
|
||||
closedMessage
|
||||
} = req.body;
|
||||
|
||||
Asset
|
||||
.update({id: req.params.asset_id}, {status: req.query.status})
|
||||
.then(() => res.status(204).end())
|
||||
.catch((err) => next(err));
|
||||
.update({id}, {
|
||||
$set: {
|
||||
closedAt,
|
||||
closedMessage
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
|
||||
res.status(204).json();
|
||||
})
|
||||
.catch((err) => {
|
||||
next(err);
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -1,5 +1,5 @@
|
||||
const express = require('express');
|
||||
const passport = require('../../../passport');
|
||||
const passport = require('../../../services/passport');
|
||||
const authorization = require('../../../middleware/authorization');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@@ -87,7 +87,6 @@ router.post('/', wordlist.filter('body'), (req, res, next) => {
|
||||
|
||||
// Check to see if the asset has closed commenting...
|
||||
if (asset.isClosed) {
|
||||
|
||||
// They have, ensure that we send back an error.
|
||||
return Promise.reject(new Error(`asset has commenting closed because: ${asset.closedMessage}`));
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ const router = express.Router();
|
||||
// Filter all content going down the pipe based on user roles.
|
||||
router.use(payloadFilter);
|
||||
|
||||
router.use('/asset', authorization.needed('admin'), require('./asset'));
|
||||
router.use('/assets', authorization.needed('admin'), require('./assets'));
|
||||
router.use('/settings', authorization.needed('admin'), require('./settings'));
|
||||
router.use('/queue', authorization.needed('admin'), require('./queue'));
|
||||
|
||||
@@ -19,6 +19,6 @@ router.use('/stream', require('./stream'));
|
||||
router.use('/users', require('./users'));
|
||||
|
||||
// Bind the kue handler to the /kue path.
|
||||
router.use('/kue', authorization.needed('admin'), require('../../kue').kue.app);
|
||||
router.use('/kue', authorization.needed('admin'), require('../../services/kue').kue.app);
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@@ -38,7 +38,7 @@ router.get('/', (req, res, next) => {
|
||||
}),
|
||||
|
||||
// Get the moderation setting from the settings.
|
||||
Setting.retrieve()
|
||||
Setting.public()
|
||||
])
|
||||
.then(([asset, settings]) => {
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
# install selenium
|
||||
../node_modules/selenium-standalone/bin/selenium-standalone install
|
||||
|
||||
# start the app server
|
||||
npm start &
|
||||
@@ -1,5 +1,5 @@
|
||||
const passport = require('passport');
|
||||
const User = require('./models/user');
|
||||
const User = require('../models/user');
|
||||
const LocalStrategy = require('passport-local').Strategy;
|
||||
const FacebookStrategy = require('passport-facebook').Strategy;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const kue = require('../kue');
|
||||
const kue = require('./kue');
|
||||
const debug = require('debug')('talk:services:scraper');
|
||||
const Asset = require('../models/asset');
|
||||
const JOB_NAME = 'scraper';
|
||||
|
||||
@@ -3,7 +3,7 @@ const expect = require('chai').expect;
|
||||
|
||||
describe('models.Setting', () => {
|
||||
|
||||
beforeEach(() => Setting.init({moderation: 'pre'}));
|
||||
beforeEach(() => Setting.init({moderation: 'pre', wordlist: ['donut']}));
|
||||
|
||||
describe('#retrieve()', () => {
|
||||
it('should have a moderation field defined', () => {
|
||||
@@ -20,6 +20,20 @@ describe('models.Setting', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('#public()', () => {
|
||||
it('should have a moderation field defined', () => {
|
||||
return Setting.public().then(settings => {
|
||||
expect(settings).to.have.property('moderation').and.to.equal('pre');
|
||||
});
|
||||
});
|
||||
|
||||
it('should not have the wordlist field defined', () => {
|
||||
return Setting.public().then(settings => {
|
||||
expect(settings).to.have.property('wordlist').and.to.have.length(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#update()', () => {
|
||||
it('should update the settings with a passed object', () => {
|
||||
const mockSettings = {moderation: 'post', infoBoxEnable: true, infoBoxContent: 'yeah'};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const mongoose = require('../mongoose');
|
||||
const mongoose = require('../services/mongoose');
|
||||
|
||||
beforeEach(function (done) {
|
||||
function clearDB() {
|
||||
|
||||
@@ -17,7 +17,8 @@ describe('/api/v1/assets', () => {
|
||||
{
|
||||
url: 'https://coralproject.net/news/asset1',
|
||||
title: 'Asset 1',
|
||||
description: 'term1'
|
||||
description: 'term1',
|
||||
id: '1'
|
||||
},
|
||||
{
|
||||
url: 'https://coralproject.net/news/asset2',
|
||||
@@ -31,7 +32,7 @@ describe('/api/v1/assets', () => {
|
||||
|
||||
it('should return all assets without a search query', () => {
|
||||
return chai.request(app)
|
||||
.get('/api/v1/asset')
|
||||
.get('/api/v1/assets')
|
||||
.set(passport.inject({roles: ['admin']}))
|
||||
.then((res) => {
|
||||
const body = res.body;
|
||||
@@ -47,7 +48,7 @@ describe('/api/v1/assets', () => {
|
||||
|
||||
it('should return assets that we search for', () => {
|
||||
return chai.request(app)
|
||||
.get('/api/v1/asset?search=term2')
|
||||
.get('/api/v1/assets?search=term2')
|
||||
.set(passport.inject({roles: ['admin']}))
|
||||
.then((res) => {
|
||||
const body = res.body;
|
||||
@@ -68,7 +69,7 @@ describe('/api/v1/assets', () => {
|
||||
|
||||
it('should not return assets that we do not search for', () => {
|
||||
return chai.request(app)
|
||||
.get('/api/v1/asset?search=term3')
|
||||
.get('/api/v1/assets?search=term3')
|
||||
.set(passport.inject({roles: ['admin']}))
|
||||
.then((res) => {
|
||||
const body = res.body;
|
||||
@@ -82,4 +83,32 @@ describe('/api/v1/assets', () => {
|
||||
|
||||
});
|
||||
|
||||
describe('#put', () => {
|
||||
it('should close the asset', function() {
|
||||
|
||||
const today = Date.now();
|
||||
|
||||
return Asset.findOrCreateByUrl('http://test.com')
|
||||
.then((asset) => {
|
||||
expect(asset).to.have.property('isClosed', null);
|
||||
expect(asset).to.have.property('closedAt', null);
|
||||
|
||||
return chai.request(app)
|
||||
.put(`/api/v1/assets/${asset.id}/status`)
|
||||
.set(passport.inject({roles: ['admin']}))
|
||||
.send({closedAt: today});
|
||||
})
|
||||
.then((res) => {
|
||||
|
||||
expect(res).to.have.status(204);
|
||||
|
||||
return Asset.findByUrl('http://test.com');
|
||||
})
|
||||
.then((asset) => {
|
||||
expect(asset).to.have.property('isClosed', true);
|
||||
expect(asset).to.have.property('closedAt').and.to.not.equal(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const mongoose = require('../../mongoose');
|
||||
const mongoose = require('../../services/mongoose');
|
||||
|
||||
// Ensure the NODE_ENV is set to 'test',
|
||||
// this is helpful when you would like to change behavior when testing.
|
||||
|
||||