Merge branch 'master' of github.com:coralproject/talk into passport

This commit is contained in:
Belen Curcio
2016-11-11 16:49:28 -03:00
53 changed files with 804 additions and 239 deletions
+1 -1
View File
@@ -1 +1 @@
dist
dist
+2 -1
View File
@@ -59,6 +59,7 @@
"no-multiple-empty-lines": [
"error",
{"max": 1}
]
],
"newline-per-chained-call": ["error", { "ignoreChainWithDepth": 2 }]
}
}
+4 -2
View File
@@ -24,11 +24,13 @@ program
const Setting = require('../models/setting');
const defaults = {id: '1', moderation: 'pre'};
Setting.update({id: '1'}, {$setOnInsert: defaults}, {upsert: true})
Setting
.update({id: '1'}, {$setOnInsert: defaults}, {upsert: true})
.then(() => {
console.log('Created settings object.');
mongoose.disconnect();
}).catch((err) => {
})
.catch((err) => {
console.error(`failed to create the settings object ${JSON.stringify(err)}`);
throw new Error(err); // just to be safe
});
+6 -3
View File
@@ -71,10 +71,12 @@ function createUser(options) {
})
.then((result) => {
return User.createLocalUser(result.email.trim(), result.password.trim(), result.displayName.trim());
}).then((user) => {
})
.then((user) => {
console.log(`Created user ${user.id}.`);
mongoose.disconnect();
}).catch((err) => {
})
.catch((err) => {
console.error(err);
mongoose.disconnect();
});
@@ -249,7 +251,8 @@ function mergeUsers(dstUserID, srcUserID) {
.then(() => {
console.log(`User ${srcUserID} was merged into user ${dstUserID}.`);
mongoose.disconnect();
}).catch((err) => {
})
.catch((err) => {
console.error(err);
mongoose.disconnect();
});
@@ -1,24 +0,0 @@
{
"modqueue": {
"pending": "pending",
"rejected": "rejected",
"flagged": "flagged",
"shortcuts": "Shortcuts",
"close": "Close",
"actions": "Actions",
"navigation": "Navigation",
"approve": "Approve comment",
"reject": "Reject comment",
"nextcomment": "Go to the next comment",
"prevcomment": "Go to the previous comment",
"singleview": "Toggle single comment edit view",
"thismenu": "Open this menu"
},
"comment": {
"flagged": "flagged",
"anon": "Anonymous"
},
"embedlink": {
"copy": "Copy to Clipboard"
}
}
@@ -1,13 +0,0 @@
{
"modqueue": {
"pending": "pendiente",
"rejected": "rechazado",
"flagged": "marcado",
"shortcuts": "Atajos de teclado",
"close": "Cerrar"
},
"comment": {
"flagged": "marcado",
"anon": "Anónimo"
}
}
+1 -1
View File
@@ -4,7 +4,7 @@ import {Router, Route, IndexRoute, browserHistory} from 'react-router';
import ModerationQueue from 'containers/ModerationQueue';
import CommentStream from 'containers/CommentStream';
import Configure from 'containers/Configure';
import CommunityContainer from 'containers/CommunityContainer';
import CommunityContainer from 'containers/Community/CommunityContainer';
import LayoutContainer from 'containers/LayoutContainer';
const routes = (
@@ -0,0 +1,42 @@
import qs from 'qs';
import {
FETCH_COMMENTERS_REQUEST,
FETCH_COMMENTERS_SUCCESS,
FETCH_COMMENTERS_FAILURE,
SORT_UPDATE,
COMMENTERS_NEW_PAGE
} from '../constants/community';
import {base, getInit, handleResp} from '../helpers/response';
export const fetchCommenters = (query = {}) => dispatch => {
dispatch(requestFetchCommenters());
fetch(`${base}/user?${qs.stringify(query)}`, getInit('GET'))
.then(handleResp)
.then(({result, page, count, limit, totalPages}) =>
dispatch({
type: FETCH_COMMENTERS_SUCCESS,
commenters: result,
page,
count,
limit,
totalPages
})
)
.catch(error => dispatch({type: FETCH_COMMENTERS_FAILURE, error}));
};
const requestFetchCommenters = () => ({
type: FETCH_COMMENTERS_REQUEST
});
export const updateSorting = sort => ({
type: SORT_UPDATE,
sort
});
export const newPage = () => ({
type: COMMENTERS_NEW_PAGE
});
-12
View File
@@ -1,12 +0,0 @@
import React from 'react';
import {Layout} from 'react-mdl';
import 'material-design-lite';
import Header from 'components/Header';
export default (props) => (
<Layout>
<Header>
{props.children}
</Header>
</Layout>
);
@@ -9,6 +9,9 @@ export default () => (
<Link className={styles.navLink} to="/admin">Moderate</Link>
<Link className={styles.navLink} to="/admin/community">Community</Link>
<Link className={styles.navLink} to="/admin/configure">Configure</Link>
<span>
{`v${process.env.VERSION}`}
</span>
</Navigation>
</Header>
);
@@ -0,0 +1,4 @@
.layout {
max-width: 1170px;
margin: 0 auto;
}
@@ -2,11 +2,14 @@ import React from 'react';
import {Layout as LayoutMDL} from 'react-mdl';
import Header from './Header';
import Drawer from './Drawer';
import styles from './Layout.css';
export const Layout = ({children}) => (
<LayoutMDL fixedDrawer>
<Header />
<Drawer />
{children}
<div className={styles.layout} >
{children}
</div>
</LayoutMDL>
);
@@ -0,0 +1,5 @@
export const FETCH_COMMENTERS_REQUEST = 'FETCH_COMMENTERS_REQUEST';
export const FETCH_COMMENTERS_SUCCESS = 'FETCH_COMMENTERS_SUCCESS';
export const FETCH_COMMENTERS_FAILURE = 'FETCH_COMMENTERS_FAILURE';
export const SORT_UPDATE = 'SORT_UPDATE';
export const COMMENTERS_NEW_PAGE = 'COMMENTERS_NEW_PAGE';
@@ -0,0 +1,22 @@
.dataTable {
width: 100%;
}
.roleButton {
display: block;
}
.searchInput {
display: block;
padding-left: 40px;
/*border: none;*/
}
.searchBox {
/*border: 1px solid rgba(0,0,0,.12);*/
background: white;
}
.email {
display: block;
}
@@ -0,0 +1,68 @@
import React from 'react';
import I18n from 'coral-framework/i18n/i18n';
import translations from '../../translations';
import {Grid, Cell} from 'react-mdl';
import styles from './Community.css';
import Table from './Table';
import Loading from './Loading';
import NoResults from './NoResults';
import Pager from './Pager';
const lang = new I18n(translations);
const tableHeaders = [
{
title: lang.t('community.username_and_email'),
field: 'displayName'
},
{
title: lang.t('community.account_creation_date'),
field: 'created_at'
}
];
const Community = ({isFetching, commenters, ...props}) => {
const hasResults = !isFetching && !!commenters.length;
return (
<Grid>
<Cell col={4}>
<form action="">
<div className={`mdl-textfield ${styles.searchBox}`}>
<label className="mdl-button mdl-js-button mdl-button--icon" htmlFor="commenters-search">
<i className="material-icons">search</i>
</label>
<div className="">
<input
id="commenters-search"
className={`mdl-textfield__input ${styles.searchInput}`}
type="text"
value={props.searchValue}
onKeyDown={props.onKeyDownHandler}
onChange={props.onChangeHandler}
/>
</div>
</div>
</form>
</Cell>
<Cell col={8}>
{ isFetching && <Loading /> }
{ !hasResults && <NoResults /> }
{ hasResults &&
<Table
headers={tableHeaders}
data={commenters}
onHeaderClickHandler={props.onHeaderClickHandler}
/>
}
<Pager
totalPages={props.totalPages}
page={props.page}
onNewPageHandler={props.onNewPageHandler}
/>
</Cell>
</Grid>
);
};
export default Community;
@@ -0,0 +1,80 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {
fetchCommenters,
updateSorting,
newPage,
} from '../../actions/community';
import Community from './Community';
class CommunityContainer extends Component {
constructor(props) {
super(props);
this.state = {
searchValue: ''
};
this.onKeyDownHandler = this.onKeyDownHandler.bind(this);
this.onChangeHandler = this.onChangeHandler.bind(this);
this.onHeaderClickHandler = this.onHeaderClickHandler.bind(this);
this.onNewPageHandler = this.onNewPageHandler.bind(this);
}
onKeyDownHandler(e) {
if (e.key === 'Enter') {
e.preventDefault();
this.search();
}
}
onChangeHandler(e) {
this.setState({
searchValue: e.target.value
});
}
search(query = {}) {
const {community} = this.props;
this.props.dispatch(fetchCommenters({
value: this.state.searchValue,
field: community.get('field'),
asc: community.get('asc'),
...query
}));
}
componentDidMount() {
this.search();
}
onHeaderClickHandler(sort) {
this.props.dispatch(updateSorting(sort));
this.search();
}
onNewPageHandler(page) {
this.props.dispatch(newPage(page));
this.search({page});
}
render() {
const {searchValue} = this.state;
const {community} = this.props;
return (
<Community
searchValue={searchValue}
commenters={community.get('commenters')}
isFetching={community.get('isFetching')}
error={community.get('error')}
totalPages={community.get('totalPages')}
page={community.get('page')}
{...this}
/>
);
}
}
export default connect(({community}) => ({community}))(CommunityContainer);
@@ -0,0 +1,7 @@
import React from 'react';
const Loading = () => (
<h1> Loading results </h1>
);
export default Loading;
@@ -0,0 +1,9 @@
import React from 'react';
const NoResults = () => (
<div>
No users found with that user name or email address
</div>
);
export default NoResults;
@@ -0,0 +1,10 @@
.li {
display: inline-block;
margin-right: 5px;
padding: 0;
min-width: 30px;
}
.current {
background: #e3edf3;
}
@@ -0,0 +1,45 @@
import React, {PropTypes} from 'react';
import styles from './Pager.css';
const Rows = (curr, total, onClickHandler) => Array.from(Array(total)).map((e, i) =>
<li className={`mdl-button mdl-js-button ${styles.li} ${curr === i ? styles.current : ''}`}
key={i} onClick={() => onClickHandler(i + 1)}>
{i + 1}
</li>
);
const Pager = ({totalPages, page, onNewPageHandler}) => (
<div className="pager">
<ul>
{
(totalPages > page) ?
<li
className={`mdl-button mdl-js-button ${styles.li}`}
onClick={() => onNewPageHandler(page - 1)}>
Prev
</li>
:
null
}
{Rows(page, totalPages, onNewPageHandler)}
{
(page < totalPages) ?
<li
className={`mdl-button mdl-js-button ${styles.li}`}
onClick={() => onNewPageHandler(page + 1)}>
Next
</li>
:
null
}
</ul>
</div>
);
Pager.propTypes = {
totalPages: PropTypes.number.isRequired,
page: PropTypes.number.isRequired,
};
export default Pager;
@@ -0,0 +1,34 @@
import React from 'react';
import styles from './Community.css';
const Table = ({headers, data, onHeaderClickHandler}) => (
<table className={`mdl-data-table ${styles.dataTable}`}>
<thead>
<tr>
{headers.map((header, i) =>(
<th
key={i}
className="mdl-data-table__cell--non-numeric"
onClick={() => onHeaderClickHandler({field: header.field})}>
{header.title}
</th>
))}
</tr>
</thead>
<tbody>
{data.map((row, i)=> (
<tr key={i}>
<td className="mdl-data-table__cell--non-numeric">
{row.displayName}
<span className={styles.email}>{row.profiles.map(({id}) => id)}</span>
</td>
<td className="mdl-data-table__cell--non-numeric">
{row.created_at}
</td>
</tr>
))}
</tbody>
</table>
);
export default Table;
@@ -1,11 +0,0 @@
import React, {Component} from 'react';
export default class CommunityContainer extends Component {
render() {
return (
<div>
<h1>Community</h1>
</div>
);
}
}
@@ -1,18 +1,19 @@
import React, {Component}from 'react';
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {Layout} from '../components/ui/Layout';
class LayoutContainer extends Component {
render () {
return <Layout { ...this.props } />;
return <Layout {...this.props} />;
}
}
LayoutContainer.propTypes = {};
const mapStateToProps = () => ({data: {}});
const mapStateToProps = () => ({});
const mapDispatchToProps = (dispatch) => ({dispatch});
export default connect(mapStateToProps, mapDispatchToProps)(LayoutContainer);
@@ -73,7 +73,12 @@ class ModerationQueue extends React.Component {
<CommentList
isActive={activeTab === 'pending'}
singleView={singleView}
commentIds={comments.get('ids').filter(id => !comments.get('byId').get(id).get('status'))}
commentIds={
comments.get('ids')
.filter(id => !comments.get('byId')
.get(id)
.get('status'))
}
comments={comments.get('byId')}
onClickAction={(action, id) => this.onCommentAction(action, id)}
actions={['reject', 'approve']}
@@ -83,7 +88,15 @@ class ModerationQueue extends React.Component {
<CommentList
isActive={activeTab === 'rejected'}
singleView={singleView}
commentIds={comments.get('ids').filter(id => comments.get('byId').get(id).get('status') === 'rejected')}
commentIds={
comments
.get('ids')
.filter(id =>
comments
.get('byId')
.get(id)
.get('status') === 'rejected')
}
comments={comments.get('byId')}
onClickAction={(action, id) => this.onCommentAction(action, id)}
actions={['approve']}
@@ -0,0 +1,27 @@
export const base = '/api/v1';
export const getInit = (method, body) => {
const headers = new Headers({
'Content-Type': 'application/json',
'Accept': 'application/json'
});
const init = {method, headers};
if (method.toLowerCase() !== 'get') {
init.body = JSON.stringify(body);
}
return init;
};
export const handleResp = res => {
if (res.status === 401) {
throw new Error('Not Authorized to make this request');
} else if (res.status > 399) {
throw new Error('Error! Status ', res.status);
} else if (res.status === 204) {
return res.text();
} else {
return res.json();
}
};
-1
View File
@@ -1,4 +1,3 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App';
@@ -0,0 +1,47 @@
import {Map} from 'immutable';
import {
FETCH_COMMENTERS_REQUEST,
FETCH_COMMENTERS_FAILURE,
FETCH_COMMENTERS_SUCCESS,
SORT_UPDATE
} from '../constants/community';
const initialState = Map({
community: Map(),
isFetching: false,
error: '',
commenters: [],
field: 'created_at',
asc: false,
totalPages: 0,
page: 0
});
export default function community (state = initialState, action) {
switch (action.type) {
case FETCH_COMMENTERS_REQUEST :
return state
.set('isFetching', true);
case FETCH_COMMENTERS_FAILURE :
return state
.set('isFetching', false)
.set('error', action.error);
case FETCH_COMMENTERS_SUCCESS : {
const {commenters, type, ...rest} = action; // eslint-disable-line
return state
.merge({
isFetching: false,
error: '',
...rest
})
.set('commenters', commenters); // Sets to normal array
}
case SORT_UPDATE :
return state
.set('field', action.sort.field)
.set('asc', !state.get('asc'));
default :
return state;
}
}
+4 -2
View File
@@ -1,10 +1,12 @@
import {combineReducers} from 'redux';
import comments from 'reducers/comments';
import settings from 'reducers/settings';
import community from 'reducers/community';
// Combine all reducers into a main one
export default combineReducers({
settings,
comments
comments,
community
});
+8
View File
@@ -1,5 +1,9 @@
export default {
en: {
'community': {
username_and_email: 'Username and Email',
account_creation_date: 'Account Creation Date'
},
'modqueue': {
'pending': 'pending',
'rejected': 'rejected',
@@ -24,6 +28,10 @@ export default {
}
},
es: {
'community': {
username_and_email: 'Usuario y E-mail',
account_creation_date: 'Fecha de creación de la cuenta'
},
'modqueue': {
'pending': 'pendiente',
'rejected': 'rechazado',
@@ -108,6 +108,7 @@ class CommentStream extends Component {
appendItemArray={this.props.appendItemArray}
updateItem={this.props.updateItem}
id={rootItemId}
premod={this.props.config.moderation}
reply={false}/>
</div>
{
@@ -138,6 +139,7 @@ class CommentStream extends Component {
updateItem={this.props.updateItem}
id={rootItemId}
parent_id={commentId}
premod={this.props.config.moderation}
showReply={comment.showReply}/>
{
comment.children &&
@@ -168,7 +170,7 @@ class CommentStream extends Component {
})
}
<Notification
notifLength={this.props.config.notifLength}
notifLength={4500}
clearNotification={this.props.clearNotification}
notification={this.props.notification}/>
</div>
+20 -13
View File
@@ -14,17 +14,24 @@ export const FETCH_CONFIG_SUCCESS = 'FETCH_CONFIG_SUCCESS';
* Action creators
*/
export const fetchConfig = () => async (dispatch) => {
dispatch({type: FETCH_CONFIG_REQUEST});
export function fetchConfig () {
return (dispatch) => {
dispatch({type: FETCH_CONFIG_REQUEST});
try {
//TODO: Replace with fetching config from backend
// const response = await fetch(`./talk.config.json`)
// const json = await response.json()
dispatch({type: FETCH_CONFIG_SUCCESS, config: fromJS({
notifLength: 4500
})});
} catch (error) {
dispatch({type: FETCH_CONFIG_FAILED});
}
};
return fetch('/api/v1/settings')
.then(
response => {
return response.ok ? response.json()
: Promise.reject(`${response.status} ${response.statusText}`);
}
)
.then((json) => {
return dispatch({type: FETCH_CONFIG_SUCCESS, config: fromJS(json)});
})
.catch((error) => {
dispatch({type: FETCH_CONFIG_FAILED, error});
});
};
}
@@ -148,7 +148,7 @@ export function getStream (assetId) {
export function getItemsArray (ids) {
return (dispatch) => {
return fetch(`/v1/item/${ ids}`)
return fetch(`/v1/item/${ids}`)
.then(
response => {
return response.ok ? response.json()
@@ -241,7 +241,8 @@ export function postAction (item_id, action_type, user_id, item_type) {
return response.ok ? response.json()
: Promise.reject(`${response.status} ${response.statusText}`);
}
).then((json)=>{
)
.then((json)=>{
return json;
});
};
@@ -2,7 +2,7 @@ import React from 'react';
const packagename = 'coral-plugin-author-name';
const AuthorName = ({name}) =>
<div className={`${packagename }-text`}>
<div className={`${packagename}-text`}>
{name}
</div>;
@@ -1,5 +1,6 @@
import React from 'react';
import {I18n} from '../coral-framework';
import translations from './translations.json';
const name = 'coral-plugin-comment-count';
const CommentCount = ({items, id}) => {
@@ -15,9 +16,11 @@ const CommentCount = ({items, id}) => {
}
}
return <div className={`${name }-text`}>
{`${count } ${ count === 1 ? 'Comment' : 'Comments'}`}
return <div className={`${name}-text`}>
{`${count} ${count === 1 ? lang.t('comment') : lang.t('comment-plural')}`}
</div>;
};
export default CommentCount;
const lang = new I18n(translations);
@@ -0,0 +1,10 @@
{
"en": {
"comment": "Comment",
"comment-plural": "Comments"
},
"es": {
"comment": "Comentario",
"comment-plural": "Comentarios"
}
}
+13 -18
View File
@@ -1,5 +1,6 @@
import React, {Component, PropTypes} from 'react';
import {I18n} from '../coral-framework';
import translations from './translations.json';
const name = 'coral-plugin-commentbox';
@@ -19,7 +20,7 @@ class CommentBox extends Component {
}
postComment = () => {
const {postItem, updateItem, id, parent_id, addNotification, appendItemArray} = this.props;
const {postItem, updateItem, id, parent_id, addNotification, appendItemArray, premod} = this.props;
let comment = {
body: this.state.body,
asset_id: id,
@@ -38,9 +39,14 @@ class CommentBox extends Component {
updateItem(parent_id, 'showReply', false, 'comments');
postItem(comment, 'comments')
.then((comment_id) => {
appendItemArray(parent_id || id, related, comment_id, !parent_id, parent_type);
addNotification('success', 'Your comment has been posted.');
}).catch((err) => console.error(err));
if (premod === 'pre') {
addNotification('success', lang.t('comment-post-notif-premod'));
} else {
appendItemArray(parent_id || id, related, comment_id, !parent_id, parent_type);
addNotification('success', 'Your comment has been posted.');
}
})
.catch((err) => console.error(err));
this.setState({body: ''});
}
@@ -54,7 +60,7 @@ class CommentBox extends Component {
style={styles && styles.textarea}
value={this.state.username}
id={reply ? 'replyUser' : 'commentUser'}
placeholder='Name'
placeholder={lang.t('name')}
onChange={(e) => this.setState({username: e.target.value})}/>
</div>
<div
@@ -69,7 +75,7 @@ class CommentBox extends Component {
className={`${name}-textarea`}
style={styles && styles.textarea}
value={this.state.body}
placeholder='Comment'
placeholder={lang.t('comment')}
id={reply ? 'replyText' : 'commentText'}
onChange={(e) => this.setState({body: e.target.value})}
rows={3}/>
@@ -88,15 +94,4 @@ class CommentBox extends Component {
export default CommentBox;
const lang = new I18n({
en: {
post: 'Post',
reply: 'Reply',
comment: 'Comment',
},
es: {
post: 'Publicar',
reply: 'Respuesta',
comment: 'Comentario'
}
});
const lang = new I18n(translations);
@@ -0,0 +1,18 @@
{
"en": {
"post": "Post",
"reply": "Reply",
"comment": "Comment",
"name": "Name",
"comment-post-notif": "Your comment has been posted.",
"comment-post-notif-premod": "Thank you for reporting this comment. Our moderation team has been notified and will review it shortly."
},
"es": {
"post": "Publicar",
"reply": "Respuesta",
"comment": "Comentario",
"name": "Nombre",
"comment-post-notif": "¡traduceme!",
"comment-post-notif-premod": "¡traduceme!"
}
}
+7 -3
View File
@@ -1,4 +1,6 @@
import React from 'react';
import {I18n} from '../coral-framework';
import translations from './translations.json';
const name = 'coral-plugin-flags';
@@ -10,7 +12,7 @@ const FlagButton = ({flag, id, postAction, addItem, updateItem, addNotification}
addItem({...action, current_user:true}, 'actions');
updateItem(action.item_id, action.action_type, action.id, 'comments');
});
addNotification('success', 'Thank you for reporting this comment. Our moderation team has been notified and will review it shortly.');
addNotification('success', lang.t('flag-notif'));
};
return <div className={`${name }-container`}>
@@ -20,8 +22,8 @@ const FlagButton = ({flag, id, postAction, addItem, updateItem, addNotification}
aria-hidden={true}>flag</i>
{
flagged
? <span className={`${name }-button-text`}>Flagged</span>
: <span className={`${name }-button-text`}>Flag</span>
? <span className={`${name}-button-text`}>{lang.t('flag')}</span>
: <span className={`${name}-button-text`}>{lang.t('flagged')}</span>
}
</button>
</div>;
@@ -37,3 +39,5 @@ const styles = {
color: 'inherit'
}
};
const lang = new I18n(translations);
@@ -0,0 +1,12 @@
{
"en": {
"flag": "Flag",
"flagged": "Flagged",
"flag-notif": "Thank you for reporting this comment. Our moderation team has been notified and will review it shortly."
},
"es": {
"flag": "Marcar",
"flagged": "Marcado",
"flag-notif": "Gracias por marcar este comentario. Nuestro equipo de moderación ha sido notificado y muy pronto lo va a revisar."
}
}
+2 -7
View File
@@ -4,16 +4,11 @@ import CommentBox from '../coral-plugin-commentbox/CommentBox';
const name = 'coral-plugin-replies';
const ReplyBox = (props) => <div
className={`${name }-textarea`}
className={`${name}-textarea`}
style={props.styles && props.styles.container}>
{
props.showReply && <CommentBox
id = {props.id}
parent_id = {props.parent_id}
postItem = {props.postItem}
addNotification = {props.addNotification}
appendItemArray = {props.appendItemArray}
updateItem = {props.updateItem}
{...props}
comments = {props.child}
reply = {true}/>
}
+2 -8
View File
@@ -1,5 +1,6 @@
import React from 'react';
import {I18n} from '../coral-framework';
import translations from './translations.json';
const name = 'coral-plugin-replies';
@@ -13,11 +14,4 @@ const ReplyButton = (props) => <button
export default ReplyButton;
const lang = new I18n({
en: {
'reply': 'Reply'
},
es: {
'reply': '¡traduceme!'
}
});
const lang = new I18n(translations);
@@ -0,0 +1,8 @@
{
"en": {
"reply": "Reply"
},
"es": {
"reply": "Responder"
}
}
+21 -6
View File
@@ -86,9 +86,13 @@ CommentSchema.statics.findAcceptedAndNewByAssetId = function(asset_id) {
* @param {String} action_type the type of action that was performed on the comment
*/
CommentSchema.statics.findByActionType = function(action_type) {
return Action.findCommentsIdByActionType(action_type, 'comment').then((actions) => {
return Comment.find({'id': {'$in': actions.map(function(a){return a.item_id;})}});
});
return Action
.findCommentsIdByActionType(action_type, 'comment')
.then((actions) => {
return Comment.find({'id': {'$in': actions.map(function(a){
return a.item_id;})}
});
});
};
/**
@@ -97,9 +101,20 @@ CommentSchema.statics.findByActionType = function(action_type) {
* @param {String} status the status of the comment to search for
*/
CommentSchema.statics.findByStatusByActionType = function(status, action_type) {
return Action.findCommentsIdByActionType(action_type, 'comment').then((actions) => {
return Comment.find({'status': status, 'id': {'$in': actions.map(function(a){return a.item_id;})}});
});
return Action
.findCommentsIdByActionType(action_type, 'comment')
.then((actions) => {
return Comment.find({
'status': status,
'id': {
'$in': actions.map(a => {
return a.item_id;
})
}
});
});
};
/**
+5 -4
View File
@@ -20,7 +20,7 @@ SettingSchema.statics.init = function (defaults) {
};
/**
* gets the entire settings record and sends it back
* Gets the entire settings record and sends it back
* @return {Promise} settings the whole settings record
*/
SettingSchema.statics.getSettings = function () {
@@ -28,7 +28,7 @@ SettingSchema.statics.getSettings = function () {
};
/**
* gets the moderation settings and sends it back
* Gets the moderation settings and sends it back
* @return {Promise} moderation the settings for how to moderate comments
*/
SettingSchema.statics.getModerationSetting = function () {
@@ -36,12 +36,13 @@ SettingSchema.statics.getModerationSetting = function () {
};
/**
* this will update the settings object with whatever you pass in
* This will update the settings object with whatever you pass in
* @param {object} setting a hash of whatever settings you want to update
* @return {Promise} settings Promise that resolves to the entire (updated) settings object.
*/
SettingSchema.statics.updateSettings = function (setting) {
// there should only ever be one record unless something has gone wrong.
// There should only ever be one record unless something has gone wrong.
// In the future we may have multiple records for custom settings for objects/users.
return this.findOneAndUpdate({id: '1'}, {$set: setting}, {new: true});
};
+59 -34
View File
@@ -25,6 +25,11 @@ const UserSchema = new mongoose.Schema({
}
}],
roles: [String]
}, {
timestamps: {
createdAt: 'created_at',
updatedAt: 'updated_at'
}
});
// Add the indixies on the user profile data.
@@ -52,6 +57,22 @@ UserSchema.options.toJSON.transform = (doc, ret, options) => {
return ret;
};
/**
* toObject overrides to remove the password field from the toObject
* output.
*/
UserSchema.options.toObject = {};
UserSchema.options.toObject.hide = 'password';
UserSchema.options.toObject.transform = (doc, ret, options) => {
if (options.hide) {
options.hide.split(' ').forEach((prop) => {
delete ret[prop];
});
}
return ret;
};
/**
* Finds a user given their email address that we have for them in the system
* and ensures that the retuned user matches the password passed in as well.
@@ -100,19 +121,22 @@ UserSchema.statics.findLocalUser = function(email, password) {
UserSchema.statics.mergeUsers = function(dstUserID, srcUserID) {
let srcUser, dstUser;
return Promise.all([
User.findOne({id: dstUserID}).exec(),
User.findOne({id: srcUserID}).exec()
]).then((users) => {
dstUser = users[0];
srcUser = users[1];
return Promise
.all([
User.findOne({id: dstUserID}).exec(),
User.findOne({id: srcUserID}).exec()
])
.then((users) => {
dstUser = users[0];
srcUser = users[1];
srcUser.profiles.forEach((profile) => {
dstUser.profiles.push(profile);
});
srcUser.profiles.forEach((profile) => {
dstUser.profiles.push(profile);
});
return srcUser.remove();
}).then(() => dstUser.save());
return srcUser.remove();
})
.then(() => dstUser.save());
};
/**
@@ -122,33 +146,34 @@ UserSchema.statics.mergeUsers = function(dstUserID, srcUserID) {
* @param {Function} done [description]
*/
UserSchema.statics.findOrCreateExternalUser = function(profile) {
return User.findOne({
profiles: {
$elemMatch: {
id: profile.id,
provider: profile.provider
}
}
})
.then((user) => {
if (user) {
return user;
}
// The user was not found, lets create them!
user = new User({
displayName: profile.displayName,
roles: [],
profiles: [
{
return User
.findOne({
profiles: {
$elemMatch: {
id: profile.id,
provider: profile.provider
}
]
});
}
})
.then((user) => {
if (user) {
return user;
}
return user.save();
});
// The user was not found, lets create them!
user = new User({
displayName: profile.displayName,
roles: [],
profiles: [
{
id: profile.id,
provider: profile.provider
}
]
});
return user.save();
});
};
UserSchema.statics.changePassword = function(id, password) {
+77 -56
View File
@@ -12,17 +12,17 @@ const router = express.Router();
router.get('/', (req, res, next) => {
Comment.find({}).then((comments) => {
res.status(200).json(comments);
}).catch(error => {
next(error);
});
})
.catch(next);
});
router.get('/:comment_id', (req, res, next) => {
Comment.findById(req.params.comment_id).then((comment) => {
res.status(200).json(comment);
}).catch(error => {
next(error);
});
Comment
.findById(req.params.comment_id)
.then(comment => {
res.status(200).json(comment);
})
.catch(next);
});
//==============================================================================
@@ -31,20 +31,22 @@ router.get('/:comment_id', (req, res, next) => {
// Get all the comments that have that action_type over them.
router.get('/action/:action_type', (req, res, next) => {
Comment.findByActionType(req.params.action_type).then((comments) => {
res.status(200).json(comments);
}).catch(error => {
next(error);
});
Comment
.findByActionType(req.params.action_type)
.then((comments) => {
res.status(200).json(comments);
})
.catch(next);
});
// Get all the comments that were rejected.
router.get('/status/rejected', (req, res, next) => {
Comment.findByStatus('rejected').then((comments) => {
res.status(200).json(comments);
}).catch(error => {
next(error);
});
Comment
.findByStatus('rejected')
.then(comments => {
res.status(200).json(comments);
})
.catch(next);
});
// Returns back all the comments that are in the moderation queue. The moderation queue is pre or post moderated,
@@ -52,17 +54,21 @@ router.get('/status/rejected', (req, res, next) => {
// Pre-moderation: New comments are shown in the moderator queues immediately.
// Post-moderation: New comments do not appear in moderation queues unless they are flagged by other users.
router.get('/status/pending', (req, res, next) => {
Setting.getModerationSetting().then(function({moderation}){
let moderationValue = req.query.moderation;
if (typeof moderationValue === 'undefined' || moderationValue === undefined) {
moderationValue = moderation;
}
Comment.moderationQueue(moderationValue).then((comments) => {
res.status(200).json(comments);
});
}).catch(error => {
next(error);
});
Setting
.getModerationSetting()
.then(({moderation}) => {
let moderationValue = req.query.moderation;
if (typeof moderationValue === 'undefined' || moderationValue === undefined) {
moderationValue = moderation;
}
Comment
.moderationQueue(moderationValue)
.then((comments) => {
res.status(200).json(comments);
});
})
.catch(next);
});
//==============================================================================
@@ -70,27 +76,36 @@ router.get('/status/pending', (req, res, next) => {
//==============================================================================
router.post('/', (req, res, next) => {
const {body, author_id, asset_id, parent_id, status, username} = req.body;
Comment.new(body, author_id, asset_id, parent_id, status, username).then((comment) => {
res.status(200).send({'id': comment.id});
}).catch(error => {
next(error);
});
Comment
.new(body, author_id, asset_id, parent_id, status, username)
.then((comment) => {
res.status(200).send({'id': comment.id});
})
.catch(error => {
next(error);
});
});
router.post('/:comment_id', (req, res, next) => {
Comment.findById(req.params.comment_id).then((comment) => {
comment.body = req.body.body;
comment.author_id = req.body.author_id;
comment.asset_id = req.body.asset_id;
comment.parent_id = req.body.parent_id;
comment.status = req.body.status;
return comment.save();
}).then((comment) => {
res.status(200).send(comment);
}).catch(error => {
next(error);
});
Comment
.findById(req.params.comment_id)
.then((comment) => {
comment.body = req.body.body;
comment.author_id = req.body.author_id;
comment.asset_id = req.body.asset_id;
comment.parent_id = req.body.parent_id;
comment.status = req.body.status;
return comment.save();
})
.then((comment) => {
res.status(200).send(comment);
})
.catch(error => {
next(error);
});
});
router.post('/:comment_id/status', (req, res, next) => {
@@ -103,11 +118,14 @@ router.post('/:comment_id/status', (req, res, next) => {
});
router.post('/:comment_id/actions', (req, res, next) => {
Comment.addAction(req.params.comment_id, req.body.user_id, req.body.action_type).then((action) => {
res.status(200).send(action);
}).catch(error => {
next(error);
});
Comment
.addAction(req.params.comment_id, req.body.user_id, req.body.action_type)
.then((action) => {
res.status(200).send(action);
})
.catch(error => {
next(error);
});
});
//==============================================================================
@@ -115,11 +133,14 @@ router.post('/:comment_id/actions', (req, res, next) => {
//==============================================================================
router.delete('/:comment_id', (req, res, next) => {
Comment.removeById(req.params.comment_id).then(() => {
res.status(201).send('OK. Removed');
}).catch(error => {
next(error);
});
Comment
.removeById(req.params.comment_id)
.then(() => {
res.status(201).send('OK. Removed');
})
.catch(error => {
next(error);
});
});
module.exports = router;
+1
View File
@@ -7,5 +7,6 @@ router.use('/auth', require('./auth'));
router.use('/comments', require('./comments'));
router.use('/settings', require('./settings'));
router.use('/stream', require('./stream'));
router.use('/user', require('./user'));
module.exports = router;
+8 -2
View File
@@ -3,11 +3,17 @@ const router = express.Router();
const Setting = require('../../../models/setting');
router.get('/', (req, res, next) => {
Setting.getSettings().then(settings => res.json(settings)).catch(next);
Setting
.getSettings()
.then(settings => res.json(settings))
.catch(next);
});
router.put('/', (req, res, next) => {
Setting.updateSettings(req.body).then(() => res.status(204).end()).catch(next);
Setting
.updateSettings(req.body)
.then(() => res.status(204).end())
.catch(next);
});
module.exports = router;
+2 -1
View File
@@ -36,7 +36,8 @@ router.get('/', (req, res, next) => {
users,
actions
});
}).catch(error => {
})
.catch(error => {
next(error);
});
});
+63
View File
@@ -0,0 +1,63 @@
const express = require('express');
const router = express.Router();
const User = require('../../../models/user');
router.get('/', (req, res, next) => {
const {
value = '',
field = 'created_at',
page = 1,
asc = 'false',
limit = 50 // Total Per Page
} = req.query;
let q = {
$or: [
{
'displayName': {
$regex: new RegExp(`^${value}`),
$options: 'i'
},
'profiles': {
$elemMatch: {
id: {
$regex: new RegExp(`^${value}`),
$options: 'i'
},
provider: 'local'
}
}
}
]
};
Promise.all([
User.find(q)
.sort({[field]: (asc === 'true') ? 1 : -1})
.skip((page - 1) * limit)
.limit(limit),
User.count()
])
.then(([data, count]) => {
const users = data.map((user) => {
const {displayName, created_at} = user;
return {
displayName,
created_at,
profiles: user.toObject().profiles
};
});
res.json({
result: users,
limit: Number(limit),
count,
page: Number(page),
totalPages: Math.ceil(count / limit)
});
})
.catch(next);
});
module.exports = router;
+4 -2
View File
@@ -43,11 +43,13 @@ describe('update settings', () => {
return Setting.getSettings();
}).then(settings => {
})
.then(settings => {
// confirm updated settings in db
expect(settings).to.have.property('moderation');
expect(settings.moderation).to.equal('post');
}).catch(err => {
})
.catch(err => {
throw err;
});
});
+6
View File
@@ -82,6 +82,12 @@ module.exports = {
precss,
new webpack.ProvidePlugin({
'fetch': 'imports?this=>global!exports?global.fetch!whatwg-fetch'
}),
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': `"${'development'}"`,
'VERSION': `"${require('./package.json').version}"`
}
})
],
resolve: {
+1 -1
View File
@@ -8,7 +8,7 @@ devConfig.plugins = devConfig.plugins.concat([
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': `"${'production'}"`,
'VERSION': `"${require('./package.json')}"`
'VERSION': `"${require('./package.json').version}"`
}
}),
new webpack.optimize.UglifyJsPlugin({