From 8664a16bfc5e4603f675e4c7e7ff19b771895989 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Fri, 3 Feb 2017 16:05:27 -0300 Subject: [PATCH 01/28] modQueue Query --- .../src/graphql/queries/modQueueQuery.graphql | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 client/coral-admin/src/graphql/queries/modQueueQuery.graphql diff --git a/client/coral-admin/src/graphql/queries/modQueueQuery.graphql b/client/coral-admin/src/graphql/queries/modQueueQuery.graphql new file mode 100644 index 000000000..e26e440f8 --- /dev/null +++ b/client/coral-admin/src/graphql/queries/modQueueQuery.graphql @@ -0,0 +1,20 @@ +query ModQueue { + flagged_comments: comments(query: { + action_type: FLAG, + asset_id: $asset_id + }) { + id + } + accepted_comments: comments(query: { + statuses: [ACCEPTED], + asset_id: $asset_id + }) { + id + } + rejected_comments: comments(query: { + statuses: [REJECTED], + asset_id: $asset_id + }) { + id + } +} From c837e89b380fe03cbfd53a0480895e0bb6941420 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Tue, 7 Feb 2017 13:43:17 -0300 Subject: [PATCH 02/28] wip --- client/coral-admin/src/actions/moderation.js | 5 + .../coral-admin/src/constants/moderation.js | 3 + .../ModerationQueue/ModerationContainer.js | 97 ++++++++----------- client/coral-admin/src/reducers/index.js | 26 ++--- client/coral-admin/src/reducers/moderation.js | 24 +++++ 5 files changed, 89 insertions(+), 66 deletions(-) create mode 100644 client/coral-admin/src/actions/moderation.js create mode 100644 client/coral-admin/src/constants/moderation.js create mode 100644 client/coral-admin/src/reducers/moderation.js diff --git a/client/coral-admin/src/actions/moderation.js b/client/coral-admin/src/actions/moderation.js new file mode 100644 index 000000000..25a9aed73 --- /dev/null +++ b/client/coral-admin/src/actions/moderation.js @@ -0,0 +1,5 @@ +import * as actions from 'constants/moderation'; + +export const setActiveTab = activeTab => ({type: actions.SET_ACTIVE_TAB, activeTab}); +export const toggleModal = open => ({type: actions.TOGGLE_MODAL, open}); +export const singleView = open => ({type: actions.SINGLE_VIEW}); diff --git a/client/coral-admin/src/constants/moderation.js b/client/coral-admin/src/constants/moderation.js new file mode 100644 index 000000000..1ef27eddd --- /dev/null +++ b/client/coral-admin/src/constants/moderation.js @@ -0,0 +1,3 @@ +export const SET_ACTIVE_TAB = 'SET_ACTIVE_TAB'; +export const TOGGLE_MODAL = 'TOGGLE_MODAL'; +export const SINGLE_VIEW = 'SINGLE_VIEW' diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js index d1d1afd53..095321e42 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js +++ b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js @@ -11,32 +11,23 @@ import { fetchFlaggedQueue, fetchModerationQueueComments, } from 'actions/comments'; -import {userStatusUpdate, sendNotificationEmail} from 'actions/users'; + import {fetchSettings} from 'actions/settings'; +import {userStatusUpdate, sendNotificationEmail} from 'actions/users'; +import {setActiveTab, toggleModal, singleView} from 'actions/moderation'; import ModerationQueue from './ModerationQueue'; class ModerationContainer extends React.Component { - - constructor(props) { - super(props); - - this.state = { - activeTab: 'all', - singleView: false, - modalOpen: false - }; - - this.onClose = this.onClose.bind(this); - this.onTabClick = this.onTabClick.bind(this); - } - componentWillMount() { + const {toggleModal, singleView} = this.props; + this.props.fetchModerationQueueComments(); this.props.fetchSettings(); - key('s', () => this.setState({singleView: !this.state.singleView})); - key('shift+/', () => this.setState({modalOpen: true})); - key('esc', () => this.setState({modalOpen: false})); + + key('s', () => singleView()); + key('shift+/', () => toggleModal(true)); + key('esc', () => toggleModal(false)); } componentWillUnmount() { @@ -45,18 +36,9 @@ class ModerationContainer extends React.Component { key.unbind('esc'); } - componentDidMount() { - - // Hack for dynamic mdl tabs - if (typeof componentHandler !== 'undefined') { - - // FIXME: fix this hack - componentHandler.upgradeAllRegistered(); // eslint-disable-line no-undef - } - } - - onTabClick(activeTab) { - this.setState({activeTab}); + onTabClick = (activeTab) => { + const {setActiveTab} = this.props; + setActiveTab(activeTab); if (activeTab === 'premod') { this.props.fetchPremodQueue(); @@ -69,12 +51,15 @@ class ModerationContainer extends React.Component { } } - onClose() { - this.setState({modalOpen: false}); + onClose = () => { + const {toggleModal} = this.props; + toggleModal(false); } render () { - const {comments, actions, settings} = this.props; + const {comments, actions, settings, moderation} = this.props; + + // Remove all this filters 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 => @@ -97,37 +82,41 @@ class ModerationContainer extends React.Component { rejectedIds={rejectedIds} flaggedIds={flaggedIds} {...this.props} - {...this.state} + {...moderation} /> ); } } const mapStateToProps = state => ({ + moderation: state.moderation.toJS(), comments: state.comments.toJS(), settings: state.settings.toJS(), users: state.users.toJS(), - actions: state.actions.toJS(), + actions: state.actions.toJS() }); -const mapDispatchToProps = dispatch => { - return { - fetchSettings: () => dispatch(fetchSettings()), - fetchModerationQueueComments: () => dispatch(fetchModerationQueueComments()), - fetchPremodQueue: () => dispatch(fetchPremodQueue()), - fetchRejectedQueue: () => dispatch(fetchRejectedQueue()), - fetchFlaggedQueue: () => dispatch(fetchFlaggedQueue()), - showBanUserDialog: (userId, userName, commentId) => dispatch(showBanUserDialog(userId, userName, commentId)), - hideBanUserDialog: () => dispatch(hideBanUserDialog(false)), - userStatusUpdate: (status, userId, commentId) => dispatch(userStatusUpdate(status, userId, commentId)).then(() => { - dispatch(fetchModerationQueueComments()); - }), - suspendUser: (userId, subject, text) => dispatch(userStatusUpdate('suspended', userId)) - .then(() => dispatch(sendNotificationEmail(userId, subject, text))) - .then(() => dispatch(fetchModerationQueueComments())) - , - updateStatus: (action, comment) => dispatch(updateStatus(action, comment)) - }; -}; +const mapDispatchToProps = dispatch => ({ + setActiveTab: tab => dispatch(setActiveTab(tab)), + toggleModal: open => dispatch(toggleModal(open)), + singleView: () => dispatch(singleView()), + + + fetchSettings: () => dispatch(fetchSettings()), + fetchModerationQueueComments: () => dispatch(fetchModerationQueueComments()), + fetchPremodQueue: () => dispatch(fetchPremodQueue()), + fetchRejectedQueue: () => dispatch(fetchRejectedQueue()), + fetchFlaggedQueue: () => dispatch(fetchFlaggedQueue()), + showBanUserDialog: (userId, userName, commentId) => dispatch(showBanUserDialog(userId, userName, commentId)), + hideBanUserDialog: () => dispatch(hideBanUserDialog(false)), + userStatusUpdate: (status, userId, commentId) => dispatch(userStatusUpdate(status, userId, commentId)).then(() => { + dispatch(fetchModerationQueueComments()); + }), + suspendUser: (userId, subject, text) => dispatch(userStatusUpdate('suspended', userId)) + .then(() => dispatch(sendNotificationEmail(userId, subject, text))) + .then(() => dispatch(fetchModerationQueueComments())) + , + updateStatus: (action, comment) => dispatch(updateStatus(action, comment)) +}); export default connect(mapStateToProps, mapDispatchToProps)(ModerationContainer); diff --git a/client/coral-admin/src/reducers/index.js b/client/coral-admin/src/reducers/index.js index a19a5a087..415d0f581 100644 --- a/client/coral-admin/src/reducers/index.js +++ b/client/coral-admin/src/reducers/index.js @@ -1,19 +1,21 @@ import {combineReducers} from 'redux'; -import comments from 'reducers/comments'; -import settings from 'reducers/settings'; -import community from 'reducers/community'; -import users from 'reducers/users'; -import auth from 'reducers/auth'; -import actions from 'reducers/actions'; -import assets from 'reducers/assets'; -// Combine all reducers into a main one +import auth from './auth'; +import users from './users'; +import assets from './assets'; +import actions from './actions'; +import comments from './comments'; +import settings from './settings'; +import community from './community'; +import moderation from './moderation'; + export default combineReducers({ + auth, + users, + assets, + actions, settings, comments, community, - auth, - actions, - assets, - users + moderation }); diff --git a/client/coral-admin/src/reducers/moderation.js b/client/coral-admin/src/reducers/moderation.js new file mode 100644 index 000000000..3f04abfc6 --- /dev/null +++ b/client/coral-admin/src/reducers/moderation.js @@ -0,0 +1,24 @@ +import {Map} from 'immutable'; +import * as actions from '../constants/moderation'; + +const initialState = Map({ + activeTab: 'all', + singleView: false, + modalOpen: false +}); + +export default function moderation (state = initialState, action) { + switch (action.type) { + case actions.SET_ACTIVE_TAB: + return state + .set('activeTab', action.activeTab); + case actions.TOGGLE_MODAL: + return state + .set('modalOpen', action.open); + case actions.SINGLE_VIEW: + return state + .set('singleView', !state.get('singleView')); + default : + return state; + } +} From 3fc7fbd01e51f4084490256e1ad1e6e27a614f51 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Tue, 7 Feb 2017 16:06:32 -0300 Subject: [PATCH 03/28] =?UTF-8?q?=C3=81dmin=20running=20on=20Apollo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/coral-admin/src/actions/moderation.js | 2 +- .../coral-admin/src/constants/moderation.js | 2 +- .../ModerationQueue/ModerationContainer.js | 16 ++++++++-- .../coral-admin/src/graphql/queries/index.js | 10 +++++++ client/coral-admin/src/index.js | 15 ++++++++-- client/coral-admin/src/reducers/index.js | 6 ++-- .../src/services}/PymConnection.js | 0 .../src/services}/client.js | 0 client/coral-admin/src/services/store.js | 30 +++++++++++-------- .../src/services}/subscriptions.js | 0 .../src/services}/transport.js | 0 .../coral-framework/services/PymConnection.js | 9 ++++++ client/coral-framework/services/client.js | 14 +++++++++ .../coral-framework/{ => services}/store.js | 0 .../coral-framework/services/subscriptions.js | 14 +++++++++ client/coral-framework/services/transport.js | 11 +++++++ 16 files changed, 105 insertions(+), 24 deletions(-) create mode 100644 client/coral-admin/src/graphql/queries/index.js rename client/{coral-framework => coral-admin/src/services}/PymConnection.js (100%) rename client/{coral-framework => coral-admin/src/services}/client.js (100%) rename client/{coral-framework => coral-admin/src/services}/subscriptions.js (100%) rename client/{coral-framework => coral-admin/src/services}/transport.js (100%) create mode 100644 client/coral-framework/services/PymConnection.js create mode 100644 client/coral-framework/services/client.js rename client/coral-framework/{ => services}/store.js (100%) create mode 100644 client/coral-framework/services/subscriptions.js create mode 100644 client/coral-framework/services/transport.js diff --git a/client/coral-admin/src/actions/moderation.js b/client/coral-admin/src/actions/moderation.js index 25a9aed73..425628abe 100644 --- a/client/coral-admin/src/actions/moderation.js +++ b/client/coral-admin/src/actions/moderation.js @@ -2,4 +2,4 @@ import * as actions from 'constants/moderation'; export const setActiveTab = activeTab => ({type: actions.SET_ACTIVE_TAB, activeTab}); export const toggleModal = open => ({type: actions.TOGGLE_MODAL, open}); -export const singleView = open => ({type: actions.SINGLE_VIEW}); +export const singleView = () => ({type: actions.SINGLE_VIEW}); diff --git a/client/coral-admin/src/constants/moderation.js b/client/coral-admin/src/constants/moderation.js index 1ef27eddd..0fa51dc7b 100644 --- a/client/coral-admin/src/constants/moderation.js +++ b/client/coral-admin/src/constants/moderation.js @@ -1,3 +1,3 @@ export const SET_ACTIVE_TAB = 'SET_ACTIVE_TAB'; export const TOGGLE_MODAL = 'TOGGLE_MODAL'; -export const SINGLE_VIEW = 'SINGLE_VIEW' +export const SINGLE_VIEW = 'SINGLE_VIEW'; diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js index 095321e42..074d071e5 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js +++ b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js @@ -1,6 +1,9 @@ import React from 'react'; -import {connect} from 'react-redux'; import key from 'keymaster'; +import {connect} from 'react-redux'; +import {compose} from 'react-apollo'; + +import {modQueueQuery} from '../../graphql/queries'; import { updateStatus, @@ -19,6 +22,7 @@ import {setActiveTab, toggleModal, singleView} from 'actions/moderation'; import ModerationQueue from './ModerationQueue'; class ModerationContainer extends React.Component { + componentWillMount() { const {toggleModal, singleView} = this.props; @@ -88,6 +92,10 @@ class ModerationContainer extends React.Component { } } +ModerationContainer.contextTypes = { + router: React.PropTypes.func.isRequired +}; + const mapStateToProps = state => ({ moderation: state.moderation.toJS(), comments: state.comments.toJS(), @@ -101,7 +109,6 @@ const mapDispatchToProps = dispatch => ({ toggleModal: open => dispatch(toggleModal(open)), singleView: () => dispatch(singleView()), - fetchSettings: () => dispatch(fetchSettings()), fetchModerationQueueComments: () => dispatch(fetchModerationQueueComments()), fetchPremodQueue: () => dispatch(fetchPremodQueue()), @@ -119,4 +126,7 @@ const mapDispatchToProps = dispatch => ({ updateStatus: (action, comment) => dispatch(updateStatus(action, comment)) }); -export default connect(mapStateToProps, mapDispatchToProps)(ModerationContainer); +export default compose( + connect(mapStateToProps, mapDispatchToProps), + modQueueQuery +)(ModerationContainer); diff --git a/client/coral-admin/src/graphql/queries/index.js b/client/coral-admin/src/graphql/queries/index.js new file mode 100644 index 000000000..20cf1af8f --- /dev/null +++ b/client/coral-admin/src/graphql/queries/index.js @@ -0,0 +1,10 @@ +import {graphql} from 'react-apollo'; +import MOD_QUEUE_QUERY from './modQueueQuery.graphql'; + +export const modQueueQuery = graphql(MOD_QUEUE_QUERY, { + options: () => ({ + variables: { + asset_url: 'id' + } + }) +}); diff --git a/client/coral-admin/src/index.js b/client/coral-admin/src/index.js index 84d1b84ad..38b441992 100644 --- a/client/coral-admin/src/index.js +++ b/client/coral-admin/src/index.js @@ -1,6 +1,15 @@ import React from 'react'; -import ReactDOM from 'react-dom'; +import {render} from 'react-dom'; +import {ApolloProvider} from 'react-apollo'; + +import {client} from 'services/client'; +import store from 'services/store'; + import App from './components/App'; -// Render the application into the DOM -ReactDOM.render(, document.querySelector('#root')); +render( + + + + , document.querySelector('#root') +); diff --git a/client/coral-admin/src/reducers/index.js b/client/coral-admin/src/reducers/index.js index 415d0f581..53256fac5 100644 --- a/client/coral-admin/src/reducers/index.js +++ b/client/coral-admin/src/reducers/index.js @@ -1,5 +1,3 @@ -import {combineReducers} from 'redux'; - import auth from './auth'; import users from './users'; import assets from './assets'; @@ -9,7 +7,7 @@ import settings from './settings'; import community from './community'; import moderation from './moderation'; -export default combineReducers({ +export default { auth, users, assets, @@ -18,4 +16,4 @@ export default combineReducers({ comments, community, moderation -}); +}; diff --git a/client/coral-framework/PymConnection.js b/client/coral-admin/src/services/PymConnection.js similarity index 100% rename from client/coral-framework/PymConnection.js rename to client/coral-admin/src/services/PymConnection.js diff --git a/client/coral-framework/client.js b/client/coral-admin/src/services/client.js similarity index 100% rename from client/coral-framework/client.js rename to client/coral-admin/src/services/client.js diff --git a/client/coral-admin/src/services/store.js b/client/coral-admin/src/services/store.js index 0700dc579..8b9529e82 100644 --- a/client/coral-admin/src/services/store.js +++ b/client/coral-admin/src/services/store.js @@ -1,17 +1,23 @@ - -import {createStore, applyMiddleware} from 'redux'; +import {createStore, combineReducers, applyMiddleware, compose} from 'redux'; import thunk from 'redux-thunk'; -import mainReducer from 'reducers'; +import mainReducer from '../reducers'; +import {client} from './client'; -/** - * Create the store by merging the app reducers with - * the talk adapter. The talk adapter is the wire between - * this client and the coral backend. The idea is we can - * write different adapters for other platforms if we want - */ +const middlewares = [ + applyMiddleware(client.middleware()), + applyMiddleware(thunk) +]; + +if (window.devToolsExtension) { + + // we can't have the last argument of compose() be undefined + middlewares.push(window.devToolsExtension()); +} export default createStore( - mainReducer, - window.devToolsExtension && window.devToolsExtension(), - applyMiddleware(thunk) + combineReducers({ + ...mainReducer, + apollo: client.reducer() + }), + compose(...middlewares) ); diff --git a/client/coral-framework/subscriptions.js b/client/coral-admin/src/services/subscriptions.js similarity index 100% rename from client/coral-framework/subscriptions.js rename to client/coral-admin/src/services/subscriptions.js diff --git a/client/coral-framework/transport.js b/client/coral-admin/src/services/transport.js similarity index 100% rename from client/coral-framework/transport.js rename to client/coral-admin/src/services/transport.js diff --git a/client/coral-framework/services/PymConnection.js b/client/coral-framework/services/PymConnection.js new file mode 100644 index 000000000..ca592b824 --- /dev/null +++ b/client/coral-framework/services/PymConnection.js @@ -0,0 +1,9 @@ +import Pym from '../../node_modules/pym.js'; + +const pym = new Pym.Child({polling: 100}); +export default pym; + +export const link = (url) => (e) => { + e.preventDefault(); + pym.sendMessage('navigate', url); +}; diff --git a/client/coral-framework/services/client.js b/client/coral-framework/services/client.js new file mode 100644 index 000000000..b4a7a38df --- /dev/null +++ b/client/coral-framework/services/client.js @@ -0,0 +1,14 @@ +import ApolloClient, {addTypename} from 'apollo-client'; +import getNetworkInterface from './transport'; + +export const client = new ApolloClient({ + connectToDevTools: true, + queryTransformer: addTypename, + dataIdFromObject: (result) => { + if (result.id && result.__typename) { // eslint-disable-line no-underscore-dangle + return result.__typename + result.id; // eslint-disable-line no-underscore-dangle + } + return null; + }, + networkInterface: getNetworkInterface() +}); diff --git a/client/coral-framework/store.js b/client/coral-framework/services/store.js similarity index 100% rename from client/coral-framework/store.js rename to client/coral-framework/services/store.js diff --git a/client/coral-framework/services/subscriptions.js b/client/coral-framework/services/subscriptions.js new file mode 100644 index 000000000..f514ea18b --- /dev/null +++ b/client/coral-framework/services/subscriptions.js @@ -0,0 +1,14 @@ +import {print} from 'graphql-tag/printer'; + +// quick way to add the subscribe and unsubscribe functions to the network interface +const addGraphQLSubscriptions = (networkInterface, wsClient) => Object.assign(networkInterface, { + subscribe: (request, handler) => wsClient.subscribe({ + query: print(request.query), + variables: request.variables, + }, handler), + unsubscribe: (id) => { + wsClient.unsubscribe(id); + }, +}); + +export default addGraphQLSubscriptions; diff --git a/client/coral-framework/services/transport.js b/client/coral-framework/services/transport.js new file mode 100644 index 000000000..2bd6ac636 --- /dev/null +++ b/client/coral-framework/services/transport.js @@ -0,0 +1,11 @@ +import {createNetworkInterface} from 'apollo-client'; + +export default function getNetworkInterface(apiUrl = '/api/v1/graph/ql', headers = {}) { + return new createNetworkInterface({ + uri: apiUrl, + opts: { + credentials: 'same-origin', + headers, + }, + }); +} From f8d83ffbde38c49c7cd81d9b5e12c6778ba4083a Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Tue, 7 Feb 2017 16:15:53 -0300 Subject: [PATCH 04/28] Rest of the embed side config --- client/coral-embed-stream/src/index.js | 4 ++-- client/coral-framework/index.js | 4 ++-- client/coral-framework/services/PymConnection.js | 2 +- client/coral-framework/services/store.js | 2 +- client/coral-settings/containers/SettingsContainer.js | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/client/coral-embed-stream/src/index.js b/client/coral-embed-stream/src/index.js index f75146f0a..3dc69400c 100644 --- a/client/coral-embed-stream/src/index.js +++ b/client/coral-embed-stream/src/index.js @@ -2,8 +2,8 @@ import React from 'react'; import {render} from 'react-dom'; import {ApolloProvider} from 'react-apollo'; -import {client} from 'coral-framework/client'; -import store from 'coral-framework/store'; +import {client} from 'coral-framework/services/client'; +import store from 'coral-framework/services/store'; import Embed from './Embed'; diff --git a/client/coral-framework/index.js b/client/coral-framework/index.js index d2cd4197b..56a2e522c 100644 --- a/client/coral-framework/index.js +++ b/client/coral-framework/index.js @@ -1,5 +1,5 @@ -import store from './store'; -import pym from './PymConnection'; +import store from './services/store'; +import pym from './services/PymConnection'; import I18n from './modules/i18n/i18n'; import * as authActions from './actions/auth'; import * as assetActions from './actions/asset'; diff --git a/client/coral-framework/services/PymConnection.js b/client/coral-framework/services/PymConnection.js index ca592b824..6c492f2b8 100644 --- a/client/coral-framework/services/PymConnection.js +++ b/client/coral-framework/services/PymConnection.js @@ -1,4 +1,4 @@ -import Pym from '../../node_modules/pym.js'; +import Pym from '../../../node_modules/pym.js'; const pym = new Pym.Child({polling: 100}); export default pym; diff --git a/client/coral-framework/services/store.js b/client/coral-framework/services/store.js index 439569ee1..bf2735e45 100644 --- a/client/coral-framework/services/store.js +++ b/client/coral-framework/services/store.js @@ -1,6 +1,6 @@ import {createStore, combineReducers, applyMiddleware, compose} from 'redux'; import thunk from 'redux-thunk'; -import mainReducer from './reducers'; +import mainReducer from '../reducers'; import {client} from './client'; const middlewares = [ diff --git a/client/coral-settings/containers/SettingsContainer.js b/client/coral-settings/containers/SettingsContainer.js index f02badd80..cda0c657d 100644 --- a/client/coral-settings/containers/SettingsContainer.js +++ b/client/coral-settings/containers/SettingsContainer.js @@ -7,7 +7,7 @@ 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 {link} from 'coral-framework/services/PymConnection'; import NotLoggedIn from '../components/NotLoggedIn'; import {TabBar, Tab, TabContent, Spinner} from 'coral-ui'; import SettingsHeader from '../components/SettingsHeader'; From fab0c7e74d9ef3c821869338d437480cee7d41e0 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Wed, 8 Feb 2017 12:39:35 -0300 Subject: [PATCH 05/28] Mod Queue Working --- .../src/graphql/queries/modQueueQuery.graphql | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/client/coral-admin/src/graphql/queries/modQueueQuery.graphql b/client/coral-admin/src/graphql/queries/modQueueQuery.graphql index fda130510..5d69f6324 100644 --- a/client/coral-admin/src/graphql/queries/modQueueQuery.graphql +++ b/client/coral-admin/src/graphql/queries/modQueueQuery.graphql @@ -1,21 +1,27 @@ #import "../fragments/commentView.graphql" query ($asset_id: ID!) { - premod: comments(query: { - statuses: [PREMOD], + all: comments(query: { + statuses: [ACCEPTED, REJECTED, PREMOD], asset_id: $asset_id }) { ...commentView } + premod: comments(query: { + statuses: [PREMOD], + asset_id: $asset_id + }) { + ...commentView + } flagged: comments(query: { - action_type: FLAG, - asset_id: $asset_id + action_type: FLAG, + asset_id: $asset_id }) { ...commentView } - all: comments(query: { - statuses: [ACCEPTED], - asset_id: $asset_id + rejected: comments(query: { + statuses: [REJECTED], + asset_id: $asset_id }) { ...commentView } @@ -25,10 +31,4 @@ query ($asset_id: ID!) { }) { ...commentView } - rejected: comments(query: { - statuses: [REJECTED], - asset_id: $asset_id - }) { - ...commentView - } } From e234fa7e0bf76d02d5dc98a10fc23995954dc6c4 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Thu, 9 Feb 2017 07:00:39 -0300 Subject: [PATCH 06/28] Reducers refactor, working Moderate Queue --- .../ModerationQueue/ModerationContainer.js | 18 +- .../ModerationQueue/ModerationQueue.js | 33 ++-- .../ModerationQueue/components/Comment.js | 76 ++++++++ .../components/ModerationList.js | 0 .../ModerationQueue/components/styles.css | 184 ++++++++++++++++++ client/coral-admin/src/reducers/actions.js | 27 --- client/coral-admin/src/reducers/comments.js | 77 -------- client/coral-admin/src/reducers/index.js | 6 - client/coral-admin/src/reducers/settings.js | 81 ++++---- client/coral-admin/src/reducers/users.js | 28 --- client/coral-admin/src/services/client.js | 1 - 11 files changed, 325 insertions(+), 206 deletions(-) create mode 100644 client/coral-admin/src/containers/ModerationQueue/components/Comment.js create mode 100644 client/coral-admin/src/containers/ModerationQueue/components/ModerationList.js create mode 100644 client/coral-admin/src/containers/ModerationQueue/components/styles.css delete mode 100644 client/coral-admin/src/reducers/actions.js delete mode 100644 client/coral-admin/src/reducers/comments.js delete mode 100644 client/coral-admin/src/reducers/users.js diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js index cd1a3e59f..9968f25d3 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js +++ b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js @@ -29,7 +29,6 @@ class ModerationContainer extends Component { componentWillMount() { const {toggleModal, singleView} = this.props; - this.props.fetchModerationQueueComments(); this.props.fetchSettings(); key('s', () => singleView()); @@ -54,7 +53,7 @@ class ModerationContainer extends Component { } render () { - const {data, moderation} = this.props; + const {data, moderation, settings} = this.props; if (data.loading) { return
; @@ -66,27 +65,16 @@ class ModerationContainer extends Component { ); } } -// ModerationContainer.contextTypes = { -// router: React.PropTypes.func.isRequired -// }; - const mapStateToProps = state => ({ moderation: state.moderation.toJS(), - comments: state.comments.toJS(), - settings: state.settings.toJS(), - users: state.users.toJS(), - actions: state.actions.toJS() + settings: state.settings.toJS() }); const mapDispatchToProps = dispatch => ({ diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js b/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js index 961642e84..f1f3514ea 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js +++ b/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js @@ -1,22 +1,29 @@ -import React from 'react'; +import React, {PropTypes} from 'react'; -// import Comment from '../../components/Comment'; +import Comment from './components/Comment'; -export default (props) => { +const ModerationQueue = props => { return (
- {/* */} +
    { - props.data[props.activeTab].map((comment, i) => -
    - {comment.body} -
    - ) + props.data[props.activeTab].map((comment, i) => { + console.log(comment); + return ; + }) } +
); }; + +ModerationQueue.propTypes = { + data: PropTypes.object.isRequired +}; + +export default ModerationQueue; diff --git a/client/coral-admin/src/containers/ModerationQueue/components/Comment.js b/client/coral-admin/src/containers/ModerationQueue/components/Comment.js new file mode 100644 index 000000000..a06f052e4 --- /dev/null +++ b/client/coral-admin/src/containers/ModerationQueue/components/Comment.js @@ -0,0 +1,76 @@ +import React, {PropTypes} from 'react'; +import timeago from 'timeago.js'; +import Linkify from 'react-linkify'; +import Highlighter from 'react-highlight-words'; + +import styles from './styles.css'; + +import {Icon} from 'coral-ui'; + +// import ActionButton from '../components/ActionButton'; + +import I18n from 'coral-framework/modules/i18n/i18n'; +import translations from '../../../translations.json'; + +const Comment = props => { + const links = linkify.getMatches(props.body); + + return ( +
  • +
    +
    + {props.user.name} + + {timeago().format(props.created_at || (Date.now() - props.index * 60 * 1000), lang.getLocale().replace('-', '_'))} + + {props.flagged ?

    {lang.t('comment.flagged')}

    : null} +
    +
    + {links ? Contains Link : null} +
    + {/* {props.modActions.map(*/} + {/* (action, i) =>*/} + {/* */} + {/* )}*/} +
    + {props.user.banned === 'banned' ? + + + {lang.t('comment.banned_user')} + + : null} +
    +
    +
    + + + + + +
    +
  • + ); +}; + +const linkStyles = { + backgroundColor: 'rgb(255, 219, 135)', + padding: '1px 2px' +}; + +const linkify = new Linkify(); +const lang = new I18n(translations); + +Comment.propTypes = { + user: PropTypes.object.isRequired +}; + +export default Comment; diff --git a/client/coral-admin/src/containers/ModerationQueue/components/ModerationList.js b/client/coral-admin/src/containers/ModerationQueue/components/ModerationList.js new file mode 100644 index 000000000..e69de29bb diff --git a/client/coral-admin/src/containers/ModerationQueue/components/styles.css b/client/coral-admin/src/containers/ModerationQueue/components/styles.css new file mode 100644 index 000000000..fbcbce7d7 --- /dev/null +++ b/client/coral-admin/src/containers/ModerationQueue/components/styles.css @@ -0,0 +1,184 @@ + +@custom-media --big-viewport (min-width: 780px); + +.list { + padding: 8px 0; + list-style: none; + display: block; + + &.singleView .listItem { + display: none; + } + + &.singleView .listItem.activeItem { + display: block; + height: 100%; + font-size: 1.5em; + line-height: 1.5em; + border: none; + + .actions { + position: fixed; + bottom: 60px; + left: 25%; + margin: 0 auto; + display: flex; + justify-content: space-around; + width: 50%; + margin: 0; + } + + .actionButton { + transform: scale(1.4); + } + } +} + +.listItem { + border-bottom: 1px solid #e0e0e0; + font-size: 16px; + width: 100%; + max-width: 660px; + min-width: 400px; + margin: 0 auto; + padding: 16px 14px; + position: relative; + transition: box-shadow 200ms; + + + &:hover { + box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23); + } + + &:last-child { + border-bottom: none; + } + + .sideActions { + position: absolute; + right: 0; + height: 100%; + top: 0; + padding: 40px 18px; + box-sizing: border-box; + } + + .itemHeader { + display: flex; + align-items: center; + justify-content: space-between; + + .author { + min-width: 230px; + display: flex; + align-items: center; + } + } + + .itemBody { + display: flex; + justify-content: space-between; + } + + .avatar { + margin-right: 16px; + height: 40px; + width: 40px; + border-radius: 50%; + background-color: #757575; + font-size: 40px; + color: #fff; + } + + .created { + color: #666; + font-size: 13px; + margin-left: 40px; + } + + .actionButton { + transform: scale(.8); + margin: 0; + } + + .body { + margin-top: 20px; + flex: 1; + font-size: 0.88em; + color: black; + } + + .flagged { + color: rgba(255, 0, 0, .5); + padding-top: 15px; + padding-left: 10px; + } + + .flagCount{ + font-size: 12px; + color: #d32f2f; + } + +} + +.empty { + color: #444; + margin-top: 50px; + text-align: center; +} + + +@media (--big-viewport) { + .listItem { + border: 1px solid #e0e0e0; + margin-bottom: 30px; + + &:last-child { + border-bottom: 1px solid #e0e0e0; + } + + &.activeItem { + border: 2px solid #333; + } + } + +} + +.hasLinks { + color: #f00; + text-align: right; + display: flex; + align-items: center; + + i { + margin-right: 5px; + } +} + +.banned { + color: #f00; + text-align: left; + display: flex; + align-items: center; + + i { + margin-right: 5px; + } +} + +.ban { + display: block; + text-align: center; + margin-top: 5px; +} + +.banButton { + width: 114px; + letter-spacing: 1px; + + i { + vertical-align: middle; + margin-right: 10px; + font-size: 14px; + } +} diff --git a/client/coral-admin/src/reducers/actions.js b/client/coral-admin/src/reducers/actions.js deleted file mode 100644 index 284e41c72..000000000 --- a/client/coral-admin/src/reducers/actions.js +++ /dev/null @@ -1,27 +0,0 @@ -import {Map, Set, fromJS} from 'immutable'; -import * as types from '../constants/actions'; - -const initialState = Map({ - ids: Set(), - byId: Map() -}); - -export default (state = initialState, action) => { - switch (action.type) { - case types.ACTIONS_MODERATION_QUEUE_FETCH_SUCCESS: return addActions(state, action); - default: - return state; - } -}; - -const addActions = (state, action) => { - - // Make ids that are unique by item_id and by action type - const typeId = (action) => `${action.action_type}_${action.item_id}`; - const ids = action.actions.map(action => typeId(action)); - const map = action.actions.reduce((memo, action) => { - memo[typeId(action)] = action; - return memo; - }, {}); - return state.set('byId', fromJS(map)).set('ids', new Set(ids)); -}; diff --git a/client/coral-admin/src/reducers/comments.js b/client/coral-admin/src/reducers/comments.js deleted file mode 100644 index 034016088..000000000 --- a/client/coral-admin/src/reducers/comments.js +++ /dev/null @@ -1,77 +0,0 @@ -import * as actions from '../constants/comments'; -import * as userActions from '../constants/users'; -import {Map, List, fromJS} from 'immutable'; - -/** - * Comments state is stored using 2 structures: - * - byId is a Map holding the comments using the item_id property as keys - * - ids is a List of item_id, this allows us to order and iterate easily - * since maps are unordered and some times we just need a list of things - */ - -const initialState = Map({ - byId: Map(), - ids: List(), - loading: false, - showBanUserDialog: false, - banUser: { - 'userName': '', - 'userId': '', - 'commentId': '' - } -}); - -// Handle the comment actions -export default (state = initialState, action) => { - switch (action.type) { - case actions.COMMENTS_MODERATION_QUEUE_FETCH_REQUEST: return state.set('loading', true); - case actions.COMMENTS_MODERATION_QUEUE_FETCH_SUCCESS: return replaceComments(action, state); - case actions.COMMENTS_MODERATION_QUEUE_FAILED: return state.set('loading', false); - case actions.COMMENT_STATUS_UPDATE_REQUEST: return updateStatus(state, action); - case actions.COMMENT_FLAG: return flag(state, action); - case actions.COMMENT_CREATE_SUCCESS: return addComment(state, action); - case actions.COMMENT_STREAM_FETCH_SUCCESS: return replaceComments(action, state); - case actions.SHOW_BANUSER_DIALOG: return setBanUser(state, true, action); - case actions.HIDE_BANUSER_DIALOG: return setBanUser(state, false, action); - case actions.USER_BAN_SUCCESS: return setBanUser(state, false, action); - case userActions.UPDATE_STATUS_SUCCESS: return setBanUser(state, false, action); - default: return state; - } -}; - -// hide or show the UI for the dialog confirming the ban -// set the user that is going to set and the comment that is the reason -const setBanUser = (state, showBanUser, action) => { - const banUser = {'userName': action.userName, 'userId': action.userId, 'commentId': action.commentId}; - return state.set('showBanUserDialog', showBanUser) - .set('banUser', banUser); -}; - -// Update a comment status -const updateStatus = (state, action) => { - const byId = state.get('byId'); - const data = byId.get(action.id).set('status', action.status.toLowerCase()); - return state.set('byId', byId.set(action.id, data)); -}; - -// Flag a comment -const flag = (state, action) => { - const byId = state.get('byId'); - const data = byId.get(action.id).set('flagged', true); - const comment = byId.get(action.id).set('data', data); - return state.set('byId', byId.set(action.id, comment)); -}; - -// Replace the comment list with a new one -const replaceComments = (action, state) => { - const comments = fromJS(action.comments.reduce((prev, curr) => { prev[curr.id] = curr; return prev; }, {})); - return state.set('byId', comments).set('loading', false) - .set('ids', List(comments.keys())); -}; - -// Add a new comment -const addComment = (state, action) => { - const comment = fromJS(action.comment); - return state.set('byId', state.get('byId').set(comment.get('item_id'), comment)) - .set('ids', state.get('ids').unshift(comment.get('item_id'))); -}; diff --git a/client/coral-admin/src/reducers/index.js b/client/coral-admin/src/reducers/index.js index 53256fac5..e58f5bf5a 100644 --- a/client/coral-admin/src/reducers/index.js +++ b/client/coral-admin/src/reducers/index.js @@ -1,19 +1,13 @@ import auth from './auth'; -import users from './users'; import assets from './assets'; -import actions from './actions'; -import comments from './comments'; import settings from './settings'; import community from './community'; import moderation from './moderation'; export default { auth, - users, assets, - actions, settings, - comments, community, moderation }; diff --git a/client/coral-admin/src/reducers/settings.js b/client/coral-admin/src/reducers/settings.js index 12b16d9ad..fa728b3ed 100644 --- a/client/coral-admin/src/reducers/settings.js +++ b/client/coral-admin/src/reducers/settings.js @@ -1,5 +1,5 @@ import {Map, List} from 'immutable'; -import * as types from '../actions/settings'; +import * as actions from '../actions/settings'; const initialState = Map({ settings: Map({ @@ -13,43 +13,46 @@ const initialState = Map({ fetchingSettings: false }); -// Handle the comment actions -export default (state = initialState, action) => { +export default function settings (state = initialState, action) { switch (action.type) { - case types.SETTINGS_LOADING: return state.set('fetchingSettings', true).set('fetchSettingsError', null); - case types.SETTINGS_RECEIVED: return updateSettings(state, action); - case types.SETTINGS_FETCH_ERROR: return settingsFetchFailed(state, action); - case types.SETTINGS_UPDATED: return updateSettings(state, action); - case types.SAVE_SETTINGS_LOADING: return state.set('fetchingSettings', true).set('saveSettingsError', null); - case types.SAVE_SETTINGS_SUCCESS: return saveComplete(state, action); - case types.SAVE_SETTINGS_FAILED: return settingsSaveFailed(state, action); - case types.WORDLIST_UPDATED: return updateWordlist(state, action); - default: return state; + case actions.SETTINGS_LOADING: + return state + .set('fetchingSettings', true) + .set('fetchSettingsError', null); + case actions.SETTINGS_RECEIVED: + return state.merge({ + fetchingSettings: null, + fetchSettingsError: null, + ...action.settings + }); + case actions.SETTINGS_FETCH_ERROR: + return state + .set('fetchingSettings', false) + .set('fetchSettingsError', action.error); + case actions.SETTINGS_UPDATED: + return state.merge({ + fetchingSettings: null, + fetchSettingsError: null, + ...action.settings + }); + case actions.SAVE_SETTINGS_LOADING: + return state + .set('fetchingSettings', true) + .set('saveSettingsError', null); + case actions.SAVE_SETTINGS_SUCCESS: + return state.merge({ + fetchingSettings: false, + fetchSettingsError: null, + ...action.settings + }); + case actions.SAVE_SETTINGS_FAILED: + return state + .set('fetchingSettings', false) + .set('fetchSettingsError', action.error); + case actions.WORDLIST_UPDATED: + return state + .setIn(['settings', 'wordlist', action.listName], action.list); + default: + return state; } -}; - -// only for updating top-level settings -const updateSettings = (state, action) => { - const s = state.set('fetchingSettings', false).set('fetchSettingsError', null); - const settings = s.get('settings').merge(action.settings); - return s.set('settings', settings); -}; - -// any nested settings must have a specialized setter -const updateWordlist = (state, action) => { - return state.setIn(['settings', 'wordlist', action.listName], action.list); -}; - -const saveComplete = (state, action) => { - const s = state.set('fetchingSettings', false).set('saveSettingsError', null); - const settings = s.get('settings').merge(action.settings); - return s.set('settings', settings); -}; - -const settingsFetchFailed = (state, action) => { - return state.set('fetchingSettings', false).set('fetchSettingsError', action.error); -}; - -const settingsSaveFailed = (state, action) => { - return state.set('fetchingSettings', false).set('fetchSettingsError', action.error); -}; +} diff --git a/client/coral-admin/src/reducers/users.js b/client/coral-admin/src/reducers/users.js deleted file mode 100644 index ef589c155..000000000 --- a/client/coral-admin/src/reducers/users.js +++ /dev/null @@ -1,28 +0,0 @@ -import {Map, List, fromJS} from 'immutable'; - -const initialState = Map({ - byId: Map(), - ids: List() -}); - -export default (state = initialState, action) => { - switch (action.type) { - case 'USERS_MODERATION_QUEUE_FETCH_SUCCESS': return replaceUsers(action, state); - case 'USER_STATUS_UPDATE': return updateUserStatus(state, action); - default: return state; - } -}; - -// Replace the comment list with a new one -const replaceUsers = (action, state) => { - const users = fromJS(action.users.reduce((prev, curr) => { prev[curr.id] = curr; return prev; }, {})); - return state.set('byId', users) - .set('ids', List(users.keys())); -}; - -// Update a user status -const updateUserStatus = (state, action) => { - const byId = state.get('byId'); - const data = byId.get(action.author_id).set('status', action.status.toLowerCase()); - return state.set('byId', byId.set(action.author_id, data)); -}; diff --git a/client/coral-admin/src/services/client.js b/client/coral-admin/src/services/client.js index b4a7a38df..40a539634 100644 --- a/client/coral-admin/src/services/client.js +++ b/client/coral-admin/src/services/client.js @@ -2,7 +2,6 @@ import ApolloClient, {addTypename} from 'apollo-client'; import getNetworkInterface from './transport'; export const client = new ApolloClient({ - connectToDevTools: true, queryTransformer: addTypename, dataIdFromObject: (result) => { if (result.id && result.__typename) { // eslint-disable-line no-underscore-dangle From e6087dfa2569f52aa52f0536541bcf2ee00cf3dc Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Thu, 9 Feb 2017 13:53:23 -0300 Subject: [PATCH 07/28] Go to Moderate from Stream --- client/coral-admin/src/components/ActionButton.js | 2 +- .../components/ModerationList.js => graphql/mutations/index.js} | 0 client/coral-plugin-moderation/ModerationLink.js | 0 client/coral-plugin-moderation/index.js | 0 client/coral-plugin-moderation/styles.css | 0 client/coral-plugin-moderation/translations.json | 0 6 files changed, 1 insertion(+), 1 deletion(-) rename client/coral-admin/src/{containers/ModerationQueue/components/ModerationList.js => graphql/mutations/index.js} (100%) create mode 100644 client/coral-plugin-moderation/ModerationLink.js create mode 100644 client/coral-plugin-moderation/index.js create mode 100644 client/coral-plugin-moderation/styles.css create mode 100644 client/coral-plugin-moderation/translations.json diff --git a/client/coral-admin/src/components/ActionButton.js b/client/coral-admin/src/components/ActionButton.js index 61de1c669..25c7772dc 100644 --- a/client/coral-admin/src/components/ActionButton.js +++ b/client/coral-admin/src/components/ActionButton.js @@ -1,5 +1,5 @@ import React from 'react'; -import styles from './ModerationList.css'; +import styles from './styles.css'; import I18n from 'coral-framework/modules/i18n/i18n'; import translations from '../translations.json'; import {FabButton, Button, Icon} from 'coral-ui'; diff --git a/client/coral-admin/src/containers/ModerationQueue/components/ModerationList.js b/client/coral-admin/src/graphql/mutations/index.js similarity index 100% rename from client/coral-admin/src/containers/ModerationQueue/components/ModerationList.js rename to client/coral-admin/src/graphql/mutations/index.js diff --git a/client/coral-plugin-moderation/ModerationLink.js b/client/coral-plugin-moderation/ModerationLink.js new file mode 100644 index 000000000..e69de29bb diff --git a/client/coral-plugin-moderation/index.js b/client/coral-plugin-moderation/index.js new file mode 100644 index 000000000..e69de29bb diff --git a/client/coral-plugin-moderation/styles.css b/client/coral-plugin-moderation/styles.css new file mode 100644 index 000000000..e69de29bb diff --git a/client/coral-plugin-moderation/translations.json b/client/coral-plugin-moderation/translations.json new file mode 100644 index 000000000..e69de29bb From 3cd2b28bc26bcb89b1a42eed2713c91975859882 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 9 Feb 2017 16:48:59 -0700 Subject: [PATCH 08/28] Inital rename of displayName -> username --- bin/cli-setup | 10 ++-- bin/cli-users | 18 +++---- .../src/components/ActionButton.js | 2 +- client/coral-admin/src/components/Comment.js | 2 +- client/coral-admin/src/components/User.js | 2 +- .../src/containers/Community/Community.js | 2 +- .../src/containers/Community/Table.js | 2 +- .../components/Steps/CreateYourAccount.js | 4 +- client/coral-admin/src/reducers/install.js | 4 +- client/coral-admin/src/translations.json | 2 +- client/coral-embed-stream/src/Embed.js | 2 +- client/coral-embed-stream/src/Stream.js | 2 +- client/coral-framework/actions/auth.js | 4 +- client/coral-framework/actions/user.js | 4 +- .../components/SuspendedAccount.js | 18 +++---- .../graphql/fragments/commentView.graphql | 2 +- client/coral-framework/helpers/error.js | 2 +- client/coral-framework/helpers/validate.js | 2 +- client/coral-framework/reducers/auth.js | 2 +- client/coral-framework/reducers/user.js | 2 +- client/coral-framework/translations.json | 14 +++--- .../RileysAwesomeCommentBox.js | 2 +- client/coral-plugin-stream/Stream.js | 2 +- .../components/SettingsHeader.js | 2 +- .../components/CreateDisplayNameDialog.js | 10 ++-- .../coral-sign-in/components/SignUpContent.js | 12 ++--- client/coral-sign-in/components/UserBox.js | 2 +- .../containers/ChangeDisplayNameContainer.js | 2 +- .../containers/SignInContainer.js | 2 +- client/coral-sign-in/translations.js | 10 ++-- docs/swagger.yaml | 6 +-- errors.js | 4 +- graph/typeDefs.graphql | 4 +- models/user.js | 16 ++++-- routes/api/account/index.js | 4 +- routes/api/auth/index.js | 2 +- routes/api/setup/index.js | 4 +- routes/api/users/index.js | 4 +- services/passport.js | 2 +- services/setup.js | 10 ++-- services/users.js | 50 +++++++++---------- services/wordlist.js | 8 +-- test/e2e/pages/embedStreamPage.js | 4 +- test/e2e/tests/EmbedStreamTests.js | 4 +- test/e2e/tests/Visitor/SignUpTest.js | 2 +- test/routes/api/account/index.js | 12 ++--- test/routes/api/auth/index.js | 4 +- test/routes/api/comments/index.js | 12 ++--- test/routes/api/queue/index.js | 8 +-- test/services/comments.js | 4 +- test/services/users.js | 23 ++++----- views/auth-callback.ejs | 2 +- 52 files changed, 171 insertions(+), 164 deletions(-) diff --git a/bin/cli-setup b/bin/cli-setup index 185b04f01..7985bf220 100755 --- a/bin/cli-setup +++ b/bin/cli-setup @@ -116,11 +116,11 @@ const performSetup = () => { return inquirer.prompt([ { type: 'input', - name: 'displayName', - message: 'Display Name', - filter: (displayName) => { + name: 'username', + message: 'Username', + filter: (username) => { return UsersService - .isValidDisplayName(displayName, false) + .isValidDisplayName(username, false) .catch((err) => { throw err.message; }); @@ -174,7 +174,7 @@ const performSetup = () => { settings: settings.toObject(), user: { email: user.email, - displayName: user.displayName, + username: user.username, password: user.password } }); diff --git a/bin/cli-users b/bin/cli-users index 01f46ad2e..473c07d9c 100755 --- a/bin/cli-users +++ b/bin/cli-users @@ -32,7 +32,7 @@ function getUserCreateAnswers(options) { email: options.email, password: options.password, confirmPassword: options.password, - displayName: options.name, + username: options.name, roles: [] }; @@ -75,11 +75,11 @@ function getUserCreateAnswers(options) { } }, { - name: 'displayName', - message: 'Display Name', - filter: (displayName) => { + name: 'username', + message: 'Username', + filter: (username) => { return UsersService - .isValidDisplayName(displayName) + .isValidDisplayName(username) .catch((err) => { throw err.message; }); @@ -108,7 +108,7 @@ function createUser(options) { }) .then((answers) => { return UsersService - .createLocalUser(answers.email.trim(), answers.password.trim(), answers.displayName.trim()) + .createLocalUser(answers.email.trim(), answers.password.trim(), answers.username.trim()) .then((user) => { console.log(`Created user ${user.id}.`); @@ -209,7 +209,7 @@ function updateUser(userID, options) { 'id': userID }, { $set: { - displayName: options.name + username: options.name } }); @@ -238,7 +238,7 @@ function listUsers() { let table = new Table({ head: [ 'ID', - 'Display Name', + 'Username', 'Profiles', 'Roles', 'Status', @@ -249,7 +249,7 @@ function listUsers() { users.forEach((user) => { table.push([ user.id, - user.displayName, + user.username, user.profiles.map((p) => p.provider).join(', '), user.roles.join(', '), user.status, diff --git a/client/coral-admin/src/components/ActionButton.js b/client/coral-admin/src/components/ActionButton.js index 61de1c669..14b7fe95d 100644 --- a/client/coral-admin/src/components/ActionButton.js +++ b/client/coral-admin/src/components/ActionButton.js @@ -18,7 +18,7 @@ const ActionButton = ({option, type, comment = {}, user, menuOptionsMap, onClick className={`ban ${styles.banButton}`} cStyle='darkGrey' disabled={banned ? 'disabled' : ''} - onClick={() => onClickShowBanDialog(user.id, user.displayName, comment.id) + onClick={() => onClickShowBanDialog(user.id, user.username, comment.id) } raised > diff --git a/client/coral-admin/src/components/Comment.js b/client/coral-admin/src/components/Comment.js index 74b0361a2..db5cfe5bf 100644 --- a/client/coral-admin/src/components/Comment.js +++ b/client/coral-admin/src/components/Comment.js @@ -23,7 +23,7 @@ const Comment = props => {
  • - {author.displayName || lang.t('comment.anon')} + {author.username || lang.t('comment.anon')} {timeago().format(comment.createdAt || (Date.now() - props.index * 60 * 1000), lang.getLocale().replace('-', '_'))} {comment.flagged ?

    {lang.t('comment.flagged')}

    : null}
    diff --git a/client/coral-admin/src/components/User.js b/client/coral-admin/src/components/User.js index 54de0375e..6b08ed76a 100644 --- a/client/coral-admin/src/components/User.js +++ b/client/coral-admin/src/components/User.js @@ -18,7 +18,7 @@ const User = props => {
  • - {user.displayName} + {user.username}
    diff --git a/client/coral-admin/src/containers/Community/Community.js b/client/coral-admin/src/containers/Community/Community.js index d63dd1e96..bad5654cf 100644 --- a/client/coral-admin/src/containers/Community/Community.js +++ b/client/coral-admin/src/containers/Community/Community.js @@ -13,7 +13,7 @@ const lang = new I18n(translations); const tableHeaders = [ { title: lang.t('community.username_and_email'), - field: 'displayName' + field: 'username' }, { title: lang.t('community.account_creation_date'), diff --git a/client/coral-admin/src/containers/Community/Table.js b/client/coral-admin/src/containers/Community/Table.js index 254550e74..480ce72bf 100644 --- a/client/coral-admin/src/containers/Community/Table.js +++ b/client/coral-admin/src/containers/Community/Table.js @@ -44,7 +44,7 @@ class Table extends Component { {commenters.map((row, i)=> ( - {row.displayName} + {row.username} {row.profiles.map(({id}) => id)} diff --git a/client/coral-admin/src/containers/Install/components/Steps/CreateYourAccount.js b/client/coral-admin/src/containers/Install/components/Steps/CreateYourAccount.js index 91c664c37..90deff640 100644 --- a/client/coral-admin/src/containers/Install/components/Steps/CreateYourAccount.js +++ b/client/coral-admin/src/containers/Install/components/Steps/CreateYourAccount.js @@ -21,12 +21,12 @@ const InitialStep = props => { ({ }); }, clearNotification: () => dispatch(clearNotification()), - editName: (displayName) => dispatch(editName(displayName)), + editName: (username) => dispatch(editName(username)), showSignInDialog: (offset) => dispatch(showSignInDialog(offset)), logout: () => dispatch(logout()), dispatch: d => dispatch(d) diff --git a/client/coral-embed-stream/src/Stream.js b/client/coral-embed-stream/src/Stream.js index 65743e6c7..8f84bb3bf 100644 --- a/client/coral-embed-stream/src/Stream.js +++ b/client/coral-embed-stream/src/Stream.js @@ -10,7 +10,7 @@ class Stream extends React.Component { asset: PropTypes.object.isRequired, comments: PropTypes.array.isRequired, currentUser: PropTypes.shape({ - displayName: PropTypes.string, + username: PropTypes.string, id: PropTypes.string }) } diff --git a/client/coral-framework/actions/auth.js b/client/coral-framework/actions/auth.js index bda46ae42..a2206fc09 100644 --- a/client/coral-framework/actions/auth.js +++ b/client/coral-framework/actions/auth.js @@ -15,11 +15,11 @@ 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 = ({username}) => ({type: actions.UPDATE_DISPLAYNAME, username}); export const createDisplayName = (userId, formData) => dispatch => { dispatch(createDisplayNameRequest()); - coralApi('/account/displayname', {method: 'PUT', body: formData}) + coralApi('/account/username', {method: 'PUT', body: formData}) .then(() => { dispatch(createDisplayNameSuccess()); dispatch(hideCreateDisplayNameDialog()); diff --git a/client/coral-framework/actions/user.js b/client/coral-framework/actions/user.js index 42d2e0398..bf2a77402 100644 --- a/client/coral-framework/actions/user.js +++ b/client/coral-framework/actions/user.js @@ -5,8 +5,8 @@ import I18n from 'coral-framework/modules/i18n/i18n'; import translations from './../translations'; const lang = new I18n(translations); -export const editName = (displayName) => (dispatch) => { - return coralApi('/account/displayname', {method: 'PUT', body: {displayName}}) +export const editName = (username) => (dispatch) => { + return coralApi('/account/username', {method: 'PUT', body: {username}}) .then(() => { dispatch(addNotification('success', lang.t('successNameUpdate'))); }); diff --git a/client/coral-framework/components/SuspendedAccount.js b/client/coral-framework/components/SuspendedAccount.js index 4c221aa81..00adc9952 100644 --- a/client/coral-framework/components/SuspendedAccount.js +++ b/client/coral-framework/components/SuspendedAccount.js @@ -14,16 +14,16 @@ class SuspendedAccount extends Component { } state = { - displayName: '', + username: '', alert: '' } onSubmitClick = (e) => { const {editName} = this.props; - const {displayName} = this.state; + const {username} = this.state; e.preventDefault(); - if (validate.displayName(displayName)) { - editName(displayName) + if (validate.username(username)) { + editName(username) .then(() => location.reload()) .catch((error) => { this.setState({alert: lang.t(`error.${error.message}`)}); @@ -36,7 +36,7 @@ class SuspendedAccount extends Component { render () { const {canEditName} = this.props; - const {displayName, alert} = this.state; + const {username, alert} = this.state; return
    { @@ -51,7 +51,7 @@ class SuspendedAccount extends Component { {alert}
    diff --git a/client/coral-sign-in/components/SignUpContent.js b/client/coral-sign-in/components/SignUpContent.js index 03c083070..3a3404174 100644 --- a/client/coral-sign-in/components/SignUpContent.js +++ b/client/coral-sign-in/components/SignUpContent.js @@ -21,13 +21,13 @@ class SignUpContent extends React.Component { showErrors: PropTypes.bool, errors: PropTypes.shape({ email: PropTypes.string, - displayName: PropTypes.string, + username: PropTypes.string, password: PropTypes.string, confirmPassword: PropTypes.string, }), formData: PropTypes.shape({ email: PropTypes.string, - displayName: PropTypes.string, + username: PropTypes.string, password: PropTypes.string, confirmPassword: PropTypes.string }) @@ -89,12 +89,12 @@ class SignUpContent extends React.Component { onChange={handleChange} /> (
    {lang.t('signIn.loggedInAs')} - changeTab(1)}>{user.displayName}. {lang.t('signIn.notYou')} + changeTab(1)}>{user.username}. {lang.t('signIn.notYou')} {lang.t('signIn.logout')}
    ); diff --git a/client/coral-sign-in/containers/ChangeDisplayNameContainer.js b/client/coral-sign-in/containers/ChangeDisplayNameContainer.js index bc7fe8267..625985b63 100644 --- a/client/coral-sign-in/containers/ChangeDisplayNameContainer.js +++ b/client/coral-sign-in/containers/ChangeDisplayNameContainer.js @@ -21,7 +21,7 @@ import { class ChangeDisplayNameContainer extends Component { initialState = { formData: { - displayName: '', + username: '', }, errors: {}, showErrors: false diff --git a/client/coral-sign-in/containers/SignInContainer.js b/client/coral-sign-in/containers/SignInContainer.js index c92fe024d..fd9762ee3 100644 --- a/client/coral-sign-in/containers/SignInContainer.js +++ b/client/coral-sign-in/containers/SignInContainer.js @@ -29,7 +29,7 @@ class SignInContainer extends Component { initialState = { formData: { email: '', - displayName: '', + username: '', password: '', confirmPassword: '' }, diff --git a/client/coral-sign-in/translations.js b/client/coral-sign-in/translations.js index e8014014c..36ce3fce4 100644 --- a/client/coral-sign-in/translations.js +++ b/client/coral-sign-in/translations.js @@ -19,7 +19,7 @@ export default { register: 'Register', signUp: 'Sign Up', confirmPassword: 'Confirm Password', - displayName: 'Display Name', + username: 'Username', alreadyHaveAnAccount: 'Already have an account?', recoverPassword: 'Recover password', emailInUse: 'Email address already in use', @@ -32,10 +32,10 @@ export default { 'createdisplay': { writeyourusername: 'Write your username', yourusername: 'Your username is publicly visible on all comments you post. A username is needed before you can post your first comment.', - displayName: 'Display Name', + username: 'Username', save: 'Save', requiredField: 'Required field', - errorCreate: 'Error when changing display name', + errorCreate: 'Error when changing username', checkTheForm: 'Invalid Form. Please, check the fields', specialCharacters: 'Display names can contain letters, numbers and _ only' }, @@ -60,7 +60,7 @@ export default { register: 'Regƭstrate', signUp: 'Registro', confirmPassword: 'Confirmar ContraseƱa', - displayName: 'Nombre', + username: 'Nombre', alreadyHaveAnAccount: 'Ya tienes una cuenta?', recoverPassword: 'Recuperar contraseƱa', emailInUse: 'Este email se encuentra en uso', @@ -73,7 +73,7 @@ export default { 'createdisplay': { writeyourusername: 'Escribe tu nombre', yourusername: 'Tu nombre es visible publicamente en todos los comentarios que publiques. Es necesario tener un nombre de usuario antes de poder publicar tu primer comentario.', - displayName: 'Nombre a mostrar', + username: 'Nombre a mostrar', save: 'Guardar', requiredField: 'Campo necesario', errorCreate: 'Hubo un error al cambiar el nombre de usuario', diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 6358f8f06..10e8f34f6 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -515,7 +515,7 @@ paths: - name: value in: query type: string - description: A term to search users' displayNames and email addresses. + description: A term to search users' usernames and email addresses. - name: sort in: query type: string @@ -576,7 +576,7 @@ paths: format: email password: type: string - displayName: + username: type: string responses: 201: @@ -899,7 +899,7 @@ definitions: id: type: string description: The uuid.v4 id of the user. - displayName: + username: type: string description: The name appearing next to the user's comments. disabled: diff --git a/errors.js b/errors.js index 7ee932c96..662459cdf 100644 --- a/errors.js +++ b/errors.js @@ -59,12 +59,12 @@ const ErrDisplayTaken = new APIError('Display name already in use', { status: 400 }); -const ErrSpecialChars = new APIError('No special characters are allowed in a display name', { +const ErrSpecialChars = new APIError('No special characters are allowed in a username', { translation_key: 'NO_SPECIAL_CHARACTERS', status: 400 }); -const ErrMissingDisplay = new APIError('A display name is required to create a user', { +const ErrMissingDisplay = new APIError('A username is required to create a user', { translation_key: 'DISPLAY_NAME_REQUIRED', status: 400 }); diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index 8369b93c6..9426d128a 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -47,8 +47,8 @@ enum USER_ROLES { type User { id: ID! - # display name of a user. - displayName: String! + # username of a user. + username: String! # actions against a specific user. actions: [ActionSummary] diff --git a/models/user.js b/models/user.js index 06dc3c7d1..b142be5a0 100644 --- a/models/user.js +++ b/models/user.js @@ -12,7 +12,7 @@ const USER_STATUS = [ 'ACTIVE', 'BANNED', 'PENDING', - 'APPROVED' // Indicates that the users' displayname has been approved + 'APPROVED' // Indicates that the users' username has been approved ]; // ProfileSchema is the mongoose schema defined as the representation of a @@ -61,10 +61,8 @@ const UserSchema = new mongoose.Schema({ // This is sourced from the social provider or set manually during user setup // and simply provides a name to display for the given user. - displayName: { + username: { type: String, - unique: true, - lowercase: true, required: true }, @@ -128,6 +126,16 @@ UserSchema.index({ background: false }); +UserSchema.index({ + 'username': 1 +}, { + unique: true, + collation: { + locale: 'en_US', + strength: 2 + } +}); + /** * Returns true if the user has all the roles specified. */ diff --git a/routes/api/account/index.js b/routes/api/account/index.js index d383e9f36..3de9d41c3 100644 --- a/routes/api/account/index.js +++ b/routes/api/account/index.js @@ -115,9 +115,9 @@ router.put('/password/reset', (req, res, next) => { }); }); -router.put('/displayname', authorization.needed(), (req, res, next) => { +router.put('/username', authorization.needed(), (req, res, next) => { UsersService - .editName(req.user.id, req.body.displayName) + .editName(req.user.id, req.body.username) .then(() => { res.status(204).end(); }) diff --git a/routes/api/auth/index.js b/routes/api/auth/index.js index e3acf2c77..dcce15933 100644 --- a/routes/api/auth/index.js +++ b/routes/api/auth/index.js @@ -70,7 +70,7 @@ const HandleAuthPopupCallback = (req, res, next) => (err, user) => { return res.render('auth-callback', {err: JSON.stringify(errors.ErrNotAuthorized), data: null}); } - // Authorize the user to edit their displayName. + // Authorize the user to edit their username. UsersService.toggleNameEdit(user.id, true) .then(() => { diff --git a/routes/api/setup/index.js b/routes/api/setup/index.js index 60f7d7fd2..e325769cd 100644 --- a/routes/api/setup/index.js +++ b/routes/api/setup/index.js @@ -32,11 +32,11 @@ router.post('/', (req, res, next) => { const { settings, - user: {email, password, displayName} + user: {email, password, username} } = req.body; SetupService - .setup({settings, user: {email, password, displayName}}) + .setup({settings, user: {email, password, username}}) .then(() => { // We're setup! diff --git a/routes/api/users/index.js b/routes/api/users/index.js index 1ce429cb4..4182beb49 100644 --- a/routes/api/users/index.js +++ b/routes/api/users/index.js @@ -120,11 +120,11 @@ const SendEmailConfirmation = (app, userID, email, referer) => UsersService // create a local user. router.post('/', (req, res, next) => { - const {email, password, displayName} = req.body; + const {email, password, username} = req.body; const redirectUri = req.header('X-Pym-Url') || req.header('Referer'); UsersService - .createLocalUser(email, password, displayName) + .createLocalUser(email, password, username) .then((user) => { // Send an email confirmation. The Front end will know about the diff --git a/services/passport.js b/services/passport.js index dcb6904ec..b56834cc7 100644 --- a/services/passport.js +++ b/services/passport.js @@ -102,7 +102,7 @@ if (process.env.TALK_FACEBOOK_APP_ID && process.env.TALK_FACEBOOK_APP_SECRET && clientID: process.env.TALK_FACEBOOK_APP_ID, clientSecret: process.env.TALK_FACEBOOK_APP_SECRET, callbackURL: `${process.env.TALK_ROOT_URL}/api/v1/auth/facebook/callback`, - profileFields: ['id', 'displayName', 'picture.type(large)'] + profileFields: ['id', 'username', 'picture.type(large)'] }, (accessToken, refreshToken, profile, done) => { UsersService .findOrCreateExternalUser(profile) diff --git a/services/setup.js b/services/setup.js index 74ef1d431..2bfbc66a0 100644 --- a/services/setup.js +++ b/services/setup.js @@ -45,7 +45,7 @@ module.exports = class SetupService { /** * This verifies that the current input for the setup is valid. */ - static validate({settings, user: {email, displayName, password}}) { + static validate({settings, user: {email, username, password}}) { // Verify the email address of the user. if (!email) { @@ -57,7 +57,7 @@ module.exports = class SetupService { // Verify other properties of the user. return Promise.all([ - UsersService.isValidDisplayName(displayName, false), + UsersService.isValidDisplayName(username, false), UsersService.isValidPassword(password), settingsModel.validate() ]); @@ -66,11 +66,11 @@ module.exports = class SetupService { /** * This will perform the setup. */ - static setup({settings, user: {email, password, displayName}}) { + static setup({settings, user: {email, password, username}}) { // Validate the settings first. return SetupService - .validate({settings, user: {email, password, displayName}}) + .validate({settings, user: {email, password, username}}) .then(() => { return SettingsService.update(settings); }) @@ -80,7 +80,7 @@ module.exports = class SetupService { // Create the user. return UsersService - .createLocalUser(email, password, displayName) + .createLocalUser(email, password, username) // Grant them administrative privileges and confirm the email account. .then((user) => { diff --git a/services/users.js b/services/users.js index 408d5c618..22903daef 100644 --- a/services/users.js +++ b/services/users.js @@ -122,7 +122,7 @@ module.exports = class UsersService { // The user was not found, lets create them! user = new UserModel({ - displayName: profile.displayName, + username: profile.username, roles: [], profiles: [ { @@ -164,24 +164,24 @@ module.exports = class UsersService { static createLocalUsers(users) { return Promise.all(users.map((user) => { return UsersService - .createLocalUser(user.email, user.password, user.displayName); + .createLocalUser(user.email, user.password, user.username); })); } /** - * Check the requested displayname for naughty words (currently in English) and special chars - * @param {String} displayName word to be checked for profanity + * Check the requested username for blocked words and special chars + * @param {String} username word to be checked for profanity * @param {Boolean} checkAgainstWordlist enables cheching against the wordlist - * @return {Promise} rejected if the machine's sensibilites are offended + * @return {Promise} */ - static isValidDisplayName(displayName, checkAgainstWordlist = true) { + static isValidDisplayName(username, checkAgainstWordlist = true) { const onlyLettersNumbersUnderscore = /^[A-Za-z0-9_]+$/; - if (!displayName) { + if (!username) { return Promise.reject(errors.ErrMissingDisplay); } - if (!onlyLettersNumbersUnderscore.test(displayName)) { + if (!onlyLettersNumbersUnderscore.test(username)) { return Promise.reject(errors.ErrSpecialChars); } @@ -189,11 +189,11 @@ module.exports = class UsersService { if (checkAgainstWordlist) { // check for profanity - return Wordlist.displayNameCheck(displayName); + return Wordlist.usernameCheck(username); } // No errors found! - return Promise.resolve(displayName); + return Promise.resolve(username); } /** @@ -215,23 +215,23 @@ module.exports = class UsersService { * Creates the local user with a given email, password, and name. * @param {String} email email of the new user * @param {String} password plaintext password of the new user - * @param {String} displayName name of the display user + * @param {String} username name of the display user * @param {Function} done callback */ - static createLocalUser(email, password, displayName) { + static createLocalUser(email, password, username) { if (!email) { return Promise.reject(errors.ErrMissingEmail); } email = email.toLowerCase().trim(); - displayName = displayName.toLowerCase().trim(); + username = username.trim(); return Promise.all([ - UsersService.isValidDisplayName(displayName), + UsersService.isValidDisplayName(username), UsersService.isValidPassword(password) ]) - .then(() => { // displayName is valid + .then(() => { // username is valid return new Promise((resolve, reject) => { bcrypt.hash(password, SALT_ROUNDS, (err, hashedPassword) => { if (err) { @@ -239,7 +239,7 @@ module.exports = class UsersService { } let user = new UserModel({ - displayName: displayName, + username, password: hashedPassword, roles: [], profiles: [ @@ -253,7 +253,7 @@ module.exports = class UsersService { user.save((err) => { if (err) { if (err.code === 11000) { - if (err.message.match('displayName')) { + if (err.message.match('username')) { return reject(errors.ErrDisplayTaken); } return reject(errors.ErrEmailTaken); @@ -397,7 +397,7 @@ module.exports = class UsersService { static findPublicByIdArray(ids) { return UserModel.find({ id: {$in: ids} - }, 'id displayName'); + }, 'id username'); } /** @@ -473,7 +473,7 @@ module.exports = class UsersService { /** * Finds a user using a value which gets compared using a prefix match against - * the user's email address and/or their display name. + * the user's email address and/or their username. * @param {String} value value to search by * @return {Promise} */ @@ -481,9 +481,9 @@ module.exports = class UsersService { return UserModel.find({ $or: [ - // Search by a prefix match on the displayName. + // Search by a prefix match on the username. { - 'displayName': { + 'username': { $regex: new RegExp(`^${value}`), $options: 'i' } @@ -667,18 +667,18 @@ module.exports = class UsersService { } /** - * Updates the user's displayName. + * Updates the user's username. * @param {String} id the id of the user to be enabled. - * @param {String} displayName The new displayname for the user. + * @param {String} username The new username for the user. * @return {Promise} */ - static editName(id, displayName) { + static editName(id, username) { return UserModel.update({ id, canEditName: true }, { $set: { - displayName: displayName.toLowerCase(), + username: username, canEditName: false, status: 'PENDING' } diff --git a/services/wordlist.js b/services/wordlist.js index dde032f97..8dc16fba2 100644 --- a/services/wordlist.js +++ b/services/wordlist.js @@ -201,22 +201,22 @@ class Wordlist { /** * check potential username for banned words, special characters */ - static displayNameCheck(displayName) { + static usernameCheck(username) { const wl = new Wordlist(); return wl.load() .then(() => { - displayName = displayName.replace(/_/g, ''); + username = username.replace(/_/g, ''); // test each word, and fail if we find a match const hasBadWords = wl.lists.banned.some(phrase => { - return displayName.indexOf(phrase.join('')) !== -1; + return username.indexOf(phrase.join('')) !== -1; }); if (hasBadWords) { throw Errors.ErrContainsProfanity; } else { - return Promise.resolve(displayName); + return Promise.resolve(username); } }); } diff --git a/test/e2e/pages/embedStreamPage.js b/test/e2e/pages/embedStreamPage.js index 1dd467b10..28a9929ea 100644 --- a/test/e2e/pages/embedStreamPage.js +++ b/test/e2e/pages/embedStreamPage.js @@ -19,7 +19,7 @@ const embedStreamCommands = { .setValue('@signInDialogEmail', user.email) .setValue('@signInDialogPassword', user.pass) .setValue('@signUpDialogConfirmPassword', user.pass) - .setValue('@signUpDialogDisplayName', user.displayName) + .setValue('@signUpDialogDisplayName', user.username) .waitForElementVisible('@signUpButton') .click('@signUpButton') .waitForElementVisible('@signInViewTrigger') @@ -96,7 +96,7 @@ module.exports = { selector: '#signInDialog #confirmPassword' }, signUpDialogDisplayName: { - selector: '#signInDialog #displayName' + selector: '#signInDialog #username' }, logInButton: { selector: '#coralLogInButton' diff --git a/test/e2e/tests/EmbedStreamTests.js b/test/e2e/tests/EmbedStreamTests.js index 5591d6b8d..78d13ccfa 100644 --- a/test/e2e/tests/EmbedStreamTests.js +++ b/test/e2e/tests/EmbedStreamTests.js @@ -37,7 +37,7 @@ module.exports = { .click('#coralRegister') .waitForElementVisible('#email', 1000) .setValue('#email', mockUser.email) - .setValue('#displayName', mockUser.name) + .setValue('#username', mockUser.name) .setValue('#password', mockUser.pw) .setValue('#confirmPassword', mockUser.pw) .click('#coralSignUpButton') @@ -125,7 +125,7 @@ module.exports = { // Add a mock user .then(() => mocks.users([{ - displayName: 'Baby Blue', + username: 'Baby Blue', email: 'whale@tale.sea', password: 'krill' }])) diff --git a/test/e2e/tests/Visitor/SignUpTest.js b/test/e2e/tests/Visitor/SignUpTest.js index d19ca1d8c..253b41659 100644 --- a/test/e2e/tests/Visitor/SignUpTest.js +++ b/test/e2e/tests/Visitor/SignUpTest.js @@ -13,7 +13,7 @@ module.exports = { embedStreamPage .signUp({ email: `visitor_${Date.now()}@test.com`, - displayName: `visitor${Date.now()}`, + username: `visitor${Date.now()}`, pass: 'testtest' }); }, diff --git a/test/routes/api/account/index.js b/test/routes/api/account/index.js index 5b9acfa9d..3a4493279 100644 --- a/test/routes/api/account/index.js +++ b/test/routes/api/account/index.js @@ -13,7 +13,7 @@ chai.use(require('chai-http')); const UsersService = require('../../../../services/users'); -describe('/api/v1/account/displayname', () => { +describe('/api/v1/account/username', () => { let mockUser; beforeEach(() => SettingsService.init(settings).then(() => { @@ -29,9 +29,9 @@ describe('/api/v1/account/displayname', () => { .post(`/api/v1/users/${mockUser.id}/username-enable`) .set(passport.inject({id: '456', roles: ['ADMIN']})) .then(() => chai.request(app) - .put('/api/v1/account/displayname') + .put('/api/v1/account/username') .set(passport.inject({id: mockUser.id, roles: []})) - .send({displayName: 'MojoJojo'})) + .send({username: 'MojoJojo'})) .then((res) => { expect(res).to.have.status(204); }); @@ -42,9 +42,9 @@ describe('/api/v1/account/displayname', () => { .post(`/api/v1/users/${mockUser.id}/username-enable`) .set(passport.inject({id: '456', roles: ['ADMIN']})) .then(() => chai.request(app) - .put('/api/v1/account/displayname') + .put('/api/v1/account/username') .set(passport.inject({id: 'wrongid', roles: []})) - .send({displayName: 'MojoJojo'})) + .send({username: 'MojoJojo'})) .then(() => { done(new Error('Exected Error')); }) @@ -56,7 +56,7 @@ describe('/api/v1/account/displayname', () => { 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') + .put('/api/v1/account/username') .set(passport.inject({id: mockUser.id, roles: []})) .send({username: 'MojoJojo'}) .then(() => { diff --git a/test/routes/api/auth/index.js b/test/routes/api/auth/index.js index 9f3937a91..38b867b7d 100644 --- a/test/routes/api/auth/index.js +++ b/test/routes/api/auth/index.js @@ -45,7 +45,7 @@ describe('/api/v1/auth/local', () => { expect(res2).to.have.status(200); expect(res2).to.be.json; expect(res2.body).to.have.property('user'); - expect(res2.body.user).to.have.property('displayName', 'maria'); + expect(res2.body.user).to.have.property('username', 'Maria'); }); }); @@ -91,7 +91,7 @@ describe('/api/v1/auth/local', () => { expect(res).to.have.status(200); expect(res).to.be.json; expect(res.body).to.have.property('user'); - expect(res.body.user).to.have.property('displayName', 'maria'); + expect(res.body.user).to.have.property('username', 'Maria'); }); }); }); diff --git a/test/routes/api/comments/index.js b/test/routes/api/comments/index.js index c305f9f6b..698c5870e 100644 --- a/test/routes/api/comments/index.js +++ b/test/routes/api/comments/index.js @@ -49,11 +49,11 @@ describe('/api/v1/comments', () => { }]; const users = [{ - displayName: 'Ana', + username: 'Ana', email: 'ana@gmail.com', password: '123456789' }, { - displayName: 'Maria', + username: 'Maria', email: 'maria@gmail.com', password: '123456789' }]; @@ -183,11 +183,11 @@ describe('/api/v1/comments/:comment_id', () => { }]; const users = [{ - displayName: 'Ana', + username: 'Ana', email: 'ana@gmail.com', password: '123456789' }, { - displayName: 'Maria', + username: 'Maria', email: 'maria@gmail.com', password: '123456789' }]; @@ -296,11 +296,11 @@ describe('/api/v1/comments/:comment_id/actions', () => { }]; const users = [{ - displayName: 'Ana', + username: 'Ana', email: 'ana@gmail.com', password: '123456789' }, { - displayName: 'Maria', + username: 'Maria', email: 'maria@gmail.com', password: '123456789' }]; diff --git a/test/routes/api/queue/index.js b/test/routes/api/queue/index.js index 9f89a961e..bca68f355 100644 --- a/test/routes/api/queue/index.js +++ b/test/routes/api/queue/index.js @@ -45,11 +45,11 @@ describe('/api/v1/queue', () => { }]; const users = [{ - displayName: 'Ana', + username: 'Ana', email: 'ana@gmail.com', password: '123456789' }, { - displayName: 'Maria', + username: 'Maria', email: 'maria@gmail.com', password: '123456789' }]; @@ -103,7 +103,7 @@ describe('/api/v1/queue', () => { expect(res).to.have.status(200); expect(res.body.comments).to.have.length(1); expect(res.body.comments[0]).to.have.property('body'); - expect(res.body.users[0]).to.have.property('displayName'); + expect(res.body.users[0]).to.have.property('username'); expect(res.body.actions[0]).to.have.property('action_type'); }); }); @@ -115,7 +115,7 @@ describe('/api/v1/queue', () => { .end(function(err, res){ expect(err).to.be.null; expect(res).to.have.status(200); - expect(res.body.users[0]).to.have.property('displayName'); + expect(res.body.users[0]).to.have.property('username'); expect(res.body.actions[0]).to.have.property('action_type'); done(); }); diff --git a/test/services/comments.js b/test/services/comments.js index fc2105cb1..0de4e9d4b 100644 --- a/test/services/comments.js +++ b/test/services/comments.js @@ -69,11 +69,11 @@ describe('services.CommentsService', () => { const users = [{ email: 'stampi@gmail.com', - displayName: 'Stampi', + username: 'Stampi', password: '1Coral!!' }, { email: 'sockmonster@gmail.com', - displayName: 'Sockmonster', + username: 'Sockmonster', password: '2Coral!!' }]; diff --git a/test/services/users.js b/test/services/users.js index 5774d45ae..51ba01403 100644 --- a/test/services/users.js +++ b/test/services/users.js @@ -12,15 +12,15 @@ describe('services.UsersService', () => { return SettingsService.init(settings).then(() => { return UsersService.createLocalUsers([{ email: 'stampi@gmail.com', - displayName: 'Stampi', + username: 'Stampi', password: '1Coral!-' }, { email: 'sockmonster@gmail.com', - displayName: 'Sockmonster', + username: 'Sockmonster', password: '2Coral!2' }, { email: 'marvel@gmail.com', - displayName: 'Marvel', + username: 'Marvel', password: '3Coral!3' }]).then((users) => { mockUsers = users; @@ -33,7 +33,7 @@ describe('services.UsersService', () => { return UsersService .findById(mockUsers[0].id) .then((user) => { - expect(user).to.have.property('displayName', 'stampi'); + expect(user).to.have.property('username', 'Stampi'); }); }); }); @@ -53,11 +53,11 @@ describe('services.UsersService', () => { return UsersService.findPublicByIdArray(ids).then((result) => { expect(result).to.have.length(3); const sorted = result.sort((a, b) => { - if(a.displayName < b.displayName) {return -1;} - if(a.displayName > b.displayName) {return 1;} + if(a.username < b.username) {return -1;} + if(a.username > b.username) {return 1;} return 0; }); - expect(sorted[0]).to.have.property('displayName', 'marvel'); + expect(sorted[0]).to.have.property('username', 'Marvel'); }); }); }); @@ -68,8 +68,7 @@ describe('services.UsersService', () => { return UsersService .findLocalUser(mockUsers[0].profiles[0].id, '1Coral!-') .then((user) => { - expect(user).to.have.property('displayName') - .and.to.equal(mockUsers[0].displayName.toLowerCase()); + expect(user).to.have.property('username', mockUsers[0].username); }); }); @@ -84,10 +83,10 @@ describe('services.UsersService', () => { }); describe('#createLocalUser', () => { - it('should not create a user with duplicate display name', () => { + it('should not create a user with duplicate username', () => { return UsersService.createLocalUsers([{ email: 'otrostampi@gmail.com', - displayName: 'StampiTheSecond', + username: 'StampiTheSecond', password: '1Coralito!' }]) .then((user) => { @@ -227,7 +226,7 @@ describe('services.UsersService', () => { .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('username', 'Jojo'); expect(user).to.have.property('canEditName', false); }); }); diff --git a/views/auth-callback.ejs b/views/auth-callback.ejs index d38d759ee..66b9cfede 100644 --- a/views/auth-callback.ejs +++ b/views/auth-callback.ejs @@ -3,7 +3,7 @@ From 7db3da5fdd5d7104962db92ddcbed137e1b4e5c9 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 9 Feb 2017 17:03:41 -0700 Subject: [PATCH 09/28] Added fix for facebook --- services/passport.js | 5 ++++- services/users.js | 18 ++++++++---------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/services/passport.js b/services/passport.js index b56834cc7..d265d9812 100644 --- a/services/passport.js +++ b/services/passport.js @@ -102,7 +102,10 @@ if (process.env.TALK_FACEBOOK_APP_ID && process.env.TALK_FACEBOOK_APP_SECRET && clientID: process.env.TALK_FACEBOOK_APP_ID, clientSecret: process.env.TALK_FACEBOOK_APP_SECRET, callbackURL: `${process.env.TALK_ROOT_URL}/api/v1/auth/facebook/callback`, - profileFields: ['id', 'username', 'picture.type(large)'] + + // TODO: remove displayName reference when we have steps in the FE to handle + // the username create flow. + profileFields: ['id', 'displayName', 'picture.type(large)'] }, (accessToken, refreshToken, profile, done) => { UsersService .findOrCreateExternalUser(profile) diff --git a/services/users.js b/services/users.js index 22903daef..97cf3e125 100644 --- a/services/users.js +++ b/services/users.js @@ -105,13 +105,13 @@ module.exports = class UsersService { * @param {Object} profile - User social/external profile * @param {Function} done [description] */ - static findOrCreateExternalUser(profile) { + static findOrCreateExternalUser({id, provider, displayName}) { return UserModel .findOne({ profiles: { $elemMatch: { - id: profile.id, - provider: profile.provider + id, + provider } } }) @@ -122,14 +122,12 @@ module.exports = class UsersService { // The user was not found, lets create them! user = new UserModel({ - username: profile.username, + + // TODO: remove displayName reference when we have steps in the FE to handle + // the username create flow. + username: displayName.replace(/ /g, '_').replace(/[^a-zA-Z_]/g, ''), roles: [], - profiles: [ - { - id: profile.id, - provider: profile.provider - } - ] + profiles: [{id, provider}] }); return user.save(); From 229ddb11f20631ce1503ec080407f27273dec467 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 9 Feb 2017 17:37:39 -0700 Subject: [PATCH 10/28] Adjusted circle --- INSTALL.md | 2 +- circle.yml | 109 +++++++++++++++++++++++++++++++++-------------------- 2 files changed, 70 insertions(+), 41 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 008c4cba3..33b634de8 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -15,7 +15,7 @@ There are some runtime requirements for running Talk from source: - [Node](https://nodejs.org/) v7 or later -- [MongoDB](https://www.mongodb.com/) v3.2 or later +- [MongoDB](https://www.mongodb.com/) v3.4 or later - [Redis](https://redis.io/) v3.2 or later - [Yarn](https://yarnpkg.com/) v0.19.1 or later diff --git a/circle.yml b/circle.yml index bae929df7..a467e8bba 100644 --- a/circle.yml +++ b/circle.yml @@ -1,45 +1,74 @@ -machine: - node: - version: 7 - services: - - docker - - redis - environment: - PATH: "${PATH}:${HOME}/${CIRCLE_PROJECT_REPONAME}/node_modules/.bin" - NODE_ENV: "test" +version: 2 -dependencies: - override: - - yarn - cache_directories: - - ~/.cache/yarn - post: - # Build the static assets - - yarn build - # Lint the project here, before tests are ran. - - yarn lint +containerInfo: + - image: node:7 + - image: mongo:3.4 + - image: redis:3.2 -database: - post: - # Initialize the settings in the database, this will create indicies for the - # database. - - ./bin/cli setup --defaults - - sleep 2 +stages: + build: + workDir: ~/talk + environment: + - "PATH=$PATH:~/talk/node_modules/.bin" + - "NODE_ENV=test" + steps: + # Get the code + - type: checkout -test: - override: - # Run the tests using the junit reporter. - - MOCHA_FILE=$CIRCLE_TEST_REPORTS/junit/test-results.xml MOCHA_REPORTER=mocha-junit-reporter yarn test - # Run the e2e test suite - - E2E_REPORT_PATH=$CIRCLE_TEST_REPORTS/e2e yarn e2e + # Restore the yarn cache + - type: cache-restore + key: yarn-cache -deployment: - release: - tag: /v[0-9]+(\.[0-9]+)*/ - commands: - - bash ./scripts/deploy.sh + # Install dependencies + - type: shell + shell: /bin/bash + command: | + # install yarn + npm install -g yarn - latest: - branch: master - commands: - - bash ./scripts/deploy.sh + # install dependencies + yarn + + # Save the yarn cache + - type: cache-save + key: yarn-cache + paths: + - ~/.cache/yarn + + # Build the dependencies + - type: shell + shell: /bin/bash + command: | + # build static dependencies + yarn build + + # lint the project + yarn lint + + - type: shell + shell: /bin/bash + command: | + # Initialize the settings in the database, this will create indicies + # for the database. + ./bin/cli setup --defaults + + # Ugly fix to wait until database indicies are created. + sleep 2 + + # Run the tests using the junit reporter. + MOCHA_FILE=$CIRCLE_TEST_REPORTS/junit/test-results.xml MOCHA_REPORTER=mocha-junit-reporter yarn test + + # Run the e2e test suite + E2E_REPORT_PATH=$CIRCLE_TEST_REPORTS/e2e yarn e2e + +# TODO: fix +# deployment: +# release: +# tag: /v[0-9]+(\.[0-9]+)*/ +# commands: +# - bash ./scripts/deploy.sh +# +# latest: +# branch: master +# commands: +# - bash ./scripts/deploy.sh From 832d1f67635ef1525004f4503667eefeb196a552 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 9 Feb 2017 18:02:25 -0700 Subject: [PATCH 11/28] Revert "Adjusted circle" This reverts commit 229ddb11f20631ce1503ec080407f27273dec467. --- INSTALL.md | 2 +- circle.yml | 109 ++++++++++++++++++++--------------------------------- 2 files changed, 41 insertions(+), 70 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 33b634de8..008c4cba3 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -15,7 +15,7 @@ There are some runtime requirements for running Talk from source: - [Node](https://nodejs.org/) v7 or later -- [MongoDB](https://www.mongodb.com/) v3.4 or later +- [MongoDB](https://www.mongodb.com/) v3.2 or later - [Redis](https://redis.io/) v3.2 or later - [Yarn](https://yarnpkg.com/) v0.19.1 or later diff --git a/circle.yml b/circle.yml index a467e8bba..bae929df7 100644 --- a/circle.yml +++ b/circle.yml @@ -1,74 +1,45 @@ -version: 2 +machine: + node: + version: 7 + services: + - docker + - redis + environment: + PATH: "${PATH}:${HOME}/${CIRCLE_PROJECT_REPONAME}/node_modules/.bin" + NODE_ENV: "test" -containerInfo: - - image: node:7 - - image: mongo:3.4 - - image: redis:3.2 +dependencies: + override: + - yarn + cache_directories: + - ~/.cache/yarn + post: + # Build the static assets + - yarn build + # Lint the project here, before tests are ran. + - yarn lint -stages: - build: - workDir: ~/talk - environment: - - "PATH=$PATH:~/talk/node_modules/.bin" - - "NODE_ENV=test" - steps: - # Get the code - - type: checkout +database: + post: + # Initialize the settings in the database, this will create indicies for the + # database. + - ./bin/cli setup --defaults + - sleep 2 - # Restore the yarn cache - - type: cache-restore - key: yarn-cache +test: + override: + # Run the tests using the junit reporter. + - MOCHA_FILE=$CIRCLE_TEST_REPORTS/junit/test-results.xml MOCHA_REPORTER=mocha-junit-reporter yarn test + # Run the e2e test suite + - E2E_REPORT_PATH=$CIRCLE_TEST_REPORTS/e2e yarn e2e - # Install dependencies - - type: shell - shell: /bin/bash - command: | - # install yarn - npm install -g yarn +deployment: + release: + tag: /v[0-9]+(\.[0-9]+)*/ + commands: + - bash ./scripts/deploy.sh - # install dependencies - yarn - - # Save the yarn cache - - type: cache-save - key: yarn-cache - paths: - - ~/.cache/yarn - - # Build the dependencies - - type: shell - shell: /bin/bash - command: | - # build static dependencies - yarn build - - # lint the project - yarn lint - - - type: shell - shell: /bin/bash - command: | - # Initialize the settings in the database, this will create indicies - # for the database. - ./bin/cli setup --defaults - - # Ugly fix to wait until database indicies are created. - sleep 2 - - # Run the tests using the junit reporter. - MOCHA_FILE=$CIRCLE_TEST_REPORTS/junit/test-results.xml MOCHA_REPORTER=mocha-junit-reporter yarn test - - # Run the e2e test suite - E2E_REPORT_PATH=$CIRCLE_TEST_REPORTS/e2e yarn e2e - -# TODO: fix -# deployment: -# release: -# tag: /v[0-9]+(\.[0-9]+)*/ -# commands: -# - bash ./scripts/deploy.sh -# -# latest: -# branch: master -# commands: -# - bash ./scripts/deploy.sh + latest: + branch: master + commands: + - bash ./scripts/deploy.sh From c202042cdaaebb56331ea48e000c94299e33dc52 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 9 Feb 2017 18:04:31 -0700 Subject: [PATCH 12/28] Revert "Revert "Adjusted circle"" This reverts commit 832d1f67635ef1525004f4503667eefeb196a552. --- INSTALL.md | 2 +- circle.yml | 109 +++++++++++++++++++++++++++++++++-------------------- 2 files changed, 70 insertions(+), 41 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 008c4cba3..33b634de8 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -15,7 +15,7 @@ There are some runtime requirements for running Talk from source: - [Node](https://nodejs.org/) v7 or later -- [MongoDB](https://www.mongodb.com/) v3.2 or later +- [MongoDB](https://www.mongodb.com/) v3.4 or later - [Redis](https://redis.io/) v3.2 or later - [Yarn](https://yarnpkg.com/) v0.19.1 or later diff --git a/circle.yml b/circle.yml index bae929df7..a467e8bba 100644 --- a/circle.yml +++ b/circle.yml @@ -1,45 +1,74 @@ -machine: - node: - version: 7 - services: - - docker - - redis - environment: - PATH: "${PATH}:${HOME}/${CIRCLE_PROJECT_REPONAME}/node_modules/.bin" - NODE_ENV: "test" +version: 2 -dependencies: - override: - - yarn - cache_directories: - - ~/.cache/yarn - post: - # Build the static assets - - yarn build - # Lint the project here, before tests are ran. - - yarn lint +containerInfo: + - image: node:7 + - image: mongo:3.4 + - image: redis:3.2 -database: - post: - # Initialize the settings in the database, this will create indicies for the - # database. - - ./bin/cli setup --defaults - - sleep 2 +stages: + build: + workDir: ~/talk + environment: + - "PATH=$PATH:~/talk/node_modules/.bin" + - "NODE_ENV=test" + steps: + # Get the code + - type: checkout -test: - override: - # Run the tests using the junit reporter. - - MOCHA_FILE=$CIRCLE_TEST_REPORTS/junit/test-results.xml MOCHA_REPORTER=mocha-junit-reporter yarn test - # Run the e2e test suite - - E2E_REPORT_PATH=$CIRCLE_TEST_REPORTS/e2e yarn e2e + # Restore the yarn cache + - type: cache-restore + key: yarn-cache -deployment: - release: - tag: /v[0-9]+(\.[0-9]+)*/ - commands: - - bash ./scripts/deploy.sh + # Install dependencies + - type: shell + shell: /bin/bash + command: | + # install yarn + npm install -g yarn - latest: - branch: master - commands: - - bash ./scripts/deploy.sh + # install dependencies + yarn + + # Save the yarn cache + - type: cache-save + key: yarn-cache + paths: + - ~/.cache/yarn + + # Build the dependencies + - type: shell + shell: /bin/bash + command: | + # build static dependencies + yarn build + + # lint the project + yarn lint + + - type: shell + shell: /bin/bash + command: | + # Initialize the settings in the database, this will create indicies + # for the database. + ./bin/cli setup --defaults + + # Ugly fix to wait until database indicies are created. + sleep 2 + + # Run the tests using the junit reporter. + MOCHA_FILE=$CIRCLE_TEST_REPORTS/junit/test-results.xml MOCHA_REPORTER=mocha-junit-reporter yarn test + + # Run the e2e test suite + E2E_REPORT_PATH=$CIRCLE_TEST_REPORTS/e2e yarn e2e + +# TODO: fix +# deployment: +# release: +# tag: /v[0-9]+(\.[0-9]+)*/ +# commands: +# - bash ./scripts/deploy.sh +# +# latest: +# branch: master +# commands: +# - bash ./scripts/deploy.sh From fded4f0a917ab636755a5ea091e23844d718facf Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 9 Feb 2017 18:05:47 -0700 Subject: [PATCH 13/28] Added new machine type --- circle.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/circle.yml b/circle.yml index a467e8bba..90da5084e 100644 --- a/circle.yml +++ b/circle.yml @@ -1,5 +1,7 @@ version: 2 +executorType: machine + containerInfo: - image: node:7 - image: mongo:3.4 From bc14b908ebcde65a5bf44d53dc9809e86f816b0b Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Fri, 10 Feb 2017 17:15:26 -0300 Subject: [PATCH 14/28] banUsers and moderate separate streams, not found assets etc --- client/coral-admin/src/actions/assets.js | 8 +- client/coral-admin/src/actions/comments.js | 9 --- client/coral-admin/src/actions/moderation.js | 9 +++ .../coral-admin/src/components/ui/Header.css | 1 + client/coral-admin/src/constants/assets.js | 3 + .../coral-admin/src/constants/moderation.js | 2 + .../ModerationQueue/ModerationContainer.js | 81 ++++++++----------- .../ModerationQueue/components/Comment.js | 17 +++- .../components/ModerationHeader.js | 25 ++++++ ...rationQueueHeader.js => ModerationMenu.js} | 14 +--- .../components/NotFoundAsset.js | 14 ++++ .../ModerationQueue/components/styles.css | 59 ++++++++++++++ .../src/containers/Streams/Streams.css | 6 ++ .../src/containers/Streams/Streams.js | 36 ++++----- .../src/containers/Streams/StreamsTable.js | 39 +++++++++ .../src/graphql/queries/assetsQuery.graphql | 6 ++ .../src/graphql/queries/modQueueQuery.graphql | 10 +-- client/coral-admin/src/reducers/assets.js | 22 +++-- graph/mutators/index.js | 2 + graph/mutators/user.js | 34 ++++++++ graph/resolvers/root_mutation.js | 3 + graph/typeDefs.graphql | 12 +++ models/user.js | 3 +- 23 files changed, 310 insertions(+), 105 deletions(-) create mode 100644 client/coral-admin/src/containers/ModerationQueue/components/ModerationHeader.js rename client/coral-admin/src/containers/ModerationQueue/components/{ModerationQueueHeader.js => ModerationMenu.js} (80%) create mode 100644 client/coral-admin/src/containers/ModerationQueue/components/NotFoundAsset.js create mode 100644 client/coral-admin/src/containers/Streams/StreamsTable.js create mode 100644 client/coral-admin/src/graphql/queries/assetsQuery.graphql create mode 100644 graph/mutators/user.js diff --git a/client/coral-admin/src/actions/assets.js b/client/coral-admin/src/actions/assets.js index f431f1ad6..a4a3513a8 100644 --- a/client/coral-admin/src/actions/assets.js +++ b/client/coral-admin/src/actions/assets.js @@ -4,8 +4,10 @@ import { FETCH_ASSETS_FAILURE, UPDATE_ASSET_STATE_REQUEST, UPDATE_ASSET_STATE_SUCCESS, - UPDATE_ASSET_STATE_FAILURE + UPDATE_ASSET_STATE_FAILURE, + UPDATE_ASSETS } from '../constants/assets'; + import coralApi from '../../../coral-framework/helpers/response'; /** @@ -34,3 +36,7 @@ export const updateAssetState = (id, closedAt) => (dispatch) => { dispatch({type: UPDATE_ASSET_STATE_SUCCESS})) .catch(error => dispatch({type: UPDATE_ASSET_STATE_FAILURE, error})); }; + +export const updateAssets = assets => dispatch => { + dispatch({type: UPDATE_ASSETS, assets}); +}; diff --git a/client/coral-admin/src/actions/comments.js b/client/coral-admin/src/actions/comments.js index ac7af12fd..14f33bf36 100644 --- a/client/coral-admin/src/actions/comments.js +++ b/client/coral-admin/src/actions/comments.js @@ -101,12 +101,3 @@ export const flagComment = id => (dispatch, getState) => { dispatch({type: commentTypes.COMMENT_FLAG, id}); dispatch({type: 'COMMENT_UPDATE', comment: getState().comments.get('byId').get(id)}); }; - -// Dialog Actions -export const showBanUserDialog = (userId, userName, commentId) => { - return {type: commentTypes.SHOW_BANUSER_DIALOG, userId, userName, commentId}; -}; - -export const hideBanUserDialog = (showDialog) => { - return {type: commentTypes.HIDE_BANUSER_DIALOG, showDialog}; -}; diff --git a/client/coral-admin/src/actions/moderation.js b/client/coral-admin/src/actions/moderation.js index 425628abe..7d34a14ed 100644 --- a/client/coral-admin/src/actions/moderation.js +++ b/client/coral-admin/src/actions/moderation.js @@ -3,3 +3,12 @@ import * as actions from 'constants/moderation'; export const setActiveTab = activeTab => ({type: actions.SET_ACTIVE_TAB, activeTab}); export const toggleModal = open => ({type: actions.TOGGLE_MODAL, open}); export const singleView = () => ({type: actions.SINGLE_VIEW}); + +// Ban User Dialog +export const showBanUserDialog = (userId, userName, commentId) => { + return {type: actions.SHOW_BANUSER_DIALOG, userId, userName, commentId}; +}; + +export const hideBanUserDialog = (showDialog) => { + return {type: actions.HIDE_BANUSER_DIALOG, showDialog}; +}; diff --git a/client/coral-admin/src/components/ui/Header.css b/client/coral-admin/src/components/ui/Header.css index 8fbdedc0a..e6cf85d23 100644 --- a/client/coral-admin/src/components/ui/Header.css +++ b/client/coral-admin/src/components/ui/Header.css @@ -16,6 +16,7 @@ .rightPanel { position: absolute; + top: 0; right: 0; width: 170px; height: 100%; diff --git a/client/coral-admin/src/constants/assets.js b/client/coral-admin/src/constants/assets.js index 0a2ecf73c..20ec0a9c3 100644 --- a/client/coral-admin/src/constants/assets.js +++ b/client/coral-admin/src/constants/assets.js @@ -1,6 +1,9 @@ export const FETCH_ASSETS_REQUEST = 'FETCH_ASSETS_REQUEST'; export const FETCH_ASSETS_SUCCESS = 'FETCH_ASSETS_SUCCESS'; export const FETCH_ASSETS_FAILURE = 'FETCH_ASSETS_FAILURE'; + export const UPDATE_ASSET_STATE_REQUEST = 'UPDATE_ASSET_STATE_REQUEST'; export const UPDATE_ASSET_STATE_SUCCESS = 'UPDATE_ASSET_STATE_SUCCESS'; export const UPDATE_ASSET_STATE_FAILURE = 'UPDATE_ASSET_STATE_FAILURE'; + +export const UPDATE_ASSETS = 'UPDATE_ASSETS'; diff --git a/client/coral-admin/src/constants/moderation.js b/client/coral-admin/src/constants/moderation.js index 0fa51dc7b..10c6a7c4c 100644 --- a/client/coral-admin/src/constants/moderation.js +++ b/client/coral-admin/src/constants/moderation.js @@ -1,3 +1,5 @@ export const SET_ACTIVE_TAB = 'SET_ACTIVE_TAB'; export const TOGGLE_MODAL = 'TOGGLE_MODAL'; export const SINGLE_VIEW = 'SINGLE_VIEW'; +export const SHOW_BANUSER_DIALOG = 'SHOW_BANUSER_DIALOG'; +export const HIDE_BANUSER_DIALOG = 'HIDE_BANUSER_DIALOG'; diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js index 277a0344b..ae78d1b8e 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js +++ b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js @@ -1,28 +1,20 @@ import React, {Component} from 'react'; -import key from 'keymaster'; import {connect} from 'react-redux'; import {compose} from 'react-apollo'; -import {Spinner} from 'coral-ui'; -import {withRouter} from 'react-router'; +import key from 'keymaster'; +import isEqual from 'lodash/isEqual'; import {modQueueQuery} from '../../graphql/queries'; -import { - updateStatus, - showBanUserDialog, - hideBanUserDialog, - fetchPremodQueue, - fetchRejectedQueue, - fetchFlaggedQueue, - fetchModerationQueueComments, -} from 'actions/comments'; - import {fetchSettings} from 'actions/settings'; -import {userStatusUpdate, sendNotificationEmail} from 'actions/users'; +import {updateAssets} from 'actions/assets'; import {setActiveTab, toggleModal, singleView} from 'actions/moderation'; +import {Spinner} from 'coral-ui'; import ModerationQueue from './ModerationQueue'; -import ModerationQueueHeader from './components/ModerationQueueHeader'; +import ModerationMenu from './components/ModerationMenu'; +import ModerationHeader from './components/ModerationHeader'; +import NotFoundAsset from './components/NotFoundAsset'; class ModerationContainer extends Component { @@ -42,29 +34,37 @@ class ModerationContainer extends Component { key.unbind('esc'); } - onTabClick = (activeTab) => { - const {setActiveTab} = this.props; - setActiveTab(activeTab); - } - - onClose = () => { - const {toggleModal} = this.props; - toggleModal(false); + componentWillReceiveProps(nextProps) { + const {updateAssets} = this.props; + if(!isEqual(nextProps.data.assets, this.props.data.assets)) { + updateAssets(nextProps.data.assets); + } } render () { - const {data, moderation, settings} = this.props; + const {data, moderation, settings, assets} = this.props; + const providedAssetId = this.props.params.id; + let asset; if (data.loading) { return
    ; } - const enablePremodTab = data.premod.length; + if (providedAssetId) { + asset = assets.find(asset => asset.id === this.props.params.id); + + if (!asset) { + return ; + } + } + + const enablePremodTab = !!data.premod.length; return (
    - + ({ moderation: state.moderation.toJS(), - settings: state.settings.toJS() + settings: state.settings.toJS(), + assets: state.assets.get('assets') }); const mapDispatchToProps = dispatch => ({ - setActiveTab: tab => dispatch(setActiveTab(tab)), - toggleModal: open => dispatch(toggleModal(open)), + onTabClick: activeTab => dispatch(setActiveTab(activeTab)), + toggleModal: toggle => dispatch(toggleModal(toggle)), + onClose: () => dispatch(toggleModal(false)), singleView: () => dispatch(singleView()), - - fetchSettings: () => dispatch(fetchSettings()), - fetchModerationQueueComments: () => dispatch(fetchModerationQueueComments()), - fetchPremodQueue: () => dispatch(fetchPremodQueue()), - fetchRejectedQueue: () => dispatch(fetchRejectedQueue()), - fetchFlaggedQueue: () => dispatch(fetchFlaggedQueue()), - showBanUserDialog: (userId, userName, commentId) => dispatch(showBanUserDialog(userId, userName, commentId)), - hideBanUserDialog: () => dispatch(hideBanUserDialog(false)), - userStatusUpdate: (status, userId, commentId) => dispatch(userStatusUpdate(status, userId, commentId)).then(() => { - dispatch(fetchModerationQueueComments()); - }), - suspendUser: (userId, subject, text) => dispatch(userStatusUpdate('suspended', userId)) - .then(() => dispatch(sendNotificationEmail(userId, subject, text))) - .then(() => dispatch(fetchModerationQueueComments())) - , - updateStatus: (action, comment) => dispatch(updateStatus(action, comment)) + updateAssets: assets => dispatch(updateAssets(assets)), + fetchSettings: () => dispatch(fetchSettings()) }); export default compose( connect(mapStateToProps, mapDispatchToProps), - withRouter, modQueueQuery )(ModerationContainer); diff --git a/client/coral-admin/src/containers/ModerationQueue/components/Comment.js b/client/coral-admin/src/containers/ModerationQueue/components/Comment.js index 771d6fe55..881675d6a 100644 --- a/client/coral-admin/src/containers/ModerationQueue/components/Comment.js +++ b/client/coral-admin/src/containers/ModerationQueue/components/Comment.js @@ -14,7 +14,6 @@ import translations from '../../../translations.json'; const Comment = props => { const links = linkify.getMatches(props.body); - return (
  • @@ -50,14 +49,24 @@ const Comment = props => { : null} + + {/*
    */} + {/* Article title */} + {/* Moderate this Article */} + {/*
    */} +
    - +

    - +

    - View context + + {/* */} + {/* View context */} + {/* */} +
  • ); }; diff --git a/client/coral-admin/src/containers/ModerationQueue/components/ModerationHeader.js b/client/coral-admin/src/containers/ModerationQueue/components/ModerationHeader.js new file mode 100644 index 000000000..474d8fd27 --- /dev/null +++ b/client/coral-admin/src/containers/ModerationQueue/components/ModerationHeader.js @@ -0,0 +1,25 @@ +import React from 'react'; +import {Link} from 'react-router'; +import styles from './styles.css'; + +const ModerationHeader = props => ( +
    +
    + { + props.asset ? +
    + All Streams + {props.asset.title} + Select Stream +
    + : +
    + + All Streams + Select Stream +
    + } +
    +
    +); +export default ModerationHeader; diff --git a/client/coral-admin/src/containers/ModerationQueue/components/ModerationQueueHeader.js b/client/coral-admin/src/containers/ModerationQueue/components/ModerationMenu.js similarity index 80% rename from client/coral-admin/src/containers/ModerationQueue/components/ModerationQueueHeader.js rename to client/coral-admin/src/containers/ModerationQueue/components/ModerationMenu.js index 06540537b..803f62ecd 100644 --- a/client/coral-admin/src/containers/ModerationQueue/components/ModerationQueueHeader.js +++ b/client/coral-admin/src/containers/ModerationQueue/components/ModerationMenu.js @@ -5,7 +5,7 @@ import translations from '../../../translations.json'; const lang = new I18n(translations); -const ModerationQueueHeader = (props) => ( +const ModerationMenu = (props) => (
    ); -ModerationQueueHeader.propTypes = { +ModerationMenu.propTypes = { activeTab: PropTypes.string.isRequired, enablePremodTab: PropTypes.bool }; -export default ModerationQueueHeader; +export default ModerationMenu; diff --git a/client/coral-admin/src/containers/ModerationQueue/components/NotFoundAsset.js b/client/coral-admin/src/containers/ModerationQueue/components/NotFoundAsset.js new file mode 100644 index 000000000..ffa1adfcd --- /dev/null +++ b/client/coral-admin/src/containers/ModerationQueue/components/NotFoundAsset.js @@ -0,0 +1,14 @@ +import React from 'react'; +import {Link} from 'react-router'; +import styles from './styles.css'; + +const NotFound = props => ( +
    +

    + The provided asset id {props.assetId} does not exist. + Go to Streams +

    +
    +); + +export default NotFound; diff --git a/client/coral-admin/src/containers/ModerationQueue/components/styles.css b/client/coral-admin/src/containers/ModerationQueue/components/styles.css index a1e918459..cc40b03a3 100644 --- a/client/coral-admin/src/containers/ModerationQueue/components/styles.css +++ b/client/coral-admin/src/containers/ModerationQueue/components/styles.css @@ -1,3 +1,62 @@ +.notFound { + position: relative; + margin: 20px auto; + text-align: center; + padding: 68px 45px; + vertical-align: middle; + min-width: 500px; + + a { + color: rgb(244, 126, 107); + font-weight: 500; + + &.goToStreams { + position: absolute; + right: 10px; + bottom: 10px; + } + } +} + +.header { + background-color: #3949AB; + color: white; + margin-bottom: -1px; + + .moderateAsset { + a { + -webkit-box-flex: 1; + -ms-flex: 1; + flex: 1; + color: white; + text-transform: capitalize; + font-weight: 500; + font-size: 15px; + letter-spacing: 1px; + transition: opacity 200ms; + opacity: 1; + + &:hover { + opacity: .8; + cursor: pointer; + } + + &:first-child { + text-align: left; + } + + &:nth-child(2) { + text-align: center; + } + + &:last-child { + text-align: right; + } + } + } +} + + @custom-media --big-viewport (min-width: 780px); .list { diff --git a/client/coral-admin/src/containers/Streams/Streams.css b/client/coral-admin/src/containers/Streams/Streams.css index 26fb4f74f..d8a75a4ce 100644 --- a/client/coral-admin/src/containers/Streams/Streams.css +++ b/client/coral-admin/src/containers/Streams/Streams.css @@ -55,6 +55,12 @@ border-left: none; border-right: none; + a { + color: rgb(44, 44, 44); + font-weight: 500; + text-decoration: none; + } + th { font-size: 1.1em; } diff --git a/client/coral-admin/src/containers/Streams/Streams.js b/client/coral-admin/src/containers/Streams/Streams.js index 1937f82c0..62f2f009e 100644 --- a/client/coral-admin/src/containers/Streams/Streams.js +++ b/client/coral-admin/src/containers/Streams/Streams.js @@ -4,14 +4,10 @@ import {connect} from 'react-redux'; import I18n from 'coral-framework/modules/i18n/i18n'; import {fetchAssets, updateAssetState} from '../../actions/assets'; import translations from '../../translations.json'; -import { - RadioGroup, - Radio, - Icon, - DataTable, - TableHeader -} from 'react-mdl'; -import Pager from 'coral-ui/components/Pager'; +import {Link} from 'react-router'; + +import {Pager, Icon} from 'coral-ui'; +import {DataTable, TableHeader, RadioGroup, Radio} from 'react-mdl'; const limit = 25; @@ -74,6 +70,8 @@ class Streams extends Component { } } + renderTitle = (title, {id}) => {title} + renderStatus = (closedAt, {id}) => { const closed = closedAt && new Date(closedAt).getTime() < Date.now(); const statusMenuOpen = this.state.statusMenus[id]; @@ -104,6 +102,9 @@ class Streams extends Component { render () { const {search, sort, filter} = this.state; const {assets} = this.props; + + const assetsIds = assets.ids.map((id) => assets.byId[id]); + return (
    @@ -142,16 +143,14 @@ class Streams extends Component {
    - assets.byId[id])}> - {lang.t('streams.article')} - - {lang.t('streams.pubdate')} - - - {lang.t('streams.status')} - + + {lang.t('streams.article')} + + {lang.t('streams.pubdate')} + + + {lang.t('streams.status')} + { assets: assets.toJS() }; }; + const mapDispatchToProps = (dispatch) => { return { fetchAssets: (...args) => { diff --git a/client/coral-admin/src/containers/Streams/StreamsTable.js b/client/coral-admin/src/containers/Streams/StreamsTable.js new file mode 100644 index 000000000..a801a3f7b --- /dev/null +++ b/client/coral-admin/src/containers/Streams/StreamsTable.js @@ -0,0 +1,39 @@ +import React from 'react'; +import translations from '../../translations.json'; +import I18n from 'coral-framework/modules/i18n/i18n'; +const lang = new I18n(translations); + +const StreamsTable = props => ( + + + + + + + + + + {props.rows.map((row, i)=> ( + + + + + + ))} + +
    + {lang.t('streams.article')} + + {lang.t('streams.pubdate')} + + {lang.t('streams.status')} +
    + {row.title} + + {row.publication_date} + + {lang.t('streams.status')} +
    +); + +export default StreamsTable; diff --git a/client/coral-admin/src/graphql/queries/assetsQuery.graphql b/client/coral-admin/src/graphql/queries/assetsQuery.graphql new file mode 100644 index 000000000..37950692d --- /dev/null +++ b/client/coral-admin/src/graphql/queries/assetsQuery.graphql @@ -0,0 +1,6 @@ +query Assets { + assets { + id + title + } +} diff --git a/client/coral-admin/src/graphql/queries/modQueueQuery.graphql b/client/coral-admin/src/graphql/queries/modQueueQuery.graphql index 5d69f6324..a22a6c808 100644 --- a/client/coral-admin/src/graphql/queries/modQueueQuery.graphql +++ b/client/coral-admin/src/graphql/queries/modQueueQuery.graphql @@ -1,6 +1,6 @@ #import "../fragments/commentView.graphql" -query ($asset_id: ID!) { +query ModQueue ($asset_id: ID!) { all: comments(query: { statuses: [ACCEPTED, REJECTED, PREMOD], asset_id: $asset_id @@ -25,10 +25,8 @@ query ($asset_id: ID!) { }) { ...commentView } - account: comments(query: { - statuses: [ACCEPTED], - asset_id: $asset_id - }) { - ...commentView + assets: assets { + id + title } } diff --git a/client/coral-admin/src/reducers/assets.js b/client/coral-admin/src/reducers/assets.js index 77d0bf081..c9a82f1c5 100644 --- a/client/coral-admin/src/reducers/assets.js +++ b/client/coral-admin/src/reducers/assets.js @@ -1,20 +1,26 @@ import {Map, List, fromJS} from 'immutable'; -import {FETCH_ASSETS_SUCCESS, UPDATE_ASSET_STATE_REQUEST} from '../constants/assets'; +import * as actions from '../constants/assets'; const initialState = Map({ byId: Map(), - ids: List() + ids: List(), + assets: List() }); -export default (state = initialState, action) => { +export default function assets (state = initialState, action) { switch (action.type) { - case FETCH_ASSETS_SUCCESS: + case actions.FETCH_ASSETS_SUCCESS: return replaceAssets(action, state); - case UPDATE_ASSET_STATE_REQUEST: - return state.setIn(['byId', action.id, 'closedAt'], action.closedAt); - default: return state; + case actions.UPDATE_ASSET_STATE_REQUEST: + return state + .setIn(['byId', action.id, 'closedAt'], action.closedAt); + case actions.UPDATE_ASSETS: + return state + .set('assets', List(action.assets)); + default: + return state; } -}; +} const replaceAssets = (action, state) => { const assets = fromJS(action.assets.reduce((prev, curr) => { prev[curr.id] = curr; return prev; }, {})); diff --git a/graph/mutators/index.js b/graph/mutators/index.js index 58d0ed62c..b799cf83d 100644 --- a/graph/mutators/index.js +++ b/graph/mutators/index.js @@ -2,6 +2,7 @@ const _ = require('lodash'); const Comment = require('./comment'); const Action = require('./action'); +const User = require('./user'); module.exports = (context) => { @@ -9,6 +10,7 @@ module.exports = (context) => { return _.merge(...[ Comment, Action, + User, ].map((mutators) => { // Each set of mutators is a function which takes the context. diff --git a/graph/mutators/user.js b/graph/mutators/user.js new file mode 100644 index 000000000..f693195d6 --- /dev/null +++ b/graph/mutators/user.js @@ -0,0 +1,34 @@ +const UsersService = require('../../services/users'); + +const setUserStatus = ({user}, {id, status}) => { + console.log('------as-d-asd-a-sads-a-sad-dsa-----'); + console.log('user', user); + console.log('id', id); + console.log('status', status); + + return UsersService.setStatus(id, status) + .then((user) => { + console.log('result', user); + return user; + }); +}; + +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:setUserStatus')) { + return { + User: { + setUserStatus: (action) => setUserStatus(context, action) + } + }; + } + + return { + User: { + setUserStatus: () => {}, + } + }; +}; diff --git a/graph/resolvers/root_mutation.js b/graph/resolvers/root_mutation.js index ef46b839a..a1d25189b 100644 --- a/graph/resolvers/root_mutation.js +++ b/graph/resolvers/root_mutation.js @@ -8,6 +8,9 @@ const RootMutation = { deleteAction(_, {id}, {mutators: {Action}}) { return Action.delete({id}); }, + setUserStatus(_, {id, status}, {mutators: {User}}) { + return User.setUserStatus({id, status}); + } }; module.exports = RootMutation; diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index 8369b93c6..a655a77f3 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -61,6 +61,9 @@ type User { # returns all comments based on a query. comments(query: CommentsQuery): [Comment] + + # returns user status + status: USER_STATUS } type Comment { @@ -177,6 +180,13 @@ enum COMMENT_STATUS { PREMOD } +enum USER_STATUS { + ACTIVE + BANNED + PENDING + APPROVED +} + type RootQuery { # retrieves site wide settings and defaults. @@ -216,6 +226,8 @@ type RootMutation { # delete an action based on the action id. deleteAction(id: ID!): Boolean + # sets user status + setUserStatus(id: ID!, status: USER_STATUS!): Boolean } schema { diff --git a/models/user.js b/models/user.js index 06dc3c7d1..0194ba5f3 100644 --- a/models/user.js +++ b/models/user.js @@ -147,7 +147,8 @@ const USER_GRAPH_OPERATIONS = [ 'mutation:createComment', 'mutation:createAction', 'mutation:deleteAction', - 'mutation:editName' + 'mutation:editName', + 'mutation:setUserStatus' ]; /** From 894fec9fb6412a006411d7b51e076018e544bb47 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Fri, 10 Feb 2017 17:55:51 -0300 Subject: [PATCH 15/28] adding context for moderation --- .../src/components/ActionButton.js | 38 ++++++++---------- .../src/components/ModerationList.css | 11 +++--- .../ModerationQueue/ModerationContainer.js | 5 +-- .../ModerationQueue/ModerationQueue.js | 2 + .../ModerationQueue/components/Comment.js | 38 +++++++----------- .../ModerationQueue/components/styles.css | 22 +++++++++++ .../src/containers/Streams/StreamsTable.js | 39 ------------------- .../src/graphql/fragments/commentView.graphql | 5 +++ graph/mutators/user.js | 10 +---- 9 files changed, 69 insertions(+), 101 deletions(-) delete mode 100644 client/coral-admin/src/containers/Streams/StreamsTable.js diff --git a/client/coral-admin/src/components/ActionButton.js b/client/coral-admin/src/components/ActionButton.js index 61de1c669..09abdd266 100644 --- a/client/coral-admin/src/components/ActionButton.js +++ b/client/coral-admin/src/components/ActionButton.js @@ -4,43 +4,39 @@ import I18n from 'coral-framework/modules/i18n/i18n'; import translations from '../translations.json'; import {FabButton, Button, Icon} from 'coral-ui'; -const ActionButton = ({option, type, comment = {}, user, menuOptionsMap, onClickAction, onClickShowBanDialog}) => -{ - const banned = user.status === 'BANNED'; +const ActionButton = ({type, user}) => { + const menuOptionsMap = { + 'reject': {status: 'REJECTED', icon: 'close', key: 'r'}, + 'approve': {status: 'ACCEPTED', icon: 'done', key: 't'}, + 'flag': {status: 'FLAGGED', icon: 'flag', filter: 'Untouched'}, + 'ban': {status: 'BANNED', icon: 'not interested'} + }; - if (option === 'flag' && (type === 'USERS' || comment.status || comment.flagged === true)) { - return null; - } - if (option === 'ban') { + if (type === 'ban') { return (
    ); } - const menuOption = menuOptionsMap[option]; - const action = { - item_type: type, - item_id: type === 'COMMENTS' ? comment.id : user.id - }; + return ( onClickAction(menuOption.status, type === 'COMMENTS' ? comment : user, action)} + className={`${type} ${styles.actionButton}`} + cStyle={type} + icon={menuOptionsMap[type].icon} + onClick={() => {}} /> ); + }; export default ActionButton; diff --git a/client/coral-admin/src/components/ModerationList.css b/client/coral-admin/src/components/ModerationList.css index fbcbce7d7..140f4bc8f 100644 --- a/client/coral-admin/src/components/ModerationList.css +++ b/client/coral-admin/src/components/ModerationList.css @@ -96,11 +96,6 @@ margin-left: 40px; } - .actionButton { - transform: scale(.8); - margin: 0; - } - .body { margin-top: 20px; flex: 1; @@ -182,3 +177,9 @@ font-size: 14px; } } + + +.actionButton { + transform: scale(.8); + margin: 0; +} diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js index ae78d1b8e..6b0ec8827 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js +++ b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js @@ -59,7 +59,6 @@ class ModerationContainer extends Component { } const enablePremodTab = !!data.premod.length; - return (
    @@ -68,9 +67,9 @@ class ModerationContainer extends Component { enablePremodTab={enablePremodTab} {...moderation} />
    diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js b/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js index ed1bc4dce..47d3d6745 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js +++ b/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js @@ -7,11 +7,13 @@ const actionsMap = { }; const ModerationQueue = props => { + console.log(props); return (
      { props.data[props.activeTab].map((comment, i) => { + console.log(props.asset); return { const links = linkify.getMatches(props.body); return (
    • + className={`mdl-card mdl-shadow--2dp ${styles.Comment} ${styles.listItem} ${props.isActive && !props.hideActive ? styles.activeItem : ''}`}>
    • ); }; @@ -80,7 +69,8 @@ const linkify = new Linkify(); const lang = new I18n(translations); Comment.propTypes = { - user: PropTypes.object.isRequired + user: PropTypes.object.isRequired, + asset: PropTypes.object.isRequired }; export default Comment; diff --git a/client/coral-admin/src/containers/ModerationQueue/components/styles.css b/client/coral-admin/src/containers/ModerationQueue/components/styles.css index cc40b03a3..f87efec24 100644 --- a/client/coral-admin/src/containers/ModerationQueue/components/styles.css +++ b/client/coral-admin/src/containers/ModerationQueue/components/styles.css @@ -248,3 +248,25 @@ font-size: 14px; } } + + +.Comment { + .moderateArticle { + padding: 10px 0px; + a { + display: block; + color: #679af3; + text-decoration: none; + font-size: 1em; + font-weight: 500; + letter-spacing: .5px; + font-size: .8em; + + &:hover { + text-decoration: underline; + opacity: .9; + cursor: pointer; + } + } + } +} diff --git a/client/coral-admin/src/containers/Streams/StreamsTable.js b/client/coral-admin/src/containers/Streams/StreamsTable.js deleted file mode 100644 index a801a3f7b..000000000 --- a/client/coral-admin/src/containers/Streams/StreamsTable.js +++ /dev/null @@ -1,39 +0,0 @@ -import React from 'react'; -import translations from '../../translations.json'; -import I18n from 'coral-framework/modules/i18n/i18n'; -const lang = new I18n(translations); - -const StreamsTable = props => ( - - - - - - - - - - {props.rows.map((row, i)=> ( - - - - - - ))} - -
      - {lang.t('streams.article')} - - {lang.t('streams.pubdate')} - - {lang.t('streams.status')} -
      - {row.title} - - {row.publication_date} - - {lang.t('streams.status')} -
      -); - -export default StreamsTable; diff --git a/client/coral-admin/src/graphql/fragments/commentView.graphql b/client/coral-admin/src/graphql/fragments/commentView.graphql index 29f9b3bfe..ced2f89fc 100644 --- a/client/coral-admin/src/graphql/fragments/commentView.graphql +++ b/client/coral-admin/src/graphql/fragments/commentView.graphql @@ -6,6 +6,11 @@ fragment commentView on Comment { user { id name: displayName + status + } + asset { + id + title } actions { type: action_type diff --git a/graph/mutators/user.js b/graph/mutators/user.js index f693195d6..7c48744d2 100644 --- a/graph/mutators/user.js +++ b/graph/mutators/user.js @@ -1,16 +1,8 @@ const UsersService = require('../../services/users'); const setUserStatus = ({user}, {id, status}) => { - console.log('------as-d-asd-a-sads-a-sad-dsa-----'); - console.log('user', user); - console.log('id', id); - console.log('status', status); - return UsersService.setStatus(id, status) - .then((user) => { - console.log('result', user); - return user; - }); + .then(res => res); }; module.exports = (context) => { From 184e63e4e3218232e38942dab7d86d5216a6ba92 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 10 Feb 2017 16:28:09 -0700 Subject: [PATCH 16/28] Updated circle based on feedback --- circle.yml | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/circle.yml b/circle.yml index 90da5084e..653b0e7d4 100644 --- a/circle.yml +++ b/circle.yml @@ -11,9 +11,14 @@ stages: build: workDir: ~/talk environment: - - "PATH=$PATH:~/talk/node_modules/.bin" - - "NODE_ENV=test" + - NODE_ENV: test steps: + - type: shell + shell: /bin/bash + command: | + # add in path rewrite + echo "PATH=$PATH:~/talk/node_modules/.bin" >> ~/.bashrc + # Get the code - type: checkout @@ -23,7 +28,7 @@ stages: # Install dependencies - type: shell - shell: /bin/bash + shell: /bin/bash -l command: | # install yarn npm install -g yarn @@ -39,7 +44,7 @@ stages: # Build the dependencies - type: shell - shell: /bin/bash + shell: /bin/bash -l command: | # build static dependencies yarn build @@ -48,7 +53,7 @@ stages: yarn lint - type: shell - shell: /bin/bash + shell: /bin/bash -l command: | # Initialize the settings in the database, this will create indicies # for the database. From af49b0f6fb68f7a44bc0abf5e9f32533471b53e3 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 10 Feb 2017 16:29:29 -0700 Subject: [PATCH 17/28] Fixed checkout order --- circle.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/circle.yml b/circle.yml index 653b0e7d4..c3788b9a1 100644 --- a/circle.yml +++ b/circle.yml @@ -13,15 +13,15 @@ stages: environment: - NODE_ENV: test steps: + # Get the code + - type: checkout + - type: shell shell: /bin/bash command: | # add in path rewrite echo "PATH=$PATH:~/talk/node_modules/.bin" >> ~/.bashrc - # Get the code - - type: checkout - # Restore the yarn cache - type: cache-restore key: yarn-cache From a2855a734303def336f0cba14278685a00b3a341 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Mon, 13 Feb 2017 10:03:55 -0700 Subject: [PATCH 18/28] Removed unused shell --- circle.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/circle.yml b/circle.yml index c3788b9a1..3c18185a1 100644 --- a/circle.yml +++ b/circle.yml @@ -16,12 +16,6 @@ stages: # Get the code - type: checkout - - type: shell - shell: /bin/bash - command: | - # add in path rewrite - echo "PATH=$PATH:~/talk/node_modules/.bin" >> ~/.bashrc - # Restore the yarn cache - type: cache-restore key: yarn-cache From 4e73d7fc234242d7a5d0bf9fdd12d47535b67d5b Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Mon, 13 Feb 2017 10:23:11 -0700 Subject: [PATCH 19/28] Switched from Circle 2.0 -> 1.0, added set -e --- circle.yml | 118 +++++++++++++++++++--------------------------- scripts/pree2e.sh | 3 ++ 2 files changed, 51 insertions(+), 70 deletions(-) diff --git a/circle.yml b/circle.yml index 3c18185a1..1b62faa4e 100644 --- a/circle.yml +++ b/circle.yml @@ -1,75 +1,53 @@ -version: 2 +machine: + node: + version: 7 + services: + - docker + - redis + environment: + PATH: "${PATH}:${HOME}/${CIRCLE_PROJECT_REPONAME}/node_modules/.bin" + NODE_ENV: "test" -executorType: machine +dependencies: + override: + # Upgrade the database version to 3.4. + - sudo apt-get purge mongodb-org* + - sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 0C49F3730359A14518585931BC711F9BA15703C6 + - echo "deb [ arch=amd64 ] http://repo.mongodb.org/apt/ubuntu precise/mongodb-org/3.4 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-3.4.list + - sudo apt-get update + - sudo apt-get install -y mongodb-org + - sudo service mongod restart + # Install node dependencies. + - yarn + cache_directories: + - ~/.cache/yarn + post: + # Build the static assets. + - yarn build + # Lint the project here, before tests are ran. + - yarn lint -containerInfo: - - image: node:7 - - image: mongo:3.4 - - image: redis:3.2 +database: + post: + # Initialize the settings in the database, this will create indicies for the + # database. + - ./bin/cli setup --defaults + - sleep 2 -stages: - build: - workDir: ~/talk - environment: - - NODE_ENV: test - steps: - # Get the code - - type: checkout +test: + override: + # Run the tests using the junit reporter. + - MOCHA_FILE=$CIRCLE_TEST_REPORTS/junit/test-results.xml MOCHA_REPORTER=mocha-junit-reporter yarn test + # Run the e2e test suite. + - E2E_REPORT_PATH=$CIRCLE_TEST_REPORTS/e2e yarn e2e - # Restore the yarn cache - - type: cache-restore - key: yarn-cache +deployment: + release: + tag: /v[0-9]+(\.[0-9]+)*/ + commands: + - bash ./scripts/deploy.sh - # Install dependencies - - type: shell - shell: /bin/bash -l - command: | - # install yarn - npm install -g yarn - - # install dependencies - yarn - - # Save the yarn cache - - type: cache-save - key: yarn-cache - paths: - - ~/.cache/yarn - - # Build the dependencies - - type: shell - shell: /bin/bash -l - command: | - # build static dependencies - yarn build - - # lint the project - yarn lint - - - type: shell - shell: /bin/bash -l - command: | - # Initialize the settings in the database, this will create indicies - # for the database. - ./bin/cli setup --defaults - - # Ugly fix to wait until database indicies are created. - sleep 2 - - # Run the tests using the junit reporter. - MOCHA_FILE=$CIRCLE_TEST_REPORTS/junit/test-results.xml MOCHA_REPORTER=mocha-junit-reporter yarn test - - # Run the e2e test suite - E2E_REPORT_PATH=$CIRCLE_TEST_REPORTS/e2e yarn e2e - -# TODO: fix -# deployment: -# release: -# tag: /v[0-9]+(\.[0-9]+)*/ -# commands: -# - bash ./scripts/deploy.sh -# -# latest: -# branch: master -# commands: -# - bash ./scripts/deploy.sh + latest: + branch: master + commands: + - bash ./scripts/deploy.sh diff --git a/scripts/pree2e.sh b/scripts/pree2e.sh index 9ed5b7b89..985ec4cc8 100755 --- a/scripts/pree2e.sh +++ b/scripts/pree2e.sh @@ -1,5 +1,8 @@ #!/bin/bash +# fail the e2e if any of these fail +set -e + # install selenium selenium-standalone install --config=./selenium.config.js From e2ebac71760534ca8db3e06db9460f4f70230473 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Mon, 13 Feb 2017 14:32:48 -0300 Subject: [PATCH 20/28] adds new mutations: setCommentStatus --- .../src/components/ActionButton.js | 3 +-- .../ModerationQueue/ModerationContainer.js | 11 ++++++-- .../ModerationQueue/ModerationQueue.js | 2 ++ .../ModerationQueue/components/Comment.js | 10 ++++--- .../src/graphql/mutations/index.js | 24 +++++++++++++++++ .../mutations/setCommentStatus.graphql | 3 +++ .../coral-admin/src/graphql/queries/index.js | 3 +-- .../src/graphql/queries/modQueueQuery.graphql | 2 +- graph/mutators/comment.js | 20 +++++++++++--- graph/resolvers/root_mutation.js | 3 +++ graph/typeDefs.graphql | 5 +++- models/user.js | 3 ++- services/comments.js | 27 +++++++++++++++++++ 13 files changed, 101 insertions(+), 15 deletions(-) create mode 100644 client/coral-admin/src/graphql/mutations/setCommentStatus.graphql diff --git a/client/coral-admin/src/components/ActionButton.js b/client/coral-admin/src/components/ActionButton.js index 0415b2529..1d1d4d9d0 100644 --- a/client/coral-admin/src/components/ActionButton.js +++ b/client/coral-admin/src/components/ActionButton.js @@ -14,10 +14,9 @@ const ActionButton = ({type = '', user, ...props}) => { className={`${type.toLowerCase()} ${styles.actionButton}`} cStyle={type.toLowerCase()} icon={menuActionsMap[type].icon} - onClick={() => {}} + onClick={type === 'APPROVE' ? props.acceptComment : props.rejectComment} /> ); }; export default ActionButton; - diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js index 36836ea0d..44d64219e 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js +++ b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js @@ -5,7 +5,7 @@ import key from 'keymaster'; import isEqual from 'lodash/isEqual'; import {modQueueQuery} from '../../graphql/queries'; -import {banUser} from '../../graphql/mutations'; +import {banUser, setCommentStatus} from '../../graphql/mutations'; import {fetchSettings} from 'actions/settings'; import {updateAssets} from 'actions/assets'; @@ -52,6 +52,11 @@ class ModerationContainer extends Component { return
      ; } + if (data.error) { + console.log(data); + return
      Error
      ; + } + if (providedAssetId) { asset = assets.find(asset => asset.id === this.props.params.id); @@ -61,7 +66,6 @@ class ModerationContainer extends Component { } const enablePremodTab = !!data.premod.length; - console.log(props.banUser); return (
      @@ -75,6 +79,8 @@ class ModerationContainer extends Component { enablePremodTab={enablePremodTab} suspectWords={settings.wordlist.suspect} showBanUserDialog={props.showBanUserDialog} + acceptComment={props.acceptComment} + rejectComment={props.rejectComment} /> ({ export default compose( connect(mapStateToProps, mapDispatchToProps), + setCommentStatus, modQueueQuery, banUser )(ModerationContainer); diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js b/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js index 41f0f80bd..322452489 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js +++ b/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js @@ -15,6 +15,8 @@ const ModerationQueue = props => { suspectWords={props.suspectWords} actions={actionsMap[comment.status]} showBanUserDialog={props.showBanUserDialog} + acceptComment={props.acceptComment} + rejectComment={props.rejectComment} {...comment} />; }) diff --git a/client/coral-admin/src/containers/ModerationQueue/components/Comment.js b/client/coral-admin/src/containers/ModerationQueue/components/Comment.js index 7467aa677..eae2f60b2 100644 --- a/client/coral-admin/src/containers/ModerationQueue/components/Comment.js +++ b/client/coral-admin/src/containers/ModerationQueue/components/Comment.js @@ -13,7 +13,7 @@ const lang = new I18n(translations); import I18n from 'coral-framework/modules/i18n/i18n'; import translations from 'coral-admin/src/translations.json'; -const Comment = props => { +const Comment = ({actions = [], ...props}) => { const links = linkify.getMatches(props.body); return ( @@ -30,8 +30,12 @@ const Comment = props => {
      {links ? Contains Link : null}
      - {props.actions.map((action, i) => - + props.acceptComment({commentId: props.id})} + rejectComment={() => props.rejectComment({commentId: props.id})} showBanUserDialog={() => props.showBanUserDialog(props.user, props.id)} /> )} diff --git a/client/coral-admin/src/graphql/mutations/index.js b/client/coral-admin/src/graphql/mutations/index.js index 372f87e56..5e1503062 100644 --- a/client/coral-admin/src/graphql/mutations/index.js +++ b/client/coral-admin/src/graphql/mutations/index.js @@ -2,6 +2,7 @@ // rejectComment import {graphql} from 'react-apollo'; import SET_USER_STATUS from './setUserStatus.graphql'; +import SET_COMMENT_STATUS from './setCommentStatus.graphql'; export const banUser = graphql(SET_USER_STATUS, { props: ({mutate}) => ({ @@ -14,3 +15,26 @@ export const banUser = graphql(SET_USER_STATUS, { }); }}), }); + +export const setCommentStatus = graphql(SET_COMMENT_STATUS, { + props: ({mutate}) => ({ + acceptComment: ({commentId}) => { + return mutate({ + variables: { + commentId, + status: 'ACCEPTED' + }, + refetchQueries: ['ModQueue'] + }); + }, + rejectComment: ({commentId}) => { + return mutate({ + variables: { + commentId, + status: 'REJECTED' + }, + refetchQueries: ['ModQueue'] + }); + } + }) +}); diff --git a/client/coral-admin/src/graphql/mutations/setCommentStatus.graphql b/client/coral-admin/src/graphql/mutations/setCommentStatus.graphql new file mode 100644 index 000000000..cfcc76421 --- /dev/null +++ b/client/coral-admin/src/graphql/mutations/setCommentStatus.graphql @@ -0,0 +1,3 @@ +mutation setCommentStatus($commentId: ID!, $status: COMMENT_STATUS!){ + setCommentStatus(id: $commentId, status: $status) +} diff --git a/client/coral-admin/src/graphql/queries/index.js b/client/coral-admin/src/graphql/queries/index.js index ab7d61b05..fc59f8f84 100644 --- a/client/coral-admin/src/graphql/queries/index.js +++ b/client/coral-admin/src/graphql/queries/index.js @@ -2,8 +2,7 @@ import {graphql} from 'react-apollo'; import MOD_QUEUE_QUERY from './modQueueQuery.graphql'; export const modQueueQuery = graphql(MOD_QUEUE_QUERY, { - options: (props) => { - const {id = ''} = props.params; + options: ({params: {id = ''}}) => { return { variables: { asset_id: id diff --git a/client/coral-admin/src/graphql/queries/modQueueQuery.graphql b/client/coral-admin/src/graphql/queries/modQueueQuery.graphql index a22a6c808..38ce9af23 100644 --- a/client/coral-admin/src/graphql/queries/modQueueQuery.graphql +++ b/client/coral-admin/src/graphql/queries/modQueueQuery.graphql @@ -2,7 +2,7 @@ query ModQueue ($asset_id: ID!) { all: comments(query: { - statuses: [ACCEPTED, REJECTED, PREMOD], + statuses: [REJECTED, PREMOD], asset_id: $asset_id }) { ...commentView diff --git a/graph/mutators/comment.js b/graph/mutators/comment.js index 6214c9df8..844b47d3c 100644 --- a/graph/mutators/comment.js +++ b/graph/mutators/comment.js @@ -162,22 +162,36 @@ const createPublicComment = (context, commentInput) => { })); }; +/** + * Sets the status of a comment + * @param {String} comment comment in graphql context + * @param {String} id identifier of the comment (uuid) + * @param {String} status the new status of the comment + */ + +const setCommentStatus = ({comment}, {id, status}) => { + return CommentsService.setStatus(id, status) + .then(res => res); +}; + 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:createComment')) { + if (context.user && context.user.can('mutation:createComment', 'mutation:setUserStatus')) { return { Comment: { - create: (comment) => createPublicComment(context, comment) + create: (comment) => createPublicComment(context, comment), + setCommentStatus: (action) => setCommentStatus(context, action) } }; } return { Comment: { - create: () => Promise.reject(errors.ErrNotAuthorized) + create: () => Promise.reject(errors.ErrNotAuthorized), + setCommentStatus: () => Promise.reject(errors.ErrNotAuthorized) } }; }; diff --git a/graph/resolvers/root_mutation.js b/graph/resolvers/root_mutation.js index dba537879..1f10d51d9 100644 --- a/graph/resolvers/root_mutation.js +++ b/graph/resolvers/root_mutation.js @@ -29,6 +29,9 @@ const RootMutation = { }, setUserStatus(_, {id, status}, {mutators: {User}}) { return User.setUserStatus({id, status}); + }, + setCommentStatus(_, {id, status}, {mutators: {Comment}}) { + return Comment.setCommentStatus({id, status}); } }; diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index d91dbbdd7..325fe3b5d 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -491,8 +491,11 @@ type RootMutation { # Delete an action based on the action id. deleteAction(id: ID!): DeleteActionResponse - # Sets user status + # Sets User status setUserStatus(id: ID!, status: USER_STATUS!): Boolean + + # Sets Comment status + setCommentStatus(id: ID!, status: COMMENT_STATUS!): Boolean } ################################################################################ diff --git a/models/user.js b/models/user.js index 0194ba5f3..bd18aa4c5 100644 --- a/models/user.js +++ b/models/user.js @@ -148,7 +148,8 @@ const USER_GRAPH_OPERATIONS = [ 'mutation:createAction', 'mutation:deleteAction', 'mutation:editName', - 'mutation:setUserStatus' + 'mutation:setUserStatus', + 'mutation:setCommentStatus' ]; /** diff --git a/services/comments.js b/services/comments.js index fc05341cb..bcc534c51 100644 --- a/services/comments.js +++ b/services/comments.js @@ -7,6 +7,12 @@ const ALLOWED_TAGS = [ {name: 'STAFF'} ]; +const STATUSES = [ + 'ACCEPTED', + 'REJECTED', + 'PREMOD', +]; + module.exports = class CommentsService { /** @@ -249,4 +255,25 @@ module.exports = class CommentsService { return CommentModel.find(query); } + + /** + * Sets Comment Status + * @param {String} id identifier of the comment (uuid) + * @param {String} status the new status of the comment + * @return {Promise} + */ + + static setStatus(id, status) { + + // Check to see if the comment status is in the allowable set of statuses. + if (STATUSES.indexOf(status) === -1) { + + // Comment status is not supported! Error out here. + return Promise.reject(new Error(`status ${status} is not supported`)); + } + + return CommentModel.update({id}, { + $set: {status} + }); + } }; From b30a5d21849a0108a3246c713da654c158edf8c2 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Mon, 13 Feb 2017 10:49:47 -0700 Subject: [PATCH 21/28] Removed 3.4 dep --- circle.yml | 16 +++++++++------- models/user.js | 19 +++++++++---------- services/users.js | 16 ++++++++++++---- 3 files changed, 30 insertions(+), 21 deletions(-) diff --git a/circle.yml b/circle.yml index 1b62faa4e..77e61f102 100644 --- a/circle.yml +++ b/circle.yml @@ -10,13 +10,15 @@ machine: dependencies: override: - # Upgrade the database version to 3.4. - - sudo apt-get purge mongodb-org* - - sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 0C49F3730359A14518585931BC711F9BA15703C6 - - echo "deb [ arch=amd64 ] http://repo.mongodb.org/apt/ubuntu precise/mongodb-org/3.4 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-3.4.list - - sudo apt-get update - - sudo apt-get install -y mongodb-org - - sudo service mongod restart + # TODO: use the following to add in support for MongoDB 3.4. + # # Upgrade the database version to 3.4. + # - sudo apt-get purge mongodb-org* + # - sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 0C49F3730359A14518585931BC711F9BA15703C6 + # - echo "deb [ arch=amd64 ] http://repo.mongodb.org/apt/ubuntu precise/mongodb-org/3.4 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-3.4.list + # - sudo apt-get update + # - sudo apt-get install -y mongodb-org + # - sudo service mongod restart + # Install node dependencies. - yarn cache_directories: diff --git a/models/user.js b/models/user.js index b142be5a0..43a34a565 100644 --- a/models/user.js +++ b/models/user.js @@ -66,6 +66,15 @@ const UserSchema = new mongoose.Schema({ required: true }, + // TODO: find a way that we can instead utilize MongoDB 3.4's collation + // options to build the index in a case insenstive manner: + // https://docs.mongodb.com/manual/reference/collation/ + lowercaseUsername: { + type: String, + required: true, + unique: true + }, + // This is true when the user account is disabled, no action should be // acknowledged when they are disabled. Logins are also prevented. disabled: Boolean, @@ -126,16 +135,6 @@ UserSchema.index({ background: false }); -UserSchema.index({ - 'username': 1 -}, { - unique: true, - collation: { - locale: 'en_US', - strength: 2 - } -}); - /** * Returns true if the user has all the roles specified. */ diff --git a/services/users.js b/services/users.js index 97cf3e125..1b8045c84 100644 --- a/services/users.js +++ b/services/users.js @@ -99,6 +99,10 @@ module.exports = class UsersService { .then(() => dstUser.save()); } + static castDisplayName(displayName) { + return displayName.replace(/ /g, '_').replace(/[^a-zA-Z_]/g, ''); + } + /** * Finds a user given a social profile and if the user does not exist, creates * them. @@ -120,12 +124,14 @@ module.exports = class UsersService { return user; } + // TODO: remove displayName reference when we have steps in the FE to handle + // the username create flow. + let username = UsersService.castDisplayName(displayName); + // The user was not found, lets create them! user = new UserModel({ - - // TODO: remove displayName reference when we have steps in the FE to handle - // the username create flow. - username: displayName.replace(/ /g, '_').replace(/[^a-zA-Z_]/g, ''), + username, + lowercaseUsername: username.toLowerCase(), roles: [], profiles: [{id, provider}] }); @@ -238,6 +244,7 @@ module.exports = class UsersService { let user = new UserModel({ username, + lowercaseUsername: username.toLowerCase(), password: hashedPassword, roles: [], profiles: [ @@ -677,6 +684,7 @@ module.exports = class UsersService { }, { $set: { username: username, + lowercaseUsername: username.toLowerCase(), canEditName: false, status: 'PENDING' } From 594b87208aff208a10e9048ac9bdcb1c4411ef73 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Mon, 13 Feb 2017 16:00:55 -0300 Subject: [PATCH 22/28] GraphQL Review and Responses --- .../ModerationQueue/ModerationContainer.js | 3 ++- .../ModerationQueue/ModerationQueue.js | 2 +- .../ModerationQueue/components/Comment.js | 27 ++++++++----------- graph/mutators/comment.js | 21 ++++++++------- graph/mutators/user.js | 3 ++- graph/resolvers/root_mutation.js | 4 +-- graph/typeDefs.graphql | 20 ++++++++++++-- models/user.js | 4 +++ 8 files changed, 51 insertions(+), 33 deletions(-) diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js index 44d64219e..1f359a1f7 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js +++ b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js @@ -72,7 +72,8 @@ class ModerationContainer extends Component { + activeTab={moderation.activeTab} + /> { return ; }) } diff --git a/client/coral-admin/src/containers/ModerationQueue/components/Comment.js b/client/coral-admin/src/containers/ModerationQueue/components/Comment.js index eae2f60b2..7a7967aaf 100644 --- a/client/coral-admin/src/containers/ModerationQueue/components/Comment.js +++ b/client/coral-admin/src/containers/ModerationQueue/components/Comment.js @@ -1,4 +1,4 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; import timeago from 'timeago.js'; import Linkify from 'react-linkify'; import Highlighter from 'react-highlight-words'; @@ -14,16 +14,16 @@ import I18n from 'coral-framework/modules/i18n/i18n'; import translations from 'coral-admin/src/translations.json'; const Comment = ({actions = [], ...props}) => { - const links = linkify.getMatches(props.body); + const links = linkify.getMatches(props.comment.body); return (
    • - {props.user.name} + {props.comment.user.name} - {timeago().format(props.created_at || (Date.now() - props.index * 60 * 1000), lang.getLocale().replace('-', '_'))} + {timeago().format(props.comment.created_at || (Date.now() - props.index * 60 * 1000), lang.getLocale().replace('-', '_'))} {props.flagged ?

      {lang.t('comment.flagged')}

      : null}
      @@ -33,14 +33,14 @@ const Comment = ({actions = [], ...props}) => { {actions.map((action, i) => props.acceptComment({commentId: props.id})} - rejectComment={() => props.rejectComment({commentId: props.id})} - showBanUserDialog={() => props.showBanUserDialog(props.user, props.id)} + user={props.comment.user} + acceptComment={() => props.acceptComment({commentId: props.comment.id})} + rejectComment={() => props.rejectComment({commentId: props.comment.id})} + showBanUserDialog={() => props.showBanUserDialog(props.comment.user, props.comment.id)} /> )}
      - {props.user.banned === 'banned' ? + {props.comment.user.banned === 'banned' ? {lang.t('comment.banned_user')} @@ -50,13 +50,13 @@ const Comment = ({actions = [], ...props}) => {
    • - {props.asset.title} Moderate Article + {props.comment.asset.title} Moderate Article

      - +

      @@ -74,9 +74,4 @@ const linkStyles = { padding: '1px 2px' }; -Comment.propTypes = { - user: PropTypes.object.isRequired, - asset: PropTypes.object.isRequired -}; - export default Comment; diff --git a/graph/mutators/comment.js b/graph/mutators/comment.js index 844b47d3c..98891953c 100644 --- a/graph/mutators/comment.js +++ b/graph/mutators/comment.js @@ -179,19 +179,20 @@ 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:createComment', 'mutation:setUserStatus')) { - return { - Comment: { - create: (comment) => createPublicComment(context, comment), - setCommentStatus: (action) => setCommentStatus(context, action) - } - }; - } - - return { + let mutators = { Comment: { create: () => Promise.reject(errors.ErrNotAuthorized), setCommentStatus: () => Promise.reject(errors.ErrNotAuthorized) } }; + + if (context.user && context.user.can('mutation:createComment')) { + mutators.Comment.create = (comment) => createPublicComment(context, comment); + } + + if (context.user && context.user.can('mutation:setCommentStatus')) { + mutators.Comment.setCommentStatus = (action) => setCommentStatus(context, action); + } + + return mutators; }; diff --git a/graph/mutators/user.js b/graph/mutators/user.js index 7c48744d2..2c43f11be 100644 --- a/graph/mutators/user.js +++ b/graph/mutators/user.js @@ -1,3 +1,4 @@ +const errors = require('../../errors'); const UsersService = require('../../services/users'); const setUserStatus = ({user}, {id, status}) => { @@ -20,7 +21,7 @@ module.exports = (context) => { return { User: { - setUserStatus: () => {}, + setUserStatus: () => Promise.reject(errors.ErrNotAuthorized) } }; }; diff --git a/graph/resolvers/root_mutation.js b/graph/resolvers/root_mutation.js index 1f10d51d9..4285f900d 100644 --- a/graph/resolvers/root_mutation.js +++ b/graph/resolvers/root_mutation.js @@ -28,10 +28,10 @@ const RootMutation = { return wrapResponse(null)(Action.delete({id})); }, setUserStatus(_, {id, status}, {mutators: {User}}) { - return User.setUserStatus({id, status}); + return wrapResponse(null)(User.setUserStatus({id, status})); }, setCommentStatus(_, {id, status}, {mutators: {Comment}}) { - return Comment.setCommentStatus({id, status}); + return wrapResponse(null)(Comment.setCommentStatus({id, status})); } }; diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index 325fe3b5d..b5c8539ad 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -476,6 +476,22 @@ type DeleteActionResponse implements Response { errors: [UserError] } +# SetUserStatusResponse is the response returned with possibly some errors +# relating to the delete action attempt. +type SetUserStatusResponse implements Response { + + # An array of errors relating to the mutation that occured. + errors: [UserError] +} + +# SetCommentStatusResponse is the response returned with possibly some errors +# relating to the delete action attempt. +type SetCommentStatusResponse implements Response { + + # An array of errors relating to the mutation that occured. + errors: [UserError] +} + # All mutations for the application are defined on this object. type RootMutation { @@ -492,10 +508,10 @@ type RootMutation { deleteAction(id: ID!): DeleteActionResponse # Sets User status - setUserStatus(id: ID!, status: USER_STATUS!): Boolean + setUserStatus(id: ID!, status: USER_STATUS!): SetUserStatusResponse # Sets Comment status - setCommentStatus(id: ID!, status: COMMENT_STATUS!): Boolean + setCommentStatus(id: ID!, status: COMMENT_STATUS!): SetCommentStatusResponse } ################################################################################ diff --git a/models/user.js b/models/user.js index bd18aa4c5..7e04386cd 100644 --- a/models/user.js +++ b/models/user.js @@ -165,6 +165,10 @@ UserSchema.method('can', function(...actions) { return false; } + if (actions.some((action) => action === 'mutation:setUserStatus' || action === 'mutation:setCommentStatus') && !this.hasRoles('ADMIN')) { + return false; + } + return true; }); From 0403c46c6ce4eb96caf1e8dca2d92f57e2eed011 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Mon, 13 Feb 2017 16:13:03 -0300 Subject: [PATCH 23/28] Adding errors response --- client/coral-admin/src/graphql/mutations/index.js | 2 -- .../src/graphql/mutations/setCommentStatus.graphql | 6 +++++- .../coral-admin/src/graphql/mutations/setUserStatus.graphql | 6 +++++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/client/coral-admin/src/graphql/mutations/index.js b/client/coral-admin/src/graphql/mutations/index.js index 5e1503062..fe3a1faf9 100644 --- a/client/coral-admin/src/graphql/mutations/index.js +++ b/client/coral-admin/src/graphql/mutations/index.js @@ -1,5 +1,3 @@ -// acceptComment -// rejectComment import {graphql} from 'react-apollo'; import SET_USER_STATUS from './setUserStatus.graphql'; import SET_COMMENT_STATUS from './setCommentStatus.graphql'; diff --git a/client/coral-admin/src/graphql/mutations/setCommentStatus.graphql b/client/coral-admin/src/graphql/mutations/setCommentStatus.graphql index cfcc76421..7ff6173a8 100644 --- a/client/coral-admin/src/graphql/mutations/setCommentStatus.graphql +++ b/client/coral-admin/src/graphql/mutations/setCommentStatus.graphql @@ -1,3 +1,7 @@ mutation setCommentStatus($commentId: ID!, $status: COMMENT_STATUS!){ - setCommentStatus(id: $commentId, status: $status) + setCommentStatus(id: $commentId, status: $status) { + errors { + translation_key + } + } } diff --git a/client/coral-admin/src/graphql/mutations/setUserStatus.graphql b/client/coral-admin/src/graphql/mutations/setUserStatus.graphql index 0ebaf54fd..32fcf7e20 100644 --- a/client/coral-admin/src/graphql/mutations/setUserStatus.graphql +++ b/client/coral-admin/src/graphql/mutations/setUserStatus.graphql @@ -1,3 +1,7 @@ mutation setUserStatus($userId: ID!, $status: USER_STATUS!) { - setUserStatus(id: $userId, status: $status) + setUserStatus(id: $userId, status: $status) { + errors { + translation_key + } + } } From 732ef9b8bd4889ccd19a4a55883f7f73ee550fb1 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Mon, 13 Feb 2017 16:24:12 -0300 Subject: [PATCH 24/28] Adding moderationList for the tests --- .../src/containers/ModerationQueue/ModerationQueue.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js b/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js index 1cd478b63..ca3b5e540 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js +++ b/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js @@ -5,7 +5,7 @@ import {actionsMap} from './helpers/moderationQueueActionsMap'; const ModerationQueue = props => { return ( -
      +
        { props.data[props.activeTab].map((comment, i) => { From 519298b980cbdeadfb7c772067fe8055dc78025e Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Mon, 13 Feb 2017 18:12:37 -0300 Subject: [PATCH 25/28] Design Pass --- client/coral-admin/src/components/ui/Header.js | 10 +++++----- .../ModerationQueue/ModerationContainer.js | 1 + .../ModerationQueue/ModerationQueue.js | 1 + .../ModerationQueue/components/Comment.js | 11 +++++------ .../ModerationQueue/components/styles.css | 17 ++++++++++------- 5 files changed, 22 insertions(+), 18 deletions(-) diff --git a/client/coral-admin/src/components/ui/Header.js b/client/coral-admin/src/components/ui/Header.js index 3e6598d6e..e92e3c84f 100644 --- a/client/coral-admin/src/components/ui/Header.js +++ b/client/coral-admin/src/components/ui/Header.js @@ -13,10 +13,14 @@ export default ({handleLogout, restricted = false}) => ( !restricted ?
        - {lang.t('configure.moderate')} + + {lang.t('configure.streams')} + {lang.t('configure.community')} @@ -25,10 +29,6 @@ export default ({handleLogout, restricted = false}) => ( activeClassName={styles.active}> {lang.t('configure.configure')} - - {lang.t('configure.streams')} -
          diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js index 1f359a1f7..5a11ef425 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js +++ b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js @@ -76,6 +76,7 @@ class ModerationContainer extends Component { /> { showBanUserDialog={props.showBanUserDialog} acceptComment={props.acceptComment} rejectComment={props.rejectComment} + currentAsset={props.currentAsset} />; }) } diff --git a/client/coral-admin/src/containers/ModerationQueue/components/Comment.js b/client/coral-admin/src/containers/ModerationQueue/components/Comment.js index 7a7967aaf..0f9202a51 100644 --- a/client/coral-admin/src/containers/ModerationQueue/components/Comment.js +++ b/client/coral-admin/src/containers/ModerationQueue/components/Comment.js @@ -15,7 +15,6 @@ import translations from 'coral-admin/src/translations.json'; const Comment = ({actions = [], ...props}) => { const links = linkify.getMatches(props.comment.body); - return (
        • @@ -48,11 +47,11 @@ const Comment = ({actions = [], ...props}) => { : null}
        - -
        - {props.comment.asset.title} Moderate Article -
        - + {!props.currentAsset && ( +
        + Article: {props.comment.asset.title} Moderate Article +
        + )}

        diff --git a/client/coral-admin/src/containers/ModerationQueue/components/styles.css b/client/coral-admin/src/containers/ModerationQueue/components/styles.css index 3290b6e1f..e11c20394 100644 --- a/client/coral-admin/src/containers/ModerationQueue/components/styles.css +++ b/client/coral-admin/src/containers/ModerationQueue/components/styles.css @@ -6,7 +6,7 @@ } .tabBar { - background: #262626; + background-color: rgba(44, 44, 44, 0.89); z-index: 5; } @@ -73,7 +73,7 @@ span { } .header { - background-color: #3949AB; + background-color: #2c2c2c; color: white; margin-bottom: -1px; @@ -156,6 +156,7 @@ span { padding: 16px 14px; position: relative; transition: box-shadow 200ms; + margin-top: 0; &:hover { @@ -222,10 +223,11 @@ span { } .body { - margin-top: 20px; + margin-top: 0px; flex: 1; font-size: 0.88em; color: black; + max-width: 500px; } .flagged { @@ -294,15 +296,16 @@ span { .Comment { .moderateArticle { - padding: 10px 0px; + font-size: 12px; a { - display: block; + display: inline-block; color: #679af3; text-decoration: none; font-size: 1em; - font-weight: 500; + font-weight: 400; letter-spacing: .5px; - font-size: .8em; + font-size: 12px; + margin-left: 10px; &:hover { text-decoration: underline; From 37ba14f3b5b8ec8f2d0b672564c429210f4bbc58 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Mon, 13 Feb 2017 18:27:26 -0300 Subject: [PATCH 26/28] breaking big words --- .../src/containers/ModerationQueue/components/styles.css | 1 + 1 file changed, 1 insertion(+) diff --git a/client/coral-admin/src/containers/ModerationQueue/components/styles.css b/client/coral-admin/src/containers/ModerationQueue/components/styles.css index e11c20394..0be87efd7 100644 --- a/client/coral-admin/src/containers/ModerationQueue/components/styles.css +++ b/client/coral-admin/src/containers/ModerationQueue/components/styles.css @@ -228,6 +228,7 @@ span { font-size: 0.88em; color: black; max-width: 500px; + word-wrap: break-word; } .flagged { From 51be47b62e9f0ebc1777aa201ab6149cd601a273 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Mon, 13 Feb 2017 19:09:26 -0300 Subject: [PATCH 27/28] added flag functionality --- .../ModerationQueue/components/Comment.js | 9 ++++++--- .../ModerationQueue/components/FlagBox.js | 19 +++++++++++++++++++ .../ModerationQueue/components/styles.css | 9 ++++++++- .../src/graphql/queries/modQueueQuery.graphql | 6 ++++++ graph/resolvers/root_query.js | 7 +------ 5 files changed, 40 insertions(+), 10 deletions(-) create mode 100644 client/coral-admin/src/containers/ModerationQueue/components/FlagBox.js diff --git a/client/coral-admin/src/containers/ModerationQueue/components/Comment.js b/client/coral-admin/src/containers/ModerationQueue/components/Comment.js index 0f9202a51..ae70b303a 100644 --- a/client/coral-admin/src/containers/ModerationQueue/components/Comment.js +++ b/client/coral-admin/src/containers/ModerationQueue/components/Comment.js @@ -7,14 +7,17 @@ import {Link} from 'react-router'; import styles from './styles.css'; import {Icon} from 'coral-ui'; import ActionButton from '../../../components/ActionButton'; +import FlagBox from './FlagBox'; + const linkify = new Linkify(); -const lang = new I18n(translations); import I18n from 'coral-framework/modules/i18n/i18n'; import translations from 'coral-admin/src/translations.json'; +const lang = new I18n(translations); const Comment = ({actions = [], ...props}) => { const links = linkify.getMatches(props.comment.body); + const actionSumaries = props.comment.action_summaries; return (

      • @@ -24,7 +27,7 @@ const Comment = ({actions = [], ...props}) => { {timeago().format(props.comment.created_at || (Date.now() - props.index * 60 * 1000), lang.getLocale().replace('-', '_'))} - {props.flagged ?

        {lang.t('comment.flagged')}

        : null} + {props.comment.action_summaries ?

        {lang.t('comment.flagged')}

        : null}
      • {links ? Contains Link : null} @@ -59,11 +62,11 @@ const Comment = ({actions = [], ...props}) => {

        + {actionSumaries && } {/* */} {/* View context*/} {/* */} - ); }; diff --git a/client/coral-admin/src/containers/ModerationQueue/components/FlagBox.js b/client/coral-admin/src/containers/ModerationQueue/components/FlagBox.js new file mode 100644 index 000000000..bf5a34f29 --- /dev/null +++ b/client/coral-admin/src/containers/ModerationQueue/components/FlagBox.js @@ -0,0 +1,19 @@ +import React, {PropTypes} from 'react'; +import styles from './styles.css'; + +const FlagBox = props => ( +
        +

        Flags:

        +
          + {props.actionSumaries.map((action, i) => +
        • {!action.reason ? No reason provided : action.reason} ({action.count})
        • + )} +
        +
        +); + +FlagBox.propTypes = { + actionSumaries: PropTypes.array.isRequired +}; + +export default FlagBox; diff --git a/client/coral-admin/src/containers/ModerationQueue/components/styles.css b/client/coral-admin/src/containers/ModerationQueue/components/styles.css index 0be87efd7..658c9a059 100644 --- a/client/coral-admin/src/containers/ModerationQueue/components/styles.css +++ b/client/coral-admin/src/containers/ModerationQueue/components/styles.css @@ -317,4 +317,11 @@ span { } } - +.flagBox { + border-top: 1px solid rgba(66, 66, 66, 0.12); + h3 { + font-size: 14px; + margin: 0; + font-weight: 500; + } +} diff --git a/client/coral-admin/src/graphql/queries/modQueueQuery.graphql b/client/coral-admin/src/graphql/queries/modQueueQuery.graphql index 38ce9af23..9eba7a971 100644 --- a/client/coral-admin/src/graphql/queries/modQueueQuery.graphql +++ b/client/coral-admin/src/graphql/queries/modQueueQuery.graphql @@ -18,6 +18,12 @@ query ModQueue ($asset_id: ID!) { asset_id: $asset_id }) { ...commentView + action_summaries { + count + ... on FlagActionSummary { + reason + } + } } rejected: comments(query: { statuses: [REJECTED], diff --git a/graph/resolvers/root_query.js b/graph/resolvers/root_query.js index 8aa14d5c6..ec3ac5b08 100644 --- a/graph/resolvers/root_query.js +++ b/graph/resolvers/root_query.js @@ -29,12 +29,7 @@ const RootQuery = { if (user != null && user.hasRoles('ADMIN') && action_type) { return Actions.getByTypes({action_type, item_type: 'COMMENTS'}) - .then((actions) => { - - // Map the actions from the items referenced byt this query. The actions - // returned by this query are explicitly going to be distinct by their - // `item_id`'s. - let ids = actions.map((action) => action.item_id); + .then((ids) => { // Perform the query using the available resolver. return Comments.getByQuery({ids, statuses, asset_id, parent_id, limit, cursor, sort}); From 51cbf79f3074316d7c1b6190e2ba595e28fa4f86 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Mon, 13 Feb 2017 15:33:10 -0700 Subject: [PATCH 28/28] Changed fragment --- client/coral-admin/src/graphql/fragments/commentView.graphql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/coral-admin/src/graphql/fragments/commentView.graphql b/client/coral-admin/src/graphql/fragments/commentView.graphql index c31e67fb1..e78c28a28 100644 --- a/client/coral-admin/src/graphql/fragments/commentView.graphql +++ b/client/coral-admin/src/graphql/fragments/commentView.graphql @@ -5,7 +5,7 @@ fragment commentView on Comment { status user { id - name: displayName + name: username status } asset {