From a9104b749c1f9c3e97cefaa35cbab5d176249444 Mon Sep 17 00:00:00 2001 From: David Jay Date: Wed, 15 Feb 2017 12:19:36 -0500 Subject: [PATCH 01/34] Updating comments and replies using updatequeries w/ optimistic updating. --- client/coral-embed-stream/src/Comment.js | 1 - client/coral-embed-stream/src/Embed.js | 1 - .../graphql/mutations/index.js | 53 +++++++++++++++++-- .../graphql/mutations/postComment.graphql | 3 ++ 4 files changed, 51 insertions(+), 7 deletions(-) diff --git a/client/coral-embed-stream/src/Comment.js b/client/coral-embed-stream/src/Comment.js index 3bf787a9c..48dd52d09 100644 --- a/client/coral-embed-stream/src/Comment.js +++ b/client/coral-embed-stream/src/Comment.js @@ -140,7 +140,6 @@ class Comment extends React.Component { ? { setActiveReplyBox(''); - refetch(); }} setActiveReplyBox={setActiveReplyBox} parentId={parentId || comment.id} diff --git a/client/coral-embed-stream/src/Embed.js b/client/coral-embed-stream/src/Embed.js index d12dbd0d8..6240c293b 100644 --- a/client/coral-embed-stream/src/Embed.js +++ b/client/coral-embed-stream/src/Embed.js @@ -122,7 +122,6 @@ class Embed extends Component { { user ? ({ fragments: commentView }), - props: ({mutate}) => ({ - postItem: ({asset_id, body, parent_id} /* , type */ ) => { - return mutate({ + props: ({ownProps, mutate}) => ({ + postItem: ({asset_id, body, parent_id}) => + mutate({ variables: { asset_id, body, parent_id + }, + optimisticResponse: { + createComment: { + comment: { + user: { + id: ownProps.auth.user.id, + name: ownProps.auth.user.username + }, + created_at: new Date(), + body, + parent_id, + asset_id, + action_summaries: [], + tags: [], + status: null, + id: `${Date.now()}_temp_id` + } + } + }, + updateQueries: { + AssetQuery: (oldData, {mutationResult:{data:{createComment:{comment}}}}) => { + + // If posting a reply + return parent_id ? { + ...oldData, + asset: { + ...oldData.asset, + comments: oldData.asset.comments.map((comment) => + comment.id === parent_id + ? {...comment, replies: [...comment.replies, comment]} + : comment) + } + } + + // If posting a top-level comment + : { + ...oldData, + asset: { + ...oldData.asset, + comments: [comment, ...oldData.asset.comments] + } + }; + } } - }); - }}), + }) + }), }); export const postLike = graphql(POST_LIKE, { diff --git a/client/coral-framework/graphql/mutations/postComment.graphql b/client/coral-framework/graphql/mutations/postComment.graphql index 6ce46eaa8..3840d66c2 100644 --- a/client/coral-framework/graphql/mutations/postComment.graphql +++ b/client/coral-framework/graphql/mutations/postComment.graphql @@ -4,6 +4,9 @@ mutation CreateComment ($asset_id: ID!, $parent_id: ID, $body: String!) { createComment(asset_id:$asset_id, parent_id:$parent_id, body:$body) { comment { ...commentView + replies { + ...commentView + } } errors { translation_key From 904bda86dbf98ef28708361d26d8a4520cf41385 Mon Sep 17 00:00:00 2001 From: David Jay Date: Wed, 15 Feb 2017 12:36:19 -0500 Subject: [PATCH 02/34] Fixing error with replies. --- client/coral-framework/graphql/mutations/index.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/client/coral-framework/graphql/mutations/index.js b/client/coral-framework/graphql/mutations/index.js index 6345e797c..fd08e1751 100644 --- a/client/coral-framework/graphql/mutations/index.js +++ b/client/coral-framework/graphql/mutations/index.js @@ -37,17 +37,17 @@ export const postComment = graphql(POST_COMMENT, { } }, updateQueries: { - AssetQuery: (oldData, {mutationResult:{data:{createComment:{comment}}}}) => { - + AssetQuery: (oldData, {mutationResult:{data:{createComment:{comment}}}}) => + // If posting a reply - return parent_id ? { + parent_id ? { ...oldData, asset: { ...oldData.asset, - comments: oldData.asset.comments.map((comment) => - comment.id === parent_id - ? {...comment, replies: [...comment.replies, comment]} - : comment) + comments: oldData.asset.comments.map((oldComment) => + oldComment.id === parent_id + ? {...oldComment, replies: [...oldComment.replies, comment]} + : oldComment) } } @@ -58,7 +58,6 @@ export const postComment = graphql(POST_COMMENT, { ...oldData.asset, comments: [comment, ...oldData.asset.comments] } - }; } } }) From 79dbe7dcdabf13850cf9a4d9b857bdb03bc0666e Mon Sep 17 00:00:00 2001 From: David Jay Date: Wed, 15 Feb 2017 13:05:31 -0500 Subject: [PATCH 03/34] Updating commentCount on comment post. --- client/coral-framework/graphql/mutations/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/coral-framework/graphql/mutations/index.js b/client/coral-framework/graphql/mutations/index.js index fd08e1751..b67343521 100644 --- a/client/coral-framework/graphql/mutations/index.js +++ b/client/coral-framework/graphql/mutations/index.js @@ -38,7 +38,7 @@ export const postComment = graphql(POST_COMMENT, { }, updateQueries: { AssetQuery: (oldData, {mutationResult:{data:{createComment:{comment}}}}) => - + // If posting a reply parent_id ? { ...oldData, @@ -56,6 +56,7 @@ export const postComment = graphql(POST_COMMENT, { ...oldData, asset: { ...oldData.asset, + commentCount: oldData.asset.commentCount + 1, comments: [comment, ...oldData.asset.comments] } } From 1dcdd4a9c92b1f190d40819bdfc2e6574d936f9d Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Thu, 16 Feb 2017 12:04:24 -0700 Subject: [PATCH 04/34] make Dashboard first tab per Sam. simplify tabs --- .../coral-admin/src/components/ui/Header.js | 14 ++--- .../components/ModerationMenu.js | 53 ++++++++----------- 2 files changed, 28 insertions(+), 39 deletions(-) diff --git a/client/coral-admin/src/components/ui/Header.js b/client/coral-admin/src/components/ui/Header.js index 6eacaba9e..141d7fecf 100644 --- a/client/coral-admin/src/components/ui/Header.js +++ b/client/coral-admin/src/components/ui/Header.js @@ -14,11 +14,17 @@ export default ({handleLogout, restricted = false}) => (
+ {lang.t('configure.dashboard')} + + {lang.t('configure.moderate')} - + @@ -35,12 +41,6 @@ export default ({handleLogout, restricted = false}) => ( activeClassName={styles.active}> {lang.t('configure.configure')} - - {lang.t('configure.dashboard')} -
    diff --git a/client/coral-admin/src/containers/ModerationQueue/components/ModerationMenu.js b/client/coral-admin/src/containers/ModerationQueue/components/ModerationMenu.js index 0694152a2..a91b2cbab 100644 --- a/client/coral-admin/src/containers/ModerationQueue/components/ModerationMenu.js +++ b/client/coral-admin/src/containers/ModerationQueue/components/ModerationMenu.js @@ -6,38 +6,27 @@ import {Link} from 'react-router'; const lang = new I18n(translations); -const ModerationMenu = (props) => ( -
    -
    - { - props.asset ? ( -
    - - {lang.t('modqueue.premod')} - - - {lang.t('modqueue.rejected')} - - - {lang.t('modqueue.flagged')} - -
    - ) : ( -
    - - {lang.t('modqueue.premod')} - - - {lang.t('modqueue.rejected')} - - - {lang.t('modqueue.flagged')} - -
    - ) - } +const ModerationMenu = ({asset}) => { + const premodPath = asset ? `/admin/moderate/premod/${asset.id}` : '/admin/moderate/premod'; + const rejectPath = asset ? `/admin/moderate/rejected/${asset.id}` : '/admin/moderate/rejected'; + const flagPath = asset ? `/admin/modetate/flagged/${asset.id}` : '/admin/moderate/flagged'; + return ( +
    +
    +
    + + {lang.t('modqueue.premod')} + + + {lang.t('modqueue.rejected')} + + + {lang.t('modqueue.flagged')} + +
    +
    -
    -); + ); +}; export default ModerationMenu; From c53aa1d93bca14141e3a38e407afc130601da5fc Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Thu, 16 Feb 2017 12:48:36 -0700 Subject: [PATCH 05/34] add a badge to the queues to show the count. proptypes --- .../ModerationQueue/ModerationContainer.js | 4 +++- .../components/ModerationMenu.js | 20 ++++++++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js index 7755b5bb0..42ea06f2c 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js +++ b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js @@ -70,8 +70,10 @@ class ModerationContainer extends Component {
    { +const ModerationMenu = ({asset, premodCount, rejectCount, flagCount}) => { const premodPath = asset ? `/admin/moderate/premod/${asset.id}` : '/admin/moderate/premod'; const rejectPath = asset ? `/admin/moderate/rejected/${asset.id}` : '/admin/moderate/rejected'; const flagPath = asset ? `/admin/modetate/flagged/${asset.id}` : '/admin/moderate/flagged'; @@ -15,13 +16,13 @@ const ModerationMenu = ({asset}) => {
    - {lang.t('modqueue.premod')} + {lang.t('modqueue.premod')} - {lang.t('modqueue.rejected')} + {lang.t('modqueue.rejected')} - {lang.t('modqueue.flagged')} + {lang.t('modqueue.flagged')}
    @@ -29,4 +30,13 @@ const ModerationMenu = ({asset}) => { ); }; +ModerationMenu.propTypes = { + premodCount: PropTypes.number.isRequired, + rejectCount: PropTypes.number.isRequired, + flagCount: PropTypes.number.isRequired, + asset: PropTypes.shape({ + id: PropTypes.string.isRequired + }) +}; + export default ModerationMenu; From e3e85c5f3b6473bd816c651370e9909fddd9b9f2 Mon Sep 17 00:00:00 2001 From: David Jay Date: Thu, 16 Feb 2017 17:18:36 -0500 Subject: [PATCH 06/34] Fixing error when posting comment. --- client/coral-framework/graphql/mutations/postComment.graphql | 1 + 1 file changed, 1 insertion(+) diff --git a/client/coral-framework/graphql/mutations/postComment.graphql b/client/coral-framework/graphql/mutations/postComment.graphql index 3840d66c2..110ab4b4e 100644 --- a/client/coral-framework/graphql/mutations/postComment.graphql +++ b/client/coral-framework/graphql/mutations/postComment.graphql @@ -4,6 +4,7 @@ mutation CreateComment ($asset_id: ID!, $parent_id: ID, $body: String!) { createComment(asset_id:$asset_id, parent_id:$parent_id, body:$body) { comment { ...commentView + replyCount replies { ...commentView } From e5512a0e549c99657de1c53cb0ca0b59c289373f Mon Sep 17 00:00:00 2001 From: David Erwin Date: Thu, 16 Feb 2017 17:56:46 -0500 Subject: [PATCH 07/34] Standardizing username --- bin/cli-setup | 2 +- bin/cli-users | 2 +- services/passport.js | 4 ++-- services/setup.js | 2 +- services/users.js | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/bin/cli-setup b/bin/cli-setup index 7985bf220..086c473d5 100755 --- a/bin/cli-setup +++ b/bin/cli-setup @@ -120,7 +120,7 @@ const performSetup = () => { message: 'Username', filter: (username) => { return UsersService - .isValidDisplayName(username, false) + .isValidUsername(username, false) .catch((err) => { throw err.message; }); diff --git a/bin/cli-users b/bin/cli-users index 473c07d9c..fccb4b8bb 100755 --- a/bin/cli-users +++ b/bin/cli-users @@ -79,7 +79,7 @@ function getUserCreateAnswers(options) { message: 'Username', filter: (username) => { return UsersService - .isValidDisplayName(username) + .isValidUsername(username) .catch((err) => { throw err.message; }); diff --git a/services/passport.js b/services/passport.js index d265d9812..0560e54bc 100644 --- a/services/passport.js +++ b/services/passport.js @@ -103,9 +103,9 @@ if (process.env.TALK_FACEBOOK_APP_ID && process.env.TALK_FACEBOOK_APP_SECRET && clientSecret: process.env.TALK_FACEBOOK_APP_SECRET, callbackURL: `${process.env.TALK_ROOT_URL}/api/v1/auth/facebook/callback`, - // TODO: remove displayName reference when we have steps in the FE to handle + // TODO: remove username reference when we have steps in the FE to handle // the username create flow. - 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 2bfbc66a0..1f1542d5e 100644 --- a/services/setup.js +++ b/services/setup.js @@ -57,7 +57,7 @@ module.exports = class SetupService { // Verify other properties of the user. return Promise.all([ - UsersService.isValidDisplayName(username, false), + UsersService.isValidUsername(username, false), UsersService.isValidPassword(password), settingsModel.validate() ]); diff --git a/services/users.js b/services/users.js index f715fa9f4..13858f575 100644 --- a/services/users.js +++ b/services/users.js @@ -176,7 +176,7 @@ module.exports = class UsersService { * @param {Boolean} checkAgainstWordlist enables cheching against the wordlist * @return {Promise} */ - static isValidUserName(username, checkAgainstWordlist = true) { + static isValidUsername(username, checkAgainstWordlist = true) { const onlyLettersNumbersUnderscore = /^[A-Za-z0-9_]+$/; if (!username) { @@ -230,7 +230,7 @@ module.exports = class UsersService { username = username.trim(); return Promise.all([ - UsersService.isValidUserName(username), + UsersService.isValidUsername(username), UsersService.isValidPassword(password) ]) .then(() => { // username is valid From 3166692d4e3470ffd8f2cec2651542976a598cf0 Mon Sep 17 00:00:00 2001 From: gaba Date: Thu, 16 Feb 2017 15:16:54 -0800 Subject: [PATCH 08/34] Rename everywhere to username. --- bin/cli-setup | 2 +- bin/cli-users | 2 +- client/coral-embed-stream/src/Embed.js | 4 +-- client/coral-framework/actions/auth.js | 26 +++++++-------- client/coral-framework/constants/auth.js | 14 ++++---- client/coral-framework/constants/user.js | 2 +- client/coral-framework/reducers/auth.js | 19 ++++++----- client/coral-framework/translations.json | 12 ++++--- ...yNameDialog.js => CreateUsernameDialog.js} | 8 ++--- ...ontainer.js => ChangeUsernameContainer.js} | 32 +++++++++---------- errors.js | 7 ++++ services/setup.js | 2 +- services/users.js | 6 ++-- test/e2e/pages/embedStreamPage.js | 4 +-- 14 files changed, 75 insertions(+), 65 deletions(-) rename client/coral-sign-in/components/{CreateDisplayNameDialog.js => CreateUsernameDialog.js} (84%) rename client/coral-sign-in/containers/{ChangeDisplayNameContainer.js => ChangeUsernameContainer.js} (74%) diff --git a/bin/cli-setup b/bin/cli-setup index 7985bf220..086c473d5 100755 --- a/bin/cli-setup +++ b/bin/cli-setup @@ -120,7 +120,7 @@ const performSetup = () => { message: 'Username', filter: (username) => { return UsersService - .isValidDisplayName(username, false) + .isValidUsername(username, false) .catch((err) => { throw err.message; }); diff --git a/bin/cli-users b/bin/cli-users index 473c07d9c..fccb4b8bb 100755 --- a/bin/cli-users +++ b/bin/cli-users @@ -79,7 +79,7 @@ function getUserCreateAnswers(options) { message: 'Username', filter: (username) => { return UsersService - .isValidDisplayName(username) + .isValidUsername(username) .catch((err) => { throw err.message; }); diff --git a/client/coral-embed-stream/src/Embed.js b/client/coral-embed-stream/src/Embed.js index f15def5d3..8b420836e 100644 --- a/client/coral-embed-stream/src/Embed.js +++ b/client/coral-embed-stream/src/Embed.js @@ -23,7 +23,7 @@ import CommentBox from 'coral-plugin-commentbox/CommentBox'; import UserBox from 'coral-sign-in/components/UserBox'; import SignInContainer from 'coral-sign-in/containers/SignInContainer'; import SuspendedAccount from 'coral-framework/components/SuspendedAccount'; -import ChangeDisplayNameContainer from '../../coral-sign-in/containers/ChangeDisplayNameContainer'; +import ChangeUsernameContainer from '../../coral-sign-in/containers/ChangeUsernameContainer'; import SettingsContainer from 'coral-settings/containers/SettingsContainer'; import RestrictedContent from 'coral-framework/components/RestrictedContent'; import ConfigureStreamContainer from 'coral-configure/containers/ConfigureStreamContainer'; @@ -147,7 +147,7 @@ class Embed extends Component { :

    {asset.settings.closedMessage}

    } {!loggedIn && } - {loggedIn && user && } + {loggedIn && user && } ({type: actions.SHOW_SIGNIN_DIALOG, offset}); export const hideSignInDialog = () => ({type: actions.HIDE_SIGNIN_DIALOG}); -export const createDisplayNameRequest = () => ({type: actions.CREATE_DISPLAYNAME_REQUEST}); -export const showCreateDisplayNameDialog = () => ({type: actions.SHOW_CREATEDISPLAYNAME_DIALOG}); -export const hideCreateDisplayNameDialog = () => ({type: actions.HIDE_CREATEDISPLAYNAME_DIALOG}); +export const createUsernameRequest = () => ({type: actions.CREATE_USERNAME_REQUEST}); +export const showCreateUsernameDialog = () => ({type: actions.SHOW_CREATEUSERNAME_DIALOG}); +export const hideCreateUsernameDialog = () => ({type: actions.HIDE_CREATEUSERNAME_DIALOG}); -const createDisplayNameSuccess = () => ({type: actions.CREATEDISPLAYNAME_SUCCESS}); -const createDisplayNameFailure = error => ({type: actions.CREATEDISPLAYNAME_FAILURE, error}); +const createUsernameSuccess = () => ({type: actions.CREATEUSERNAME_SUCCESS}); +const createUsernameFailure = error => ({type: actions.CREATEUSERNAME_FAILURE, error}); -export const updateDisplayName = ({username}) => ({type: actions.UPDATE_DISPLAYNAME, username}); +export const updateUsername = ({username}) => ({type: actions.UPDATE_USERNAME, username}); -export const createDisplayName = (userId, formData) => dispatch => { - dispatch(createDisplayNameRequest()); +export const createUsername = (userId, formData) => dispatch => { + dispatch(createUsernameRequest()); coralApi('/account/username', {method: 'PUT', body: formData}) .then(() => { - dispatch(createDisplayNameSuccess()); - dispatch(hideCreateDisplayNameDialog()); - dispatch(updateDisplayName(formData)); + dispatch(createUsernameSuccess()); + dispatch(hideCreateUsernameDialog()); + dispatch(updateUsername(formData)); }) .catch(error => { - dispatch(createDisplayNameFailure(lang.t(`error.${error.message}`))); + dispatch(createUsernameFailure(lang.t(`error.${error.message}`))); }); }; @@ -102,7 +102,7 @@ export const facebookCallback = (err, data) => dispatch => { const user = JSON.parse(data); dispatch(signInFacebookSuccess(user)); dispatch(hideSignInDialog()); - dispatch(showCreateDisplayNameDialog()); + dispatch(showCreateUsernameDialog()); } catch (err) { dispatch(signInFacebookFailure(err)); return; diff --git a/client/coral-framework/constants/auth.js b/client/coral-framework/constants/auth.js index b6ad3d3a1..8ae032f0a 100644 --- a/client/coral-framework/constants/auth.js +++ b/client/coral-framework/constants/auth.js @@ -4,12 +4,12 @@ export const CLEAN_STATE = 'CLEAN_STATE'; export const SHOW_SIGNIN_DIALOG = 'SHOW_SIGNIN_DIALOG'; export const HIDE_SIGNIN_DIALOG = 'HIDE_SIGNIN_DIALOG'; -export const CREATE_DISPLAYNAME_REQUEST = 'CREATE_DISPLAYNAME_REQUEST'; -export const CREATEDISPLAYNAME_SUCCESS = 'CREATEDISPLAYNAME_SUCCESS'; -export const CREATEDISPLAYNAME_FAILURE = 'CREATEDISPLAYNAME_FAILURE'; -export const CREATE_DISPLAYNAME = 'CREATE_DISPLAYNAME'; -export const SHOW_CREATEDISPLAYNAME_DIALOG = 'SHOW_CREATEDISPLAYNAME_DIALOG'; -export const HIDE_CREATEDISPLAYNAME_DIALOG = 'HIDE_CREATEDISPLAYNAME_DIALOG'; +export const CREATE_USERNAME_REQUEST = 'CREATE_USERNAME_REQUEST'; +export const CREATEUSERNAME_SUCCESS = 'CREATEUSERNAME_SUCCESS'; +export const CREATEUSERNAME_FAILURE = 'CREATEUSERNAME_FAILURE'; +export const CREATE_USERNAME = 'CREATE_USERNAME'; +export const SHOW_CREATEUSERNAME_DIALOG = 'SHOW_CREATEUSERNAME_DIALOG'; +export const HIDE_CREATEUSERNAME_DIALOG = 'HIDE_CREATEUSERNAME_DIALOG'; export const FETCH_SIGNUP_REQUEST = 'FETCH_SIGNUP_REQUEST'; export const FETCH_SIGNUP_FAILURE = 'FETCH_SIGNUP_FAILURE'; @@ -44,4 +44,4 @@ export const CHECK_CSRF_TOKEN = 'CHECK_CSRF_TOKEN'; export const VERIFY_EMAIL_REQUEST = 'VERIFY_EMAIL_REQUEST'; export const VERIFY_EMAIL_SUCCESS = 'VERIFY_EMAIL_SUCCESS'; export const VERIFY_EMAIL_FAILURE = 'VERIFY_EMAIL_FAILURE'; -export const UPDATE_DISPLAYNAME = 'UPDATE_DISPLAYNAME'; +export const UPDATE_USERNAME = 'UPDATE_USERNAME'; diff --git a/client/coral-framework/constants/user.js b/client/coral-framework/constants/user.js index 5e0a23d7a..5f040db5b 100644 --- a/client/coral-framework/constants/user.js +++ b/client/coral-framework/constants/user.js @@ -5,4 +5,4 @@ export const COMMENTS_BY_USER_REQUEST = 'COMMENTS_BY_USER_REQUEST'; export const COMMENTS_BY_USER_SUCCESS = 'COMMENTS_BY_USER_SUCCESS'; export const COMMENTS_BY_USER_FAILURE = 'COMMENTS_BY_USER_FAILURE'; export const LOGOUT_SUCCESS = 'LOGOUT_SUCCESS'; -export const UPDATE_DISPLAYNAME = 'UPDATE_DISPLAYNAME'; +export const UPDATE_USERNAME = 'UPDATE_USERNAME'; diff --git a/client/coral-framework/reducers/auth.js b/client/coral-framework/reducers/auth.js index 92e9bb4c2..65cd366eb 100644 --- a/client/coral-framework/reducers/auth.js +++ b/client/coral-framework/reducers/auth.js @@ -7,7 +7,7 @@ const initialState = Map({ isAdmin: false, user: null, showSignInDialog: false, - showCreateDisplayNameDialog: false, + showCreateUsernameDialog: false, view: 'SIGNIN', error: '', passwordRequestSuccess: null, @@ -43,19 +43,19 @@ export default function auth (state = initialState, action) { emailVerificationLoading: false, successSignUp: false })); - case actions.SHOW_CREATEDISPLAYNAME_DIALOG : + case actions.SHOW_CREATEUSERNAME_DIALOG : return state - .set('showCreateDisplayNameDialog', true); - case actions.HIDE_CREATEDISPLAYNAME_DIALOG : + .set('showCreateUsernameDialog', true); + case actions.HIDE_CREATEUSERNAME_DIALOG : return state.merge(Map({ - showCreateDisplayNameDialog: false + showCreateUsernameDialog: false })); - case actions.CREATEDISPLAYNAME_SUCCESS : + case actions.CREATEUSERNAME_SUCCESS : return state.merge(Map({ - showCreateDisplayNameDialog: false, + showCreateUsernameDialog: false, error: '' })); - case actions.CREATEDISPLAYNAME_FAILURE : + case actions.CREATEUSERNAME_FAILURE : return state .set('error', action.error); case actions.CHANGE_VIEW : @@ -130,8 +130,7 @@ export default function auth (state = initialState, action) { return state .set('passwordRequestFailure', 'There was an error sending your password reset email. Please try again soon!') .set('passwordRequestSuccess', null); - case actions.UPDATE_DISPLAYNAME: - console.log('Action', action); + case actions.UPDATE_USERNAME: return state .setIn(['user', 'username'], action.username); case actions.VERIFY_EMAIL_FAILURE: diff --git a/client/coral-framework/translations.json b/client/coral-framework/translations.json index d3cf71f6e..07780e602 100644 --- a/client/coral-framework/translations.json +++ b/client/coral-framework/translations.json @@ -24,10 +24,12 @@ "PASSWORD_LENGTH": "Password is too short", "EMAIL_IN_USE": "Email address already in use", "EMAIL_DISPLAY_NAME_IN_USE": "Email address or username already in use", - "DISPLAYNAME_IN_USE": "Display name already in use", + "USERNAME_IN_USE": "Display name already in use", "DISPLAY_NAME_REQUIRED": "Must input a username", "NO_SPECIAL_CHARACTERS": "Display names can contain letters, numbers and _ only", - "PROFANITY_ERROR": "Display names must not contain profanity. Please contact the administrator if you believe this to be in error." + "PROFANITY_ERROR": "Display names must not contain profanity. Please contact the administrator if you believe this to be in error.", + "NOT_AUTHORIZED": "Not authorized.", + "EDIT_USERNAME_NOT_AUTHORIZED": "You do not have permission to update your username." } }, "es": { @@ -50,10 +52,12 @@ "PASSWORD_LENGTH": "La contraseña es muy corta", "EMAIL_IN_USE": "La dirección de correo electrónico se encuentra en uso", "EMAIL_DISPLAY_NAME_IN_USE": "Correo o Nombre en uso.", - "DISPLAYNAME_IN_USE": "Nombre en uso.", + "USERNAME_IN_USE": "Nombre en uso.", "DISPLAY_NAME_REQUIRED": "Debe ingresar un nombre", "NO_SPECIAL_CHARACTERS": "Los nombres pueden contener letras, números y _", - "PROFANITY_ERROR": "Los nombres no pueden contener blasfemias. Por favor contacte al administrador si cree que esto es un error" + "PROFANITY_ERROR": "Los nombres no pueden contener blasfemias. Por favor contacte al administrador si cree que esto es un error", + "NOT_AUTHORIZED": "Acción no autorizada.", + "EDIT_USERNAME_NOT_AUTHORIZED": "No tiene permiso para editar el nombre de usuario." } } } diff --git a/client/coral-sign-in/components/CreateDisplayNameDialog.js b/client/coral-sign-in/components/CreateUsernameDialog.js similarity index 84% rename from client/coral-sign-in/components/CreateDisplayNameDialog.js rename to client/coral-sign-in/components/CreateUsernameDialog.js index 5b04e335d..d245b38ac 100644 --- a/client/coral-sign-in/components/CreateDisplayNameDialog.js +++ b/client/coral-sign-in/components/CreateUsernameDialog.js @@ -8,10 +8,10 @@ import I18n from 'coral-framework/modules/i18n/i18n'; import translations from '../translations'; const lang = new I18n(translations); -const CreateDisplayNameDialog = ({open, handleClose, offset, formData, handleSubmitDisplayName, handleChange, ...props}) => ( +const CreateUsernameDialog = ({open, handleClose, offset, formData, handleSubmitUsername, handleChange, ...props}) => ( { props.auth.error && {props.auth.error} } -
    + ); -export default CreateDisplayNameDialog; +export default CreateUsernameDialog; diff --git a/client/coral-sign-in/containers/ChangeDisplayNameContainer.js b/client/coral-sign-in/containers/ChangeUsernameContainer.js similarity index 74% rename from client/coral-sign-in/containers/ChangeDisplayNameContainer.js rename to client/coral-sign-in/containers/ChangeUsernameContainer.js index 625985b63..33ae63614 100644 --- a/client/coral-sign-in/containers/ChangeDisplayNameContainer.js +++ b/client/coral-sign-in/containers/ChangeUsernameContainer.js @@ -4,21 +4,21 @@ import {connect} from 'react-redux'; import validate from 'coral-framework/helpers/validate'; import errorMsj from 'coral-framework/helpers/error'; -import CreateDisplayNameDialog from '../components/CreateDisplayNameDialog'; +import CreateUsernameDialog from '../components/CreateUsernameDialog'; import I18n from 'coral-framework/modules/i18n/i18n'; import translations from '../translations'; const lang = new I18n(translations); import { - showCreateDisplayNameDialog, - hideCreateDisplayNameDialog, + showCreateUsernameDialog, + hideCreateUsernameDialog, invalidForm, validForm, - createDisplayName + createUsername } from '../../coral-framework/actions/auth'; -class ChangeDisplayNameContainer extends Component { +class ChangeUsernameContainer extends Component { initialState = { formData: { username: '', @@ -31,7 +31,7 @@ class ChangeDisplayNameContainer extends Component { super(props); this.state = this.initialState; this.handleChange = this.handleChange.bind(this); - this.handleSubmitDisplayName = this.handleSubmitDisplayName.bind(this); + this.handleSubmitUsername = this.handleSubmitUsername.bind(this); this.handleClose = this.handleClose.bind(this); this.addError = this.addError.bind(this); } @@ -81,13 +81,13 @@ class ChangeDisplayNameContainer extends Component { this.setState({showErrors: show}); } - handleSubmitDisplayName(e) { + handleSubmitUsername(e) { e.preventDefault(); const {errors} = this.state; const {validForm, invalidForm} = this.props; this.displayErrors(); if (this.isCompleted() && !Object.keys(errors).length) { - this.props.createDisplayName(this.props.user.id, this.state.formData); + this.props.createUsername(this.props.user.id, this.state.formData); validForm(); } else { invalidForm(lang.t('createdisplay.checkTheForm')); @@ -95,19 +95,19 @@ class ChangeDisplayNameContainer extends Component { } handleClose() { - this.props.hideCreateDisplayNameDialog(); + this.props.hideCreateUsernameDialog(); } render() { const {loggedIn, auth, offset} = this.props; return (
    - ({ }); const mapDispatchToProps = dispatch => ({ - createDisplayName: (userid, formData) => dispatch(createDisplayName(userid, formData)), - showCreateDisplayNameDialog: () => dispatch(showCreateDisplayNameDialog()), - hideCreateDisplayNameDialog: () => dispatch(hideCreateDisplayNameDialog()), + createUsername: (userid, formData) => dispatch(createUsername(userid, formData)), + showCreateUsernameDialog: () => dispatch(showCreateUsernameDialog()), + hideCreateUsernameDialog: () => dispatch(hideCreateUsernameDialog()), invalidForm: error => dispatch(invalidForm(error)), validForm: () => dispatch(validForm()) }); @@ -132,4 +132,4 @@ const mapDispatchToProps = dispatch => ({ export default connect( mapStateToProps, mapDispatchToProps -)(ChangeDisplayNameContainer); +)(ChangeUsernameContainer); diff --git a/errors.js b/errors.js index ee68b67fd..9bfa200da 100644 --- a/errors.js +++ b/errors.js @@ -141,6 +141,12 @@ const ErrInstallLock = new APIError('install lock active', { status: 500 }); +// ErrPermissionUpdateUsername is returned when the user does not have permission to update their username. +const ErrPermissionUpdateUsername = new APIError('You do not have permission to update your username.', { + translation_key: 'EDIT_USERNAME_NOT_AUTHORIZED', + status: 500 +}); + module.exports = { ExtendableError, APIError, @@ -159,6 +165,7 @@ module.exports = { ErrInvalidAssetURL, ErrAuthentication, ErrNotAuthorized, + ErrPermissionUpdateUsername, ErrSettingsInit, ErrInstallLock }; diff --git a/services/setup.js b/services/setup.js index 2bfbc66a0..1f1542d5e 100644 --- a/services/setup.js +++ b/services/setup.js @@ -57,7 +57,7 @@ module.exports = class SetupService { // Verify other properties of the user. return Promise.all([ - UsersService.isValidDisplayName(username, false), + UsersService.isValidUsername(username, false), UsersService.isValidPassword(password), settingsModel.validate() ]); diff --git a/services/users.js b/services/users.js index f715fa9f4..aef30aa75 100644 --- a/services/users.js +++ b/services/users.js @@ -176,7 +176,7 @@ module.exports = class UsersService { * @param {Boolean} checkAgainstWordlist enables cheching against the wordlist * @return {Promise} */ - static isValidUserName(username, checkAgainstWordlist = true) { + static isValidUsername(username, checkAgainstWordlist = true) { const onlyLettersNumbersUnderscore = /^[A-Za-z0-9_]+$/; if (!username) { @@ -230,7 +230,7 @@ module.exports = class UsersService { username = username.trim(); return Promise.all([ - UsersService.isValidUserName(username), + UsersService.isValidUsername(username), UsersService.isValidPassword(password) ]) .then(() => { // username is valid @@ -688,7 +688,7 @@ module.exports = class UsersService { } }).then((result) => { return result.nModified > 0 ? result : - Promise.reject(new Error('You do not have permission to update your username.')); + Promise.reject(errors.ErrPermissionUpdateUsername); }); } }; diff --git a/test/e2e/pages/embedStreamPage.js b/test/e2e/pages/embedStreamPage.js index b25eec419..ca72df1db 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.username) + .setValue('@signUpDialogUsername', user.username) .waitForElementVisible('@signUpButton') .click('@signUpButton') .waitForElementVisible('@signInViewTrigger') @@ -95,7 +95,7 @@ module.exports = { signUpDialogConfirmPassword: { selector: '#signInDialog #confirmPassword' }, - signUpDialogDisplayName: { + signUpDialogUsername: { selector: '#signInDialog #username' }, logInButton: { From 2b4e16edfafbf46a1a85221a56f706823d5e2758 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Thu, 16 Feb 2017 16:39:02 -0700 Subject: [PATCH 09/34] fix bug when likes or flags were 0 for an article --- client/coral-admin/src/components/FlagWidget.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/coral-admin/src/components/FlagWidget.js b/client/coral-admin/src/components/FlagWidget.js index 8116d4ff9..187306c8c 100644 --- a/client/coral-admin/src/components/FlagWidget.js +++ b/client/coral-admin/src/components/FlagWidget.js @@ -23,8 +23,8 @@ const FlagWidget = ({assets}) => { { assets.length ? assets.map((asset, index) => { - const flagCount = asset.action_summaries.find(s => s.__typename === 'FlagAssetActionSummary').actionCount; - const likeCount = asset.action_summaries.find(s => s.__typename === 'LikeAssetActionSummary').actionCount; + const flagSummary = asset.action_summaries.find(s => s.__typename === 'FlagAssetActionSummary'); + const likeSummary = asset.action_summaries.find(s => s.__typename === 'LikeAssetActionSummary'); return ( {index + 1}. @@ -32,8 +32,8 @@ const FlagWidget = ({assets}) => { {asset.title}

    {asset.author} - Published: {new Date(asset.created_at).toLocaleDateString()}

    - {likeCount} - {flagCount} + {flagSummary ? flagSummary.actionCount : 0} + {likeSummary ? likeSummary.actionCount : 0} {asset.commentCount} ); From 902cd8300dfbf269219849b659391bb4915cdfd8 Mon Sep 17 00:00:00 2001 From: gaba Date: Thu, 16 Feb 2017 12:17:20 -0800 Subject: [PATCH 10/34] Adds back the timeout to close window. --- views/auth-callback.ejs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/auth-callback.ejs b/views/auth-callback.ejs index 66b9cfede..d38d759ee 100644 --- a/views/auth-callback.ejs +++ b/views/auth-callback.ejs @@ -3,7 +3,7 @@ From 1e81ec9f4e067dd7f2a0b2b6ff58e5af5f9d945a Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 16 Feb 2017 15:11:41 -0700 Subject: [PATCH 11/34] Added new commentCount edge, added more docs --- graph/loaders/comments.js | 33 ++++++++++++++++++ graph/resolvers/root_query.js | 17 ++++++++++ graph/typeDefs.graphql | 64 ++++++++++++++++++++++++----------- yarn.lock | 16 ++++----- 4 files changed, 103 insertions(+), 27 deletions(-) diff --git a/graph/loaders/comments.js b/graph/loaders/comments.js index 24b66f4bd..a6b808ef2 100644 --- a/graph/loaders/comments.js +++ b/graph/loaders/comments.js @@ -68,6 +68,38 @@ const getCountsByParentID = (context, parent_ids) => { .then((results) => results.map((result) => result ? result.count : 0)); }; +/** + * Retrieves the count of comments based on the passed in query. + * @param {Object} context graph context + * @param {Object} query query to execute against the comments collection + * to compute the counts + * @return {Promise} resolves to the counts of the comments from the + * query + */ +const getCommentCountByQuery = (context, {ids, statuses, asset_id, parent_id}) => { + let query = CommentModel.find(); + + if (ids) { + query = query.where({id: {$in: ids}}); + } + + if (statuses) { + query = query.where({status: {$in: statuses}}); + } + + if (asset_id != null) { + query = query.where({asset_id}); + } + + if (parent_id !== undefined) { + query = query.where({parent_id}); + } + + return CommentModel + .find(query) + .count(); +}; + /** * Retrieves comments based on the passed in query that is filtered by the * current used passed in via the context. @@ -233,6 +265,7 @@ const genRecentComments = (_, ids) => { module.exports = (context) => ({ Comments: { getByQuery: (query) => getCommentsByQuery(context, query), + getCountByQuery: (query) => getCommentCountByQuery(context, query), countByAssetID: new util.SharedCacheDataLoader('Comments.countByAssetID', 3600, (ids) => getCountsByAssetID(context, ids)), countByParentID: new util.SharedCacheDataLoader('Comments.countByParentID', 3600, (ids) => getCountsByParentID(context, ids)), genRecentReplies: new DataLoader((ids) => genRecentReplies(context, ids)), diff --git a/graph/resolvers/root_query.js b/graph/resolvers/root_query.js index eb66274dd..ced4e65cf 100644 --- a/graph/resolvers/root_query.js +++ b/graph/resolvers/root_query.js @@ -39,6 +39,23 @@ const RootQuery = { return Comments.getByQuery(query); }, + commentCount(_, {query: {action_type, statuses, asset_id, parent_id}}, {user, loaders: {Actions, Comments}}) { + if (user == null || !user.hasRoles('ADMIN')) { + return null; + } + + if (action_type) { + return Actions.getByTypes({action_type, item_type: 'COMMENTS'}) + .then((ids) => { + + // Perform the query using the available resolver. + return Comments.getCountByQuery({ids, statuses, asset_id, parent_id}); + }); + } + + return Comments.getCountByQuery({statuses, asset_id, parent_id}); + }, + metrics(_, {from, to, sort, limit = 10}, {user, loaders: {Metrics}}) { if (user == null || !user.hasRoles('ADMIN')) { return null; diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index cabb50985..72a54a0cd 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -27,7 +27,7 @@ type User { # The ID of the User. id: ID! - # username of a user. + # Username of a user. username: String! # Action summaries against the user. @@ -96,10 +96,40 @@ enum ACTION_TYPE { # CommentsQuery allows the ability to query comments by a specific methods. input CommentsQuery { - # current status of a comment. + # Current status of a comment. Requires the `ADMIN` role. statuses: [COMMENT_STATUS!] - # asset that a comment is on. + # Asset that a comment is on. + asset_id: ID + + # The parent of the comment that we want to retrieve. + parent_id: ID + + # Comments returned will only be ones which have at least one action of this + # type. Requires the `ADMIN` role. + action_type: ACTION_TYPE + + # Limit the number of results to be returned. + limit: Int = 10 + + # Skip results from the last created_at timestamp. + cursor: Date + + # Filter by a specific tag name. + tag: [String] + + # Sort the results by created_at. + sort: SORT_ORDER = REVERSE_CHRONOLOGICAL +} + +# CommentCountQuery allows the ability to query comment counts by specific +# methods. +input CommentCountQuery { + + # Current status of a comment. Requires the `ADMIN` role. + statuses: [COMMENT_STATUS!] + + # Asset that a comment is on. asset_id: ID # the parent of the comment that we want to retrieve. @@ -109,17 +139,8 @@ input CommentsQuery { # type. action_type: ACTION_TYPE - # limit the number of results to be returned. - limit: Int = 10 - - # skip results from the last created_at timestamp. - cursor: Date - - # filter by a specific tag name. + # Filter by a specific tag name. tag: [String] - - # sort the results by created_at. - sort: SORT_ORDER = REVERSE_CHRONOLOGICAL } # Comment is the base representation of user interaction in Talk. @@ -146,7 +167,7 @@ type Comment { # The count of replies on a comment. replyCount: Int - # Actions completed on the parent. + # Actions completed on the parent. Requires the `ADMIN` role. actions: [Action] # Action summaries against a comment. @@ -350,7 +371,7 @@ type Asset { closedAt: Date # Summary of all Actions against all entities associated with the Asset. - # (likes, flags, etc.) + # (likes, flags, etc.). Requires the `ADMIN` role. action_summaries: [AssetActionSummary] # The date that the asset was created. @@ -418,7 +439,7 @@ type RootQuery { # Site wide settings and defaults. settings: Settings - # All assets. + # All assets. Requires the `ADMIN` role. assets: [Asset] # Find or create an asset by url, or just find with the ID. @@ -427,7 +448,12 @@ type RootQuery { # Comments returned based on a query. comments(query: CommentsQuery!): [Comment] - # The currently logged in user based on the request. + # Returne the count of comments satisfied by the query. Note that this edge is + # expensive as it is not batched. Requires the `ADMIN` role. + commentCount(query: CommentCountQuery!): Int + + # The currently logged in user based on the request. Requires any logged in + # role. me: User # Metrics related to user actions are saturated into the assets returned. The @@ -554,10 +580,10 @@ type RootMutation { # Delete an action based on the action id. deleteAction(id: ID!): DeleteActionResponse - # Sets User status + # Sets User status. Requires the `ADMIN` role. setUserStatus(id: ID!, status: USER_STATUS!): SetUserStatusResponse - # Sets Comment status + # Sets Comment status. Requires the `ADMIN` role. setCommentStatus(id: ID!, status: COMMENT_STATUS!): SetCommentStatusResponse } diff --git a/yarn.lock b/yarn.lock index f1c047be2..5413439fa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -175,11 +175,11 @@ anymatch@^1.3.0: arrify "^1.0.0" micromatch "^2.1.5" -apollo-client@^0.7.3: - version "0.7.3" - resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-0.7.3.tgz#f27702409ce4b90c3adbd78d8434c3e8d762c7dd" +apollo-client@^0.8.3: + version "0.8.6" + resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-0.8.6.tgz#9aa18b03ec338f0a3804122df7f77493a10b72a0" dependencies: - graphql-anywhere "^2.0.0" + graphql-anywhere "^2.1.0" graphql-tag "^1.1.1" redux "^3.4.0" symbol-observable "^1.0.2" @@ -3303,7 +3303,7 @@ graceful-fs@~2.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" -graphql-anywhere@^2.0.0: +graphql-anywhere@^2.0.0, graphql-anywhere@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/graphql-anywhere/-/graphql-anywhere-2.1.0.tgz#888c0a1718db3ff866b313070747777380560f69" @@ -6375,9 +6375,9 @@ react-addons-test-utils@15.3.2: version "15.3.2" resolved "https://registry.yarnpkg.com/react-addons-test-utils/-/react-addons-test-utils-15.3.2.tgz#c09a44f583425a4a9c1b38444d7a6c3e6f0f41f6" -react-apollo@^0.8.1: - version "0.8.3" - resolved "https://registry.yarnpkg.com/react-apollo/-/react-apollo-0.8.3.tgz#e799bdc913948bb487dfa90921b2ea8d08464816" +react-apollo@^0.10.0: + version "0.10.1" + resolved "https://registry.yarnpkg.com/react-apollo/-/react-apollo-0.10.1.tgz#97fd50855f8575672aa68330b9c64a201cd13343" dependencies: graphql-anywhere "^2.0.0" hoist-non-react-statics "^1.2.0" From 84dcf3b3b63aaaeafe976d31701e93547d7f68f5 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 16 Feb 2017 15:47:24 -0700 Subject: [PATCH 12/34] Added some comments --- graph/loaders/comments.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/graph/loaders/comments.js b/graph/loaders/comments.js index a6b808ef2..ad78a1e92 100644 --- a/graph/loaders/comments.js +++ b/graph/loaders/comments.js @@ -133,6 +133,7 @@ const getCommentsByQuery = ({user}, {ids, statuses, asset_id, parent_id, author_ }); } + // Only let an admin request any user or the current user request themself. if (user && (user.hasRoles('ADMIN') || user.id === author_id) && author_id != null) { comments = comments.where({author_id}); } @@ -168,7 +169,13 @@ const getCommentsByQuery = ({user}, {ids, statuses, asset_id, parent_id, author_ .limit(limit); }; -const genRecentReplies = (_, ids) => { +/** + * Gets the recent replies. + * @param {Object} context graph context + * @param {Array} ids ids of parent ids + * @return {Promise} resolves to recent replies + */ +const genRecentReplies = (context, ids) => { return CommentModel.aggregate([ // get all the replies for the comments in question @@ -212,6 +219,12 @@ const genRecentReplies = (_, ids) => { .then(util.arrayJoinBy(ids, 'parent_id')); }; +/** + * Gets the recent comments. + * @param {Object} context graph context + * @param {Array} ids ids of asset ids + * @return {Promise} resolves to recent comments from assets + */ const genRecentComments = (_, ids) => { return CommentModel.aggregate([ From a267397f060aca06061f1c3cae36c024cd988865 Mon Sep 17 00:00:00 2001 From: David Erwin Date: Thu, 16 Feb 2017 17:56:46 -0500 Subject: [PATCH 13/34] Standardizing username --- bin/cli-setup | 2 +- bin/cli-users | 2 +- services/passport.js | 4 ++-- services/setup.js | 2 +- services/users.js | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/bin/cli-setup b/bin/cli-setup index 7985bf220..086c473d5 100755 --- a/bin/cli-setup +++ b/bin/cli-setup @@ -120,7 +120,7 @@ const performSetup = () => { message: 'Username', filter: (username) => { return UsersService - .isValidDisplayName(username, false) + .isValidUsername(username, false) .catch((err) => { throw err.message; }); diff --git a/bin/cli-users b/bin/cli-users index 473c07d9c..fccb4b8bb 100755 --- a/bin/cli-users +++ b/bin/cli-users @@ -79,7 +79,7 @@ function getUserCreateAnswers(options) { message: 'Username', filter: (username) => { return UsersService - .isValidDisplayName(username) + .isValidUsername(username) .catch((err) => { throw err.message; }); diff --git a/services/passport.js b/services/passport.js index d265d9812..0560e54bc 100644 --- a/services/passport.js +++ b/services/passport.js @@ -103,9 +103,9 @@ if (process.env.TALK_FACEBOOK_APP_ID && process.env.TALK_FACEBOOK_APP_SECRET && clientSecret: process.env.TALK_FACEBOOK_APP_SECRET, callbackURL: `${process.env.TALK_ROOT_URL}/api/v1/auth/facebook/callback`, - // TODO: remove displayName reference when we have steps in the FE to handle + // TODO: remove username reference when we have steps in the FE to handle // the username create flow. - 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 2bfbc66a0..1f1542d5e 100644 --- a/services/setup.js +++ b/services/setup.js @@ -57,7 +57,7 @@ module.exports = class SetupService { // Verify other properties of the user. return Promise.all([ - UsersService.isValidDisplayName(username, false), + UsersService.isValidUsername(username, false), UsersService.isValidPassword(password), settingsModel.validate() ]); diff --git a/services/users.js b/services/users.js index f715fa9f4..13858f575 100644 --- a/services/users.js +++ b/services/users.js @@ -176,7 +176,7 @@ module.exports = class UsersService { * @param {Boolean} checkAgainstWordlist enables cheching against the wordlist * @return {Promise} */ - static isValidUserName(username, checkAgainstWordlist = true) { + static isValidUsername(username, checkAgainstWordlist = true) { const onlyLettersNumbersUnderscore = /^[A-Za-z0-9_]+$/; if (!username) { @@ -230,7 +230,7 @@ module.exports = class UsersService { username = username.trim(); return Promise.all([ - UsersService.isValidUserName(username), + UsersService.isValidUsername(username), UsersService.isValidPassword(password) ]) .then(() => { // username is valid From f925e6a8ea2fecfd9805928bc3c9925cd58581ac Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Thu, 16 Feb 2017 16:39:02 -0700 Subject: [PATCH 14/34] fix bug when likes or flags were 0 for an article --- client/coral-admin/src/components/FlagWidget.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/coral-admin/src/components/FlagWidget.js b/client/coral-admin/src/components/FlagWidget.js index 8116d4ff9..187306c8c 100644 --- a/client/coral-admin/src/components/FlagWidget.js +++ b/client/coral-admin/src/components/FlagWidget.js @@ -23,8 +23,8 @@ const FlagWidget = ({assets}) => { { assets.length ? assets.map((asset, index) => { - const flagCount = asset.action_summaries.find(s => s.__typename === 'FlagAssetActionSummary').actionCount; - const likeCount = asset.action_summaries.find(s => s.__typename === 'LikeAssetActionSummary').actionCount; + const flagSummary = asset.action_summaries.find(s => s.__typename === 'FlagAssetActionSummary'); + const likeSummary = asset.action_summaries.find(s => s.__typename === 'LikeAssetActionSummary'); return ( {index + 1}. @@ -32,8 +32,8 @@ const FlagWidget = ({assets}) => { {asset.title}

    {asset.author} - Published: {new Date(asset.created_at).toLocaleDateString()}

    - {likeCount} - {flagCount} + {flagSummary ? flagSummary.actionCount : 0} + {likeSummary ? likeSummary.actionCount : 0} {asset.commentCount} ); From 4765973a58886a8938551bb33aa8828a5af31b2a Mon Sep 17 00:00:00 2001 From: gaba Date: Thu, 16 Feb 2017 16:07:19 -0800 Subject: [PATCH 15/34] Other small changes and fix bug because displayname instead displayName... --- client/coral-framework/actions/auth.js | 6 +++--- client/coral-framework/constants/auth.js | 4 ++-- client/coral-framework/reducers/auth.js | 4 ++-- services/passport.js | 2 -- services/users.js | 10 +++++----- 5 files changed, 12 insertions(+), 14 deletions(-) diff --git a/client/coral-framework/actions/auth.js b/client/coral-framework/actions/auth.js index 51a06e2d7..7576478c4 100644 --- a/client/coral-framework/actions/auth.js +++ b/client/coral-framework/actions/auth.js @@ -12,8 +12,8 @@ export const createUsernameRequest = () => ({type: actions.CREATE_USERNAME_REQUE export const showCreateUsernameDialog = () => ({type: actions.SHOW_CREATEUSERNAME_DIALOG}); export const hideCreateUsernameDialog = () => ({type: actions.HIDE_CREATEUSERNAME_DIALOG}); -const createUsernameSuccess = () => ({type: actions.CREATEUSERNAME_SUCCESS}); -const createUsernameFailure = error => ({type: actions.CREATEUSERNAME_FAILURE, error}); +const createUsernameSuccess = () => ({type: actions.CREATE_USERNAME_SUCCESS}); +const createUsernameFailure = error => ({type: actions.CREATE_USERNAME_FAILURE, error}); export const updateUsername = ({username}) => ({type: actions.UPDATE_USERNAME, username}); @@ -95,7 +95,7 @@ export const fetchSignUpFacebook = () => dispatch => { export const facebookCallback = (err, data) => dispatch => { if (err) { - signInFacebookFailure(err); + dispatch(signInFacebookFailure(err)); return; } try { diff --git a/client/coral-framework/constants/auth.js b/client/coral-framework/constants/auth.js index 8ae032f0a..9a217fb43 100644 --- a/client/coral-framework/constants/auth.js +++ b/client/coral-framework/constants/auth.js @@ -5,8 +5,8 @@ export const SHOW_SIGNIN_DIALOG = 'SHOW_SIGNIN_DIALOG'; export const HIDE_SIGNIN_DIALOG = 'HIDE_SIGNIN_DIALOG'; export const CREATE_USERNAME_REQUEST = 'CREATE_USERNAME_REQUEST'; -export const CREATEUSERNAME_SUCCESS = 'CREATEUSERNAME_SUCCESS'; -export const CREATEUSERNAME_FAILURE = 'CREATEUSERNAME_FAILURE'; +export const CREATE_USERNAME_SUCCESS = 'CREATE_USERNAME_SUCCESS'; +export const CREATE_USERNAME_FAILURE = 'CREATE_USERNAME_FAILURE'; export const CREATE_USERNAME = 'CREATE_USERNAME'; export const SHOW_CREATEUSERNAME_DIALOG = 'SHOW_CREATEUSERNAME_DIALOG'; export const HIDE_CREATEUSERNAME_DIALOG = 'HIDE_CREATEUSERNAME_DIALOG'; diff --git a/client/coral-framework/reducers/auth.js b/client/coral-framework/reducers/auth.js index 65cd366eb..f9712c937 100644 --- a/client/coral-framework/reducers/auth.js +++ b/client/coral-framework/reducers/auth.js @@ -50,12 +50,12 @@ export default function auth (state = initialState, action) { return state.merge(Map({ showCreateUsernameDialog: false })); - case actions.CREATEUSERNAME_SUCCESS : + case actions.CREATE_USERNAME_SUCCESS : return state.merge(Map({ showCreateUsernameDialog: false, error: '' })); - case actions.CREATEUSERNAME_FAILURE : + case actions.CREATE_USERNAME_FAILURE : return state .set('error', action.error); case actions.CHANGE_VIEW : diff --git a/services/passport.js b/services/passport.js index d265d9812..229541521 100644 --- a/services/passport.js +++ b/services/passport.js @@ -103,8 +103,6 @@ if (process.env.TALK_FACEBOOK_APP_ID && process.env.TALK_FACEBOOK_APP_SECRET && clientSecret: process.env.TALK_FACEBOOK_APP_SECRET, callbackURL: `${process.env.TALK_ROOT_URL}/api/v1/auth/facebook/callback`, - // 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 diff --git a/services/users.js b/services/users.js index aef30aa75..a319cebe2 100644 --- a/services/users.js +++ b/services/users.js @@ -109,7 +109,7 @@ module.exports = class UsersService { * @param {Object} profile - User social/external profile * @param {Function} done [description] */ - static findOrCreateExternalUser({id, provider, displayname}) { + static findOrCreateExternalUser({id, provider, displayName}) { return UserModel .findOne({ profiles: { @@ -123,8 +123,8 @@ module.exports = class UsersService { if (user) { return user; } - - let username = UsersService.castUsername(displayname); + + let username = UsersService.castUsername(displayName); // The user was not found, lets create them! user = new UserModel({ @@ -220,14 +220,14 @@ module.exports = class UsersService { * @param {String} username name of the display user * @param {Function} done callback */ - static createLocalUser(email, password, username) { + static createLocalUser(email, password, displayname) { if (!email) { return Promise.reject(errors.ErrMissingEmail); } email = email.toLowerCase().trim(); - username = username.trim(); + let username = displayname.trim(); return Promise.all([ UsersService.isValidUsername(username), From 8502b4397470f7703bae837c23473a66a35d72fe Mon Sep 17 00:00:00 2001 From: David Erwin Date: Fri, 17 Feb 2017 10:04:15 -0500 Subject: [PATCH 16/34] Updating more displayname to username --- client/coral-framework/translations.json | 12 ++++++------ client/coral-sign-in/translations.js | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/client/coral-framework/translations.json b/client/coral-framework/translations.json index 07780e602..5e6a307e4 100644 --- a/client/coral-framework/translations.json +++ b/client/coral-framework/translations.json @@ -9,13 +9,13 @@ "msg": "Your account is currently suspended because your username has been deemed inappropriate. To restore your account, please enter a new username. You may contact moderator@fakeurl.com for more information.", "label": "New Username", "button": "Submit", - "error": "Display names can contain letters, numbers and _ only" + "error": "Usernames can contain letters, numbers and _ only" }, "error": { "emailNotVerified": "Email address {0} not verified.", "email": "Not a valid E-Mail", "password": "Password must be at least 8 characters", - "username": "Display names can contain letters, numbers and _ only", + "username": "Usernames can contain letters, numbers and _ only", "confirmPassword": "Passwords don't match. Please, check again", "organizationName": "Organization name must only contain letters or numbers.", "emailPasswordError": "Email and/or password combination incorrect.", @@ -23,11 +23,11 @@ "PASSWORD_REQUIRED": "Must input a password", "PASSWORD_LENGTH": "Password is too short", "EMAIL_IN_USE": "Email address already in use", - "EMAIL_DISPLAY_NAME_IN_USE": "Email address or username already in use", - "USERNAME_IN_USE": "Display name already in use", + "EMAIL_USERNAME_IN_USE": "Email address or username already in use", + "USERNAME_IN_USE": "Username already in use", "DISPLAY_NAME_REQUIRED": "Must input a username", - "NO_SPECIAL_CHARACTERS": "Display names can contain letters, numbers and _ only", - "PROFANITY_ERROR": "Display names must not contain profanity. Please contact the administrator if you believe this to be in error.", + "NO_SPECIAL_CHARACTERS": "Usernames can contain letters, numbers and _ only", + "PROFANITY_ERROR": "Usernames must not contain profanity. Please contact the administrator if you believe this to be in error.", "NOT_AUTHORIZED": "Not authorized.", "EDIT_USERNAME_NOT_AUTHORIZED": "You do not have permission to update your username." } diff --git a/client/coral-sign-in/translations.js b/client/coral-sign-in/translations.js index 36ce3fce4..0ac463b1c 100644 --- a/client/coral-sign-in/translations.js +++ b/client/coral-sign-in/translations.js @@ -26,7 +26,7 @@ export default { emailORusernameInUse: 'Email address or Username already in use', requiredField: 'This field is required', passwordsDontMatch: 'Passwords don\'t match.', - specialCharacters: 'Display names can contain letters, numbers and _ only', + specialCharacters: 'Usernames can contain letters, numbers and _ only', checkTheForm: 'Invalid Form. Please, check the fields' }, 'createdisplay': { @@ -37,7 +37,7 @@ export default { requiredField: 'Required field', errorCreate: 'Error when changing username', checkTheForm: 'Invalid Form. Please, check the fields', - specialCharacters: 'Display names can contain letters, numbers and _ only' + specialCharacters: 'Usernames can contain letters, numbers and _ only' }, }, es: { From 5370a6f81a14b8ab95f5eb5b19d3b945471997bf Mon Sep 17 00:00:00 2001 From: gaba Date: Fri, 17 Feb 2017 08:05:32 -0800 Subject: [PATCH 17/34] A few more changes from display to username. --- client/coral-framework/translations.json | 6 +++--- errors.js | 12 ++++++------ routes/api/account/index.js | 2 +- services/users.js | 10 +++++----- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/client/coral-framework/translations.json b/client/coral-framework/translations.json index 5e6a307e4..2884178b6 100644 --- a/client/coral-framework/translations.json +++ b/client/coral-framework/translations.json @@ -25,7 +25,7 @@ "EMAIL_IN_USE": "Email address already in use", "EMAIL_USERNAME_IN_USE": "Email address or username already in use", "USERNAME_IN_USE": "Username already in use", - "DISPLAY_NAME_REQUIRED": "Must input a username", + "USERNAME_REQUIRED": "Must input a username", "NO_SPECIAL_CHARACTERS": "Usernames can contain letters, numbers and _ only", "PROFANITY_ERROR": "Usernames must not contain profanity. Please contact the administrator if you believe this to be in error.", "NOT_AUTHORIZED": "Not authorized.", @@ -51,9 +51,9 @@ "PASSWORD_REQUIRED": "Debe ingresar una contraseña", "PASSWORD_LENGTH": "La contraseña es muy corta", "EMAIL_IN_USE": "La dirección de correo electrónico se encuentra en uso", - "EMAIL_DISPLAY_NAME_IN_USE": "Correo o Nombre en uso.", + "EMAIL_USERNAME_IN_USE": "Correo o Nombre en uso.", "USERNAME_IN_USE": "Nombre en uso.", - "DISPLAY_NAME_REQUIRED": "Debe ingresar un nombre", + "USERNAME_REQUIRED": "Debe ingresar un nombre", "NO_SPECIAL_CHARACTERS": "Los nombres pueden contener letras, números y _", "PROFANITY_ERROR": "Los nombres no pueden contener blasfemias. Por favor contacte al administrador si cree que esto es un error", "NOT_AUTHORIZED": "Acción no autorizada.", diff --git a/errors.js b/errors.js index 9bfa200da..0c473d59c 100644 --- a/errors.js +++ b/errors.js @@ -54,8 +54,8 @@ const ErrEmailTaken = new APIError('Email address already in use', { status: 400 }); -const ErrDisplayTaken = new APIError('Username already in use', { - translation_key: 'DISPLAYNAME_IN_USE', +const ErrUsernameTaken = new APIError('Username already in use', { + translation_key: 'USERNAME_IN_USE', status: 400 }); @@ -64,8 +64,8 @@ const ErrSpecialChars = new APIError('No special characters are allowed in a use status: 400 }); -const ErrMissingDisplay = new APIError('A username is required to create a user', { - translation_key: 'DISPLAY_NAME_REQUIRED', +const ErrMissingUsername = new APIError('A username is required to create a user', { + translation_key: 'USERNAME_REQUIRED', status: 400 }); @@ -157,9 +157,9 @@ module.exports = { ErrMissingToken, ErrEmailTaken, ErrSpecialChars, - ErrMissingDisplay, + ErrMissingUsername, ErrContainsProfanity, - ErrDisplayTaken, + ErrUsernameTaken, ErrAssetCommentingClosed, ErrNotFound, ErrInvalidAssetURL, diff --git a/routes/api/account/index.js b/routes/api/account/index.js index 3de9d41c3..68befd665 100644 --- a/routes/api/account/index.js +++ b/routes/api/account/index.js @@ -123,7 +123,7 @@ router.put('/username', authorization.needed(), (req, res, next) => { }) .catch(error => { if (error.code === 11000) { - next(errors.ErrDisplayTaken); + next(errors.ErrUsernameTake); } else { next(error); } diff --git a/services/users.js b/services/users.js index a319cebe2..df9536525 100644 --- a/services/users.js +++ b/services/users.js @@ -123,7 +123,7 @@ module.exports = class UsersService { if (user) { return user; } - + let username = UsersService.castUsername(displayName); // The user was not found, lets create them! @@ -180,7 +180,7 @@ module.exports = class UsersService { const onlyLettersNumbersUnderscore = /^[A-Za-z0-9_]+$/; if (!username) { - return Promise.reject(errors.ErrMissingDisplay); + return Promise.reject(errors.ErrMissingUsername); } if (!onlyLettersNumbersUnderscore.test(username)) { @@ -220,14 +220,14 @@ module.exports = class UsersService { * @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(); - let username = displayname.trim(); + username = username.trim(); return Promise.all([ UsersService.isValidUsername(username), @@ -257,7 +257,7 @@ module.exports = class UsersService { if (err) { if (err.code === 11000) { if (err.message.match('Username')) { - return reject(errors.ErrDisplayTaken); + return reject(errors.ErrUsernameTake); } return reject(errors.ErrEmailTaken); } From 3bfac591b38e2336565590625d90b07bd7e7f4b2 Mon Sep 17 00:00:00 2001 From: gaba Date: Fri, 17 Feb 2017 08:20:27 -0800 Subject: [PATCH 18/34] An N was missing... --- routes/api/account/index.js | 2 +- services/users.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/routes/api/account/index.js b/routes/api/account/index.js index 68befd665..4eab8b920 100644 --- a/routes/api/account/index.js +++ b/routes/api/account/index.js @@ -123,7 +123,7 @@ router.put('/username', authorization.needed(), (req, res, next) => { }) .catch(error => { if (error.code === 11000) { - next(errors.ErrUsernameTake); + next(errors.ErrUsernameTaken); } else { next(error); } diff --git a/services/users.js b/services/users.js index df9536525..b09a7d399 100644 --- a/services/users.js +++ b/services/users.js @@ -257,7 +257,7 @@ module.exports = class UsersService { if (err) { if (err.code === 11000) { if (err.message.match('Username')) { - return reject(errors.ErrUsernameTake); + return reject(errors.ErrUsernameTaken); } return reject(errors.ErrEmailTaken); } From 41d06109854c102d1dbf4374a1adbe48f1778f53 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Fri, 17 Feb 2017 13:22:41 -0300 Subject: [PATCH 19/34] PropTypes --- .../ModerationQueue/ModerationContainer.js | 6 +++--- .../ModerationQueue/components/CommentCount.css | 14 ++++++++++++++ .../ModerationQueue/components/CommentCount.js | 12 ++++++++++++ .../ModerationQueue/components/ModerationMenu.js | 10 +++++----- .../src/graphql/queries/modQueueQuery.graphql | 13 +++++++++++++ 5 files changed, 47 insertions(+), 8 deletions(-) create mode 100644 client/coral-admin/src/containers/ModerationQueue/components/CommentCount.css create mode 100644 client/coral-admin/src/containers/ModerationQueue/components/CommentCount.js diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js index 42ea06f2c..f685ac094 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js +++ b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js @@ -71,9 +71,9 @@ class ModerationContainer extends Component { ( + {props.children} +); + +CommentCount.propTypes = { + children: PropTypes.node +}; + +export default CommentCount; diff --git a/client/coral-admin/src/containers/ModerationQueue/components/ModerationMenu.js b/client/coral-admin/src/containers/ModerationQueue/components/ModerationMenu.js index 47383c535..945c28be4 100644 --- a/client/coral-admin/src/containers/ModerationQueue/components/ModerationMenu.js +++ b/client/coral-admin/src/containers/ModerationQueue/components/ModerationMenu.js @@ -1,13 +1,13 @@ import React, {PropTypes} from 'react'; +import CommentCount from './CommentCount'; import styles from './styles.css'; import I18n from 'coral-framework/modules/i18n/i18n'; import translations from 'coral-admin/src/translations.json'; import {Link} from 'react-router'; -import {Badge} from 'react-mdl'; const lang = new I18n(translations); -const ModerationMenu = ({asset, premodCount, rejectCount, flagCount}) => { +const ModerationMenu = ({asset, premodCount, rejectedCount, flaggedCount}) => { const premodPath = asset ? `/admin/moderate/premod/${asset.id}` : '/admin/moderate/premod'; const rejectPath = asset ? `/admin/moderate/rejected/${asset.id}` : '/admin/moderate/rejected'; const flagPath = asset ? `/admin/modetate/flagged/${asset.id}` : '/admin/moderate/flagged'; @@ -16,13 +16,13 @@ const ModerationMenu = ({asset, premodCount, rejectCount, flagCount}) => {
    - {lang.t('modqueue.premod')} + {lang.t('modqueue.premod')}{premodCount} - {lang.t('modqueue.rejected')} + {lang.t('modqueue.rejected')}{rejectedCount} - {lang.t('modqueue.flagged')} + {lang.t('modqueue.flagged')}{flaggedCount}
    diff --git a/client/coral-admin/src/graphql/queries/modQueueQuery.graphql b/client/coral-admin/src/graphql/queries/modQueueQuery.graphql index 735f3294e..f57e25eb2 100644 --- a/client/coral-admin/src/graphql/queries/modQueueQuery.graphql +++ b/client/coral-admin/src/graphql/queries/modQueueQuery.graphql @@ -30,4 +30,17 @@ query ModQueue ($asset_id: ID!) { id title } + premodCount: commentCount(query: { + statuses: [PREMOD], + asset_id: $asset_id + }) + rejectedCount: commentCount(query: { + statuses: [REJECTED], + asset_id: $asset_id + }) + flaggedCount: commentCount(query: { + action_type: FLAG, + asset_id: $asset_id, + statuses: [NONE, PREMOD] + }) } From a35af36ee1eaf06aee7ca8a121e8d82952cf7c1d Mon Sep 17 00:00:00 2001 From: David Jay Date: Fri, 17 Feb 2017 11:26:19 -0500 Subject: [PATCH 20/34] Handling premod in optimistic comment updates. --- .../coral-framework/graphql/mutations/index.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/client/coral-framework/graphql/mutations/index.js b/client/coral-framework/graphql/mutations/index.js index b67343521..cdc57e73e 100644 --- a/client/coral-framework/graphql/mutations/index.js +++ b/client/coral-framework/graphql/mutations/index.js @@ -25,7 +25,7 @@ export const postComment = graphql(POST_COMMENT, { id: ownProps.auth.user.id, name: ownProps.auth.user.username }, - created_at: new Date(), + created_at: new Date().toString(), body, parent_id, asset_id, @@ -37,17 +37,22 @@ export const postComment = graphql(POST_COMMENT, { } }, updateQueries: { - AssetQuery: (oldData, {mutationResult:{data:{createComment:{comment}}}}) => + AssetQuery: (oldData, {mutationResult:{data:{createComment:{comment}}}}) => { + + if (oldData.asset.moderation === 'PRE') { + return oldData; + } // If posting a reply - parent_id ? { + return parent_id ? { ...oldData, asset: { ...oldData.asset, - comments: oldData.asset.comments.map((oldComment) => - oldComment.id === parent_id + comments: oldData.asset.comments.map((oldComment) => { + return oldComment.id === parent_id ? {...oldComment, replies: [...oldComment.replies, comment]} - : oldComment) + : oldComment; + }) } } @@ -59,6 +64,7 @@ export const postComment = graphql(POST_COMMENT, { commentCount: oldData.asset.commentCount + 1, comments: [comment, ...oldData.asset.comments] } + }; } } }) From dd3435fffe66ceb7f01f8a03543056ad6b0c2357 Mon Sep 17 00:00:00 2001 From: gaba Date: Fri, 17 Feb 2017 08:42:48 -0800 Subject: [PATCH 21/34] Replace with translation_key to manage error translation. --- client/coral-framework/actions/auth.js | 4 ++-- client/coral-sign-in/translations.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/coral-framework/actions/auth.js b/client/coral-framework/actions/auth.js index 7576478c4..8618cf1d3 100644 --- a/client/coral-framework/actions/auth.js +++ b/client/coral-framework/actions/auth.js @@ -26,7 +26,7 @@ export const createUsername = (userId, formData) => dispatch => { dispatch(updateUsername(formData)); }) .catch(error => { - dispatch(createUsernameFailure(lang.t(`error.${error.message}`))); + dispatch(createUsernameFailure(lang.t(`error.${error.translation_key}`))); }); }; @@ -123,7 +123,7 @@ export const fetchSignUp = (formData, redirectUri) => (dispatch) => { dispatch(signUpSuccess(user)); }) .catch(error => { - dispatch(signUpFailure(lang.t(`error.${error.message}`))); + dispatch(signUpFailure(lang.t(`error.${error.translation_key}`))); }); }; diff --git a/client/coral-sign-in/translations.js b/client/coral-sign-in/translations.js index 0ac463b1c..a2bb60819 100644 --- a/client/coral-sign-in/translations.js +++ b/client/coral-sign-in/translations.js @@ -38,7 +38,7 @@ export default { errorCreate: 'Error when changing username', checkTheForm: 'Invalid Form. Please, check the fields', specialCharacters: 'Usernames can contain letters, numbers and _ only' - }, + } }, es: { 'signIn': { From 7eca9db63cd51385b8f46470140c67f6a9ab3be2 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Fri, 17 Feb 2017 13:53:34 -0300 Subject: [PATCH 22/34] null assetIDs and old typo --- .../ModerationQueue/components/ModerationMenu.js | 8 ++++---- client/coral-admin/src/graphql/queries/index.js | 2 +- .../coral-admin/src/graphql/queries/modQueueQuery.graphql | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/client/coral-admin/src/containers/ModerationQueue/components/ModerationMenu.js b/client/coral-admin/src/containers/ModerationQueue/components/ModerationMenu.js index 945c28be4..d431e4175 100644 --- a/client/coral-admin/src/containers/ModerationQueue/components/ModerationMenu.js +++ b/client/coral-admin/src/containers/ModerationQueue/components/ModerationMenu.js @@ -10,7 +10,7 @@ const lang = new I18n(translations); const ModerationMenu = ({asset, premodCount, rejectedCount, flaggedCount}) => { const premodPath = asset ? `/admin/moderate/premod/${asset.id}` : '/admin/moderate/premod'; const rejectPath = asset ? `/admin/moderate/rejected/${asset.id}` : '/admin/moderate/rejected'; - const flagPath = asset ? `/admin/modetate/flagged/${asset.id}` : '/admin/moderate/flagged'; + const flagPath = asset ? `/admin/moderate/flagged/${asset.id}` : '/admin/moderate/flagged'; return (
    @@ -32,10 +32,10 @@ const ModerationMenu = ({asset, premodCount, rejectedCount, flaggedCount}) => { ModerationMenu.propTypes = { premodCount: PropTypes.number.isRequired, - rejectCount: PropTypes.number.isRequired, - flagCount: PropTypes.number.isRequired, + rejectedCount: PropTypes.number.isRequired, + flaggedCount: PropTypes.number.isRequired, asset: PropTypes.shape({ - id: PropTypes.string.isRequired + id: PropTypes.string }) }; diff --git a/client/coral-admin/src/graphql/queries/index.js b/client/coral-admin/src/graphql/queries/index.js index 5a429756e..3325249e1 100644 --- a/client/coral-admin/src/graphql/queries/index.js +++ b/client/coral-admin/src/graphql/queries/index.js @@ -20,7 +20,7 @@ export const mostFlags = graphql(MOST_FLAGS, { }); export const modQueueQuery = graphql(MOD_QUEUE_QUERY, { - options: ({params: {id = ''}}) => { + options: ({params: {id = null}}) => { 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 f57e25eb2..200e97d37 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 ModQueue ($asset_id: ID!) { +query ModQueue ($asset_id: ID) { premod: comments(query: { statuses: [PREMOD], asset_id: $asset_id From 25f366c5f19707098ef87944c9214af0fe143542 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Fri, 17 Feb 2017 13:55:35 -0300 Subject: [PATCH 23/34] Comment Count Component --- .../containers/ModerationQueue/components/CommentCount.js | 4 ++-- .../ModerationQueue/components/ModerationMenu.js | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/client/coral-admin/src/containers/ModerationQueue/components/CommentCount.js b/client/coral-admin/src/containers/ModerationQueue/components/CommentCount.js index 9835aaf44..14cf8ecfc 100644 --- a/client/coral-admin/src/containers/ModerationQueue/components/CommentCount.js +++ b/client/coral-admin/src/containers/ModerationQueue/components/CommentCount.js @@ -2,11 +2,11 @@ import React, {PropTypes} from 'react'; import styles from './CommentCount.css'; const CommentCount = props => ( - {props.children} + {props.count} ); CommentCount.propTypes = { - children: PropTypes.node + count: PropTypes.number.isRequired }; export default CommentCount; diff --git a/client/coral-admin/src/containers/ModerationQueue/components/ModerationMenu.js b/client/coral-admin/src/containers/ModerationQueue/components/ModerationMenu.js index d431e4175..767bc815d 100644 --- a/client/coral-admin/src/containers/ModerationQueue/components/ModerationMenu.js +++ b/client/coral-admin/src/containers/ModerationQueue/components/ModerationMenu.js @@ -16,14 +16,14 @@ const ModerationMenu = ({asset, premodCount, rejectedCount, flaggedCount}) => {
    - {lang.t('modqueue.premod')}{premodCount} + {lang.t('modqueue.premod')} - {lang.t('modqueue.rejected')}{rejectedCount} + {lang.t('modqueue.rejected')} - {lang.t('modqueue.flagged')}{flaggedCount} - + {lang.t('modqueue.flagged')} +
    From 761afeaf4f97f22452710ca63016318fec67cdbd Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Fri, 17 Feb 2017 14:15:16 -0300 Subject: [PATCH 24/34] Stream cog, settings --- .../ModerationQueue/components/ModerationHeader.js | 6 +++++- .../src/containers/ModerationQueue/components/styles.css | 8 ++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/client/coral-admin/src/containers/ModerationQueue/components/ModerationHeader.js b/client/coral-admin/src/containers/ModerationQueue/components/ModerationHeader.js index 474d8fd27..10ec27af3 100644 --- a/client/coral-admin/src/containers/ModerationQueue/components/ModerationHeader.js +++ b/client/coral-admin/src/containers/ModerationQueue/components/ModerationHeader.js @@ -1,5 +1,6 @@ import React from 'react'; import {Link} from 'react-router'; +import {Icon} from 'coral-ui'; import styles from './styles.css'; const ModerationHeader = props => ( @@ -9,7 +10,10 @@ const ModerationHeader = props => ( props.asset ?
    All Streams - {props.asset.title} + + {props.asset.title} + + Select Stream
    : diff --git a/client/coral-admin/src/containers/ModerationQueue/components/styles.css b/client/coral-admin/src/containers/ModerationQueue/components/styles.css index 705fdd16f..e2fd31911 100644 --- a/client/coral-admin/src/containers/ModerationQueue/components/styles.css +++ b/client/coral-admin/src/containers/ModerationQueue/components/styles.css @@ -77,6 +77,14 @@ span { color: white; margin-bottom: -1px; + .settingsButton { + i { + vertical-align: middle; + margin-left: 10px; + margin-top: -4px; + } + } + .moderateAsset { a { -webkit-box-flex: 1; From 051978a1eeb53483c54ca0bafed5c8388fce643a Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Fri, 17 Feb 2017 14:21:47 -0300 Subject: [PATCH 25/34] to the configure --- .../containers/ModerationQueue/components/ModerationHeader.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/coral-admin/src/containers/ModerationQueue/components/ModerationHeader.js b/client/coral-admin/src/containers/ModerationQueue/components/ModerationHeader.js index 10ec27af3..bcd06b072 100644 --- a/client/coral-admin/src/containers/ModerationQueue/components/ModerationHeader.js +++ b/client/coral-admin/src/containers/ModerationQueue/components/ModerationHeader.js @@ -12,7 +12,7 @@ const ModerationHeader = props => ( All Streams {props.asset.title} - + Select Stream
    From 2d374fcdab1c04cddeb642cd264da826ce50a24f Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Fri, 17 Feb 2017 15:08:55 -0300 Subject: [PATCH 26/34] =?UTF-8?q?=C3=9Asing=20the=20db=20url?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../containers/ModerationQueue/components/ModerationHeader.js | 2 +- client/coral-admin/src/graphql/queries/modQueueQuery.graphql | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/client/coral-admin/src/containers/ModerationQueue/components/ModerationHeader.js b/client/coral-admin/src/containers/ModerationQueue/components/ModerationHeader.js index bcd06b072..c5a11b642 100644 --- a/client/coral-admin/src/containers/ModerationQueue/components/ModerationHeader.js +++ b/client/coral-admin/src/containers/ModerationQueue/components/ModerationHeader.js @@ -12,7 +12,7 @@ const ModerationHeader = props => ( All Streams {props.asset.title} - + Select Stream
    diff --git a/client/coral-admin/src/graphql/queries/modQueueQuery.graphql b/client/coral-admin/src/graphql/queries/modQueueQuery.graphql index 200e97d37..86cd26bbd 100644 --- a/client/coral-admin/src/graphql/queries/modQueueQuery.graphql +++ b/client/coral-admin/src/graphql/queries/modQueueQuery.graphql @@ -29,6 +29,7 @@ query ModQueue ($asset_id: ID) { assets: assets { id title + url } premodCount: commentCount(query: { statuses: [PREMOD], From 5a09beb727a48920cd03f4c2e1431839a85364fa Mon Sep 17 00:00:00 2001 From: gaba Date: Fri, 17 Feb 2017 10:12:37 -0800 Subject: [PATCH 27/34] It has to be translation_key and not message. --- client/coral-admin/src/actions/auth.js | 2 +- client/coral-admin/src/actions/install.js | 4 ++-- client/coral-framework/actions/auth.js | 2 +- client/coral-framework/components/SuspendedAccount.js | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/client/coral-admin/src/actions/auth.js b/client/coral-admin/src/actions/auth.js index 8bc0d4e4c..bdb101362 100644 --- a/client/coral-admin/src/actions/auth.js +++ b/client/coral-admin/src/actions/auth.js @@ -16,7 +16,7 @@ export const checkLogin = () => dispatch => { }) .catch(error => { console.error(error); - dispatch(checkLoginFailure(`${error.message}`)); + dispatch(checkLoginFailure(`${error.translation_key}`)); }); }; diff --git a/client/coral-admin/src/actions/install.js b/client/coral-admin/src/actions/install.js index a74a1929f..c1d05d253 100644 --- a/client/coral-admin/src/actions/install.js +++ b/client/coral-admin/src/actions/install.js @@ -81,7 +81,7 @@ export const submitUser = () => (dispatch, getState) => { }) .catch(error => { console.error(error); - dispatch(installFailure(`${error.message}`)); + dispatch(installFailure(`${error.translation_key}`)); }); }); }; @@ -104,6 +104,6 @@ export const checkInstall = next => dispatch => { }) .catch(error => { console.error(error); - dispatch(checkInstallFailure(`${error.message}`)); + dispatch(checkInstallFailure(`${error.translation_key}`)); }); }; diff --git a/client/coral-framework/actions/auth.js b/client/coral-framework/actions/auth.js index 8618cf1d3..92f489ae7 100644 --- a/client/coral-framework/actions/auth.js +++ b/client/coral-framework/actions/auth.js @@ -177,7 +177,7 @@ export const checkLogin = () => dispatch => { }) .catch(error => { console.error(error); - dispatch(checkLoginFailure(`${error.message}`)); + dispatch(checkLoginFailure(`${error.translation_key}`)); }); }; diff --git a/client/coral-framework/components/SuspendedAccount.js b/client/coral-framework/components/SuspendedAccount.js index 00adc9952..446169b37 100644 --- a/client/coral-framework/components/SuspendedAccount.js +++ b/client/coral-framework/components/SuspendedAccount.js @@ -26,7 +26,7 @@ class SuspendedAccount extends Component { editName(username) .then(() => location.reload()) .catch((error) => { - this.setState({alert: lang.t(`error.${error.message}`)}); + this.setState({alert: lang.t(`error.${error.translation_key}`)}); }); } else { this.setState({alert: lang.t('editName.error')}); From 6eb4725dd9bfe273dbfc4edb056951692bf6c2e1 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Fri, 17 Feb 2017 14:03:37 -0700 Subject: [PATCH 28/34] remove ternary from return statement --- .../graphql/mutations/index.js | 45 +++++++++++-------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/client/coral-framework/graphql/mutations/index.js b/client/coral-framework/graphql/mutations/index.js index cdc57e73e..ef5359eb1 100644 --- a/client/coral-framework/graphql/mutations/index.js +++ b/client/coral-framework/graphql/mutations/index.js @@ -43,28 +43,35 @@ export const postComment = graphql(POST_COMMENT, { return oldData; } + let updatedAsset; + // If posting a reply - return parent_id ? { - ...oldData, - asset: { - ...oldData.asset, - comments: oldData.asset.comments.map((oldComment) => { - return oldComment.id === parent_id - ? {...oldComment, replies: [...oldComment.replies, comment]} - : oldComment; - }) - } + if (parent_id) { + updatedAsset = { + ...oldData, + asset: { + ...oldData.asset, + comments: oldData.asset.comments.map((oldComment) => { + return oldComment.id === parent_id + ? {...oldComment, replies: [...oldComment.replies, comment]} + : oldComment; + }) + } + }; + } else { + + // If posting a top-level comment + updatedAsset = { + ...oldData, + asset: { + ...oldData.asset, + commentCount: oldData.asset.commentCount + 1, + comments: [comment, ...oldData.asset.comments] + } + }; } - // If posting a top-level comment - : { - ...oldData, - asset: { - ...oldData.asset, - commentCount: oldData.asset.commentCount + 1, - comments: [comment, ...oldData.asset.comments] - } - }; + return updatedAsset; } } }) From 2506442453877f041e401d68dc9d3a54ccbfe5ba Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Fri, 17 Feb 2017 14:10:21 -0700 Subject: [PATCH 29/34] toISOString --- client/coral-framework/graphql/mutations/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/coral-framework/graphql/mutations/index.js b/client/coral-framework/graphql/mutations/index.js index ef5359eb1..a9d08da85 100644 --- a/client/coral-framework/graphql/mutations/index.js +++ b/client/coral-framework/graphql/mutations/index.js @@ -25,7 +25,7 @@ export const postComment = graphql(POST_COMMENT, { id: ownProps.auth.user.id, name: ownProps.auth.user.username }, - created_at: new Date().toString(), + created_at: new Date().toISOString(), body, parent_id, asset_id, From 9f168e627adda55a4ac4e93157f2604794494643 Mon Sep 17 00:00:00 2001 From: gaba Date: Fri, 17 Feb 2017 14:19:30 -0800 Subject: [PATCH 30/34] It uses message and not translation key. --- client/coral-framework/actions/auth.js | 2 +- routes/api/users/index.js | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/client/coral-framework/actions/auth.js b/client/coral-framework/actions/auth.js index 92f489ae7..4a12245eb 100644 --- a/client/coral-framework/actions/auth.js +++ b/client/coral-framework/actions/auth.js @@ -123,7 +123,7 @@ export const fetchSignUp = (formData, redirectUri) => (dispatch) => { dispatch(signUpSuccess(user)); }) .catch(error => { - dispatch(signUpFailure(lang.t(`error.${error.translation_key}`))); + dispatch(signUpFailure(lang.t(`error.${error.message}`))); }); }; diff --git a/routes/api/users/index.js b/routes/api/users/index.js index 4182beb49..df7369bb3 100644 --- a/routes/api/users/index.js +++ b/routes/api/users/index.js @@ -136,7 +136,7 @@ router.post('/', (req, res, next) => { res.status(201).json(user); }); }) - .catch(err => { + .catch((err) => { next(err); }); }); @@ -163,7 +163,6 @@ router.post('/:user_id/actions', authorization.needed(), (req, res, next) => { res.status(201).json(action); }) .catch((err) => { - console.log('Error', err); next(err); }); }); From a00befafcb29776af7e3020e57ab9f81aee1ca54 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 17 Feb 2017 15:48:46 -0700 Subject: [PATCH 31/34] Initial graph tests --- bin/cli-setup | 1 + errors.js | 3 +- graph/mutators/comment.js | 17 +- graph/resolvers/root_mutation.js | 20 +- models/comment.js | 5 +- routes/api/actions/index.js | 20 -- routes/api/comments/index.js | 130 ------------- routes/api/index.js | 4 - routes/api/queue/index.js | 101 ---------- services/comments.js | 24 +-- test/graph/context.js | 64 ++++++ test/graph/mutations/createComment.js | 233 ++++++++++++++++++++++ test/routes/api/comments/index.js | 269 -------------------------- test/routes/api/queue/index.js | 123 ------------ 14 files changed, 337 insertions(+), 677 deletions(-) delete mode 100644 routes/api/actions/index.js delete mode 100644 routes/api/comments/index.js delete mode 100644 routes/api/queue/index.js create mode 100644 test/graph/context.js create mode 100644 test/graph/mutations/createComment.js delete mode 100644 test/routes/api/comments/index.js delete mode 100644 test/routes/api/queue/index.js diff --git a/bin/cli-setup b/bin/cli-setup index 7985bf220..b8e8538ed 100755 --- a/bin/cli-setup +++ b/bin/cli-setup @@ -184,6 +184,7 @@ const performSetup = () => { console.log('Settings created.'); console.log(`User ${user.id} created.`); console.log('\nTalk is now installed!'); + console.log('\nWe recommend adding TALK_INSTALL_LOCK=TRUE to your environment to turn off the dynamic setup.'); util.shutdown(); }) .catch((err) => { diff --git a/errors.js b/errors.js index ee68b67fd..ad8288dad 100644 --- a/errors.js +++ b/errors.js @@ -80,7 +80,8 @@ const ErrMissingToken = new APIError('token is required', { class ErrAssetCommentingClosed extends APIError { constructor(closedMessage = null) { super('asset commenting is closed', { - status: 400 + status: 400, + translation_key: 'COMMENTING_CLOSED' }, { // Include the closedMessage in the metadata piece of the error. diff --git a/graph/mutators/comment.js b/graph/mutators/comment.js index 5007a3039..33d2c4f85 100644 --- a/graph/mutators/comment.js +++ b/graph/mutators/comment.js @@ -15,11 +15,18 @@ const Wordlist = require('../../services/wordlist'); * @return {Promise} resolves to the created comment */ const createComment = ({user, loaders: {Comments}}, {body, asset_id, parent_id = null}, status = 'NONE') => { + + let tags = []; + if (user.hasRoles('ADMIN') || user.hasRoles('MODERATOR')) { + tags = [{name: 'STAFF'}]; + } + return CommentsService.publicCreate({ body, asset_id, parent_id, status, + tags, author_id: user.id }) .then((comment) => { @@ -36,12 +43,6 @@ const createComment = ({user, loaders: {Comments}}, {body, asset_id, parent_id = } } - if (user.hasRoles('ADMIN')) { - return CommentsService - .addTag(comment.id, 'STAFF', user.id) - .then(() => comment); - } - return comment; }); }; @@ -140,12 +141,12 @@ const createPublicComment = (context, commentInput) => { // Otherwise just return the new comment. // TODO: Check why the wordlist is undefined - if (wordlist != null) { + if (wordlist != null && wordlist.suspect != null) { // TODO: this is kind of fragile, we should refactor this to resolve // all these const's that we're using like 'COMMENTS', 'FLAG' to be // defined in a checkable schema. - return context.mutators.Action.createAction(null, { + return context.mutators.Action.create({ item_id: comment.id, item_type: 'COMMENTS', action_type: 'FLAG', diff --git a/graph/resolvers/root_mutation.js b/graph/resolvers/root_mutation.js index 4285f900d..71ac8b8a6 100644 --- a/graph/resolvers/root_mutation.js +++ b/graph/resolvers/root_mutation.js @@ -1,3 +1,6 @@ +const {Error: {ValidationError}} = require('mongoose'); +const errors = require('../../errors'); + /** * Wraps up a promise to return an object with the resolution of the promise * keyed at `key` or an error caught at `errors`. @@ -9,9 +12,20 @@ const wrapResponse = (key) => (promise) => { res[key] = value; } return res; - }).catch((err) => ({ - errors: [err] - })); + }).catch((err) => { + + if (err instanceof errors.APIError) { + return { + errors: [err] + }; + } else if (err instanceof ValidationError) { + + // TODO: wrap this with one of our internal errors. + throw err; + } + + throw err; + }); }; const RootMutation = { diff --git a/models/comment.js b/models/comment.js index c88041c8a..e6523e45f 100644 --- a/models/comment.js +++ b/models/comment.js @@ -43,7 +43,10 @@ const TagSchema = new Schema({ default: null }, - created_at: Date + created_at: { + type: Date, + default: Date + } }, { _id: false }); diff --git a/routes/api/actions/index.js b/routes/api/actions/index.js deleted file mode 100644 index 20316d93b..000000000 --- a/routes/api/actions/index.js +++ /dev/null @@ -1,20 +0,0 @@ -const express = require('express'); -const ActionsService = require('../../../services/actions'); - -const router = express.Router(); - -router.delete('/:action_id', (req, res, next) => { - ActionsService - .findOneAndRemove({ - id: req.params.action_id, - user_id: req.user.id - }) - .then(() => { - res.status(204).end(); - }) - .catch(error => { - next(error); - }); -}); - -module.exports = router; diff --git a/routes/api/comments/index.js b/routes/api/comments/index.js deleted file mode 100644 index e77ca0c30..000000000 --- a/routes/api/comments/index.js +++ /dev/null @@ -1,130 +0,0 @@ -const express = require('express'); -const errors = require('../../../errors'); - -const CommentModel = require('../../../models/comment'); -const CommentsService = require('../../../services/comments'); -const AssetsService = require('../../../services/assets'); -const UsersService = require('../../../services/users'); -const ActionsService = require('../../../services/actions'); - -const authorization = require('../../../middleware/authorization'); -const _ = require('lodash'); - -const router = express.Router(); - -router.get('/', (req, res, next) => { - - const { - status = null, - action_type = null, - asset_id = null, - user_id = null - } = req.query; - - // everything on this route requires admin privileges besides listing comments for owner of said comments - if (!authorization.has(req.user, 'ADMIN') && !user_id) { - next(errors.ErrNotAuthorized); - return; - } - - // if the user is not an admin, only return comment list for the owner of the comments - if (req.user.id !== user_id && !authorization.has(req.user, 'ADMIN')) { - next(errors.ErrNotAuthorized); - return; - } - - /** - * This adds the asset_id requirement to the query if the asset_id is defined. - */ - const assetIDWrap = (query) => { - if (asset_id) { - query = query.where('asset_id', asset_id); - } - - return query; - }; - - let query; - - // the check for user_id MUST be first here. - // otherwise this will be a vulnerability if you pass user_id and something else, - // the app will return admin-level data without the proper checks - if (user_id) { - query = CommentsService.findByUserId(user_id, authorization.has(req.user, 'ADMIN')); - } else if (status) { - query = assetIDWrap(CommentsService.findByStatus(status === 'NEW' ? 'NONE' : status)); - } else if (action_type) { - query = CommentsService - .findIdsByActionType(action_type) - .then((ids) => assetIDWrap(CommentModel.find({ - id: { - $in: ids - } - }))); - } else { - query = assetIDWrap(CommentsService.all()); - } - - query.then((comments) => { - return Promise.all([ - comments, - AssetsService.findMultipleById(comments.map(comment => comment.asset_id)), - UsersService.findByIdArray(_.uniq(comments.map((comment) => comment.author_id))), - ActionsService.getActionSummariesFromComments(asset_id, comments, req.user ? req.user.id : false) - ]); - }) - .then(([comments, assets, users, actions]) => - res.status(200).json({ - comments, - assets, - users, - actions - })) - .catch((err) => { - next(err); - }); -}); - -router.get('/:comment_id', authorization.needed('ADMIN'), (req, res, next) => { - CommentsService - .findById(req.params.comment_id) - .then(comment => { - if (!comment) { - res.status(404).end(); - return; - } - - res.status(200).json(comment); - }) - .catch((err) => { - next(err); - }); -}); - -router.delete('/:comment_id', authorization.needed('ADMIN'), (req, res, next) => { - CommentsService - .removeById(req.params.comment_id) - .then(() => { - res.status(204).end(); - }) - .catch((err) => { - next(err); - }); -}); - -router.put('/:comment_id/status', authorization.needed('ADMIN'), (req, res, next) => { - const { - status - } = req.body; - - CommentsService - .pushStatus(req.params.comment_id, status, req.user.id) - .then(() => { - res.status(204).end(); - }) - .catch((err) => { - next(err); - }); -}); - -module.exports = router; diff --git a/routes/api/index.js b/routes/api/index.js index 9913d8ad2..56846acde 100644 --- a/routes/api/index.js +++ b/routes/api/index.js @@ -5,10 +5,6 @@ const router = express.Router(); router.use('/assets', authorization.needed('ADMIN'), require('./assets')); router.use('/settings', authorization.needed('ADMIN'), require('./settings')); -router.use('/queue', authorization.needed('ADMIN'), require('./queue')); - -router.use('/comments', authorization.needed(), require('./comments')); -router.use('/actions', authorization.needed(), require('./actions')); router.use('/auth', require('./auth')); router.use('/users', require('./users')); diff --git a/routes/api/queue/index.js b/routes/api/queue/index.js deleted file mode 100644 index b3cbb1a52..000000000 --- a/routes/api/queue/index.js +++ /dev/null @@ -1,101 +0,0 @@ -const express = require('express'); -const CommentsService = require('../../../services/comments'); -const CommentModel = require('../../../models/comment'); -const UsersService = require('../../../services/users'); -const ActionsService = require('../../../services/actions'); -const authorization = require('../../../middleware/authorization'); -const _ = require('lodash'); - -const router = express.Router(); - -function gatherActionsAndUsers (comments) { - return Promise.all([ - comments, - UsersService.findByIdArray(_.uniq(comments.map((comment) => comment.author_id))), - ActionsService.getActionSummaries(_.uniq([ - ...comments.map((comment) => comment.id), - ...comments.map((comment) => comment.author_id) - ])) - ]); -} - -//============================================================================== -// Get Routes -//============================================================================== - -// Returns back all the comments that are in the moderation queue. The moderation queue is pre or post moderated, -// depending on the settings. The :moderation overwrites this settings. -// Pre-moderation: New comments are shown in the moderator queues immediately. -// Post-moderation: New comments do not appear in moderation queues unless they are flagged by other users. -router.get('/comments/premod', authorization.needed('ADMIN'), (req, res, next) => { - - const {asset_id} = req.query; - - CommentsService.moderationQueue('PREMOD', asset_id) - .then(gatherActionsAndUsers) - .then(([comments, users, actions]) => { - res.json({comments, users, actions}); - }) - .catch(error => { - next(error); - }); -}); - -router.get('/comments/rejected', authorization.needed('ADMIN'), (req, res, next) => { - const {asset_id} = req.query; - - CommentsService.moderationQueue('REJECTED', asset_id) - .then(gatherActionsAndUsers) - .then(([comments, users, actions]) => { - res.json({comments, users, actions}); - }) - .catch(error => { - next(error); - }); -}); - -router.get('/comments/flagged', authorization.needed('ADMIN'), (req, res, next) => { - const {asset_id} = req.query; - - const assetIDWrap = (query) => { - if (asset_id) { - query = query.where('asset_id', asset_id); - } - - return query; - }; - - CommentsService.findIdsByActionType('FLAG') - .then(ids => assetIDWrap(CommentModel.find({ - id: {$in: ids} - }))) - .then(gatherActionsAndUsers) - .then(([comments, users, actions]) => { - res.json({comments, users, actions}); - }) - .catch(error => { - next(error); - }); -}); - -// Returns back all the users that are in the moderation queue. -router.get('/users/flagged', (req, res, next) => { - UsersService.moderationQueue() - .then((users) => { - return Promise.all([ - users, - ActionsService.getActionSummaries(users.map((user) => user.id)) - ]); - }) - .then(([users, actions]) => { - res.json({ - users, - actions - }); - }) - .catch(error => { - next(error); - }); -}); - -module.exports = router; diff --git a/services/comments.js b/services/comments.js index ad9ce18db..4dda82b04 100644 --- a/services/comments.js +++ b/services/comments.js @@ -29,27 +29,17 @@ module.exports = class CommentsService { } const { - body, - asset_id, - parent_id, status = 'NONE', - author_id } = comment; - comment = new CommentModel({ - body, - asset_id, - parent_id, - status_history: status ? [{ - type: status, - created_at: new Date() - }] : [], - tags: [], - status, - author_id - }); + comment.status_history = status ? [{ + type: status, + created_at: new Date() + }] : []; - return comment.save(); + let commentModel = new CommentModel(comment); + + return commentModel.save(); } /** diff --git a/test/graph/context.js b/test/graph/context.js new file mode 100644 index 000000000..413622678 --- /dev/null +++ b/test/graph/context.js @@ -0,0 +1,64 @@ +const expect = require('chai').expect; + +const User = require('../../models/user'); +const Context = require('../../graph/context'); +const errors = require('../../errors'); + +describe('graph.Context', () => { + + describe('#constructor: with a user', () => { + let c; + + beforeEach(() => { + c = new Context({user: new User({id: '1'})}); + }); + + it('creates a context with a user', (done) => { + expect(c).to.have.property('user'); + expect(c.user).to.have.property('id', '1'); + + done(); + }); + + it('does have access to mutators', () => { + return c.mutators.Action.create({ + item_id: '1', + item_type: 'COMMENTS', + action_type: 'LIKE' + }) + .then((action) => { + expect(action).to.have.property('item_id', '1'); + expect(action).to.have.property('item_type', 'COMMENTS'); + expect(action).to.have.property('action_type', 'LIKE'); + }); + }); + }); + + describe('#constructor: without a user', () => { + let c; + + beforeEach(() => { + c = new Context({user: undefined}); + }); + + it('creates a context without a user', (done) => { + expect(c).to.not.have.property('user'); + + done(); + }); + + it('does not have access to mutators', () => { + return c.mutators.Action.create({ + item_id: '1', + item_type: 'COMMENTS', + action_type: 'LIKE' + }) + .then((action) => { + expect(action).to.be.null; + }) + .catch((err) => { + expect(err).to.be.equal(errors.ErrNotAuthorized); + }); + }); + }); +}); diff --git a/test/graph/mutations/createComment.js b/test/graph/mutations/createComment.js new file mode 100644 index 000000000..b80cdbab2 --- /dev/null +++ b/test/graph/mutations/createComment.js @@ -0,0 +1,233 @@ +const expect = require('chai').expect; +const {graphql} = require('graphql'); + +const schema = require('../../../graph/schema'); +const Context = require('../../../graph/context'); +const UserModel = require('../../../models/user'); +const AssetModel = require('../../../models/asset'); +const SettingsService = require('../../../services/settings'); +const ActionModel = require('../../../models/action'); + +describe('graph.mutation.createComment', () => { + beforeEach(() => SettingsService.init()); + + const query = ` + mutation CreateComment($body: String = "Here's my comment!") { + createComment(asset_id: "123", body: $body) { + comment { + id + status + tags { + name + } + } + errors { + translation_key + } + } + } + `; + + describe('context with different user properties', () => { + + beforeEach(() => AssetModel.create({id: '123'})); + + [ + {user: null, error: 'NOT_AUTHORIZED'}, + {user: new UserModel({}), error: null} + ].forEach(({user, error}) => { + describe(user != null ? 'with user' : 'without user', () => { + + it(error ? 'does not create the comment' : 'creates the comment', () => { + const context = new Context({user}); + + return graphql(schema, query, {}, context) + .then(({data, errors}) => { + expect(errors).to.be.undefined; + if (error) { + expect(data.createComment).to.have.property('comment').null; + expect(data.createComment).to.have.property('errors').not.null; + expect(data.createComment.errors[0]).to.have.property('translation_key', error); + } else { + expect(data.createComment).to.have.property('comment').not.null; + expect(data.createComment).to.have.property('errors').null; + } + }); + }); + + }); + }); + + }); + + describe('users with different statuses', () => { + + beforeEach(() => AssetModel.create({id: '123'})); + + [ + {user: new UserModel({status: 'ACTIVE'}), error: null}, + {user: new UserModel({status: 'BANNED'}), error: 'NOT_AUTHORIZED'}, + {user: new UserModel({status: 'PENDING'}), error: null}, + {user: new UserModel({status: 'APPROVED'}), error: null} + ].forEach(({user, error}) => { + describe(`user.status=${user.status}`, () => { + it(error ? 'does not create the comment' : 'creates the comment', () => { + const context = new Context({user}); + + return graphql(schema, query, {}, context) + .then(({data, errors}) => { + expect(errors).to.be.undefined; + if (error) { + expect(data.createComment).to.have.property('comment').null; + expect(data.createComment).to.have.property('errors').not.null; + expect(data.createComment.errors[0]).to.have.property('translation_key', error); + } else { + expect(data.createComment).to.have.property('comment').not.null; + expect(data.createComment).to.have.property('errors').null; + } + }); + }); + }); + }); + + }); + + describe('assets with different statuses', () => { + + [ + {asset: new AssetModel({id: '123', closedAt: new Date((new Date()).getTime() + (10 * 86400000))}), error: null}, + {asset: new AssetModel({id: '123', closedAt: new Date((new Date()).getTime() - (10 * 86400000))}), error: 'COMMENTING_CLOSED'} + ].forEach(({asset, error}) => { + describe(`asset.isClosed=${asset.isClosed}`, () => { + + beforeEach(() => asset.save()); + + it(error ? 'does not create the comment' : 'creates the comment', () => { + const context = new Context({user: new UserModel({status: 'ACTIVE'})}); + + return graphql(schema, query, {}, context) + .then(({data, errors}) => { + expect(errors).to.be.undefined; + if (error) { + expect(data.createComment).to.have.property('comment').null; + expect(data.createComment).to.have.property('errors').not.null; + expect(data.createComment.errors[0]).to.have.property('translation_key', error); + } else { + expect(data.createComment).to.have.property('comment').not.null; + expect(data.createComment).to.have.property('errors').null; + } + }); + }); + + }); + + }); + + }); + + describe('comments made with different asset moderation settings', () => { + + [ + {moderation: 'PRE', status: 'PREMOD'}, + {moderation: 'POST', status: 'NONE'} + ].forEach(({moderation, status}) => { + describe(`moderation=${moderation}`, () => { + + beforeEach(() => AssetModel.create({id: '123', settings: {moderation}})); + + it(`creates comment with status=${status}`, () => { + const context = new Context({user: new UserModel({status: 'ACTIVE'})}); + + return graphql(schema, query, {}, context) + .then(({data, errors}) => { + expect(errors).to.be.undefined; + expect(data.createComment).to.have.property('comment').not.null; + expect(data.createComment).to.have.property('errors').null; + expect(data.createComment.comment).to.have.property('status', status); + }); + }); + + }); + }); + + }); + + describe('comments with/without banned words', () => { + + beforeEach(() => Promise.all([ + AssetModel.create({id: '123'}), + SettingsService.update({wordlist: {banned: ['WORST'], suspect: ['EH']}}) + ])); + + [ + {message: 'comment does not contain banned/suspect words', body: 'This is such a nice comment!', status: 'NONE', flagged: false}, + {message: 'comment contains banned words', body: 'This is the WORST comment!', status: 'REJECTED', flagged: false}, + {message: 'comment contains suspect words', body: 'This is the EH comment!', status: 'NONE', flagged: true} + ].forEach(({message, body, status, flagged}) => { + describe(message, () => { + + it(`should create a comment with status=${status} and it ${flagged ? 'should' : 'should not'} be flagged`, () => { + const context = new Context({user: new UserModel({status: 'ACTIVE'})}); + + return graphql(schema, query, {}, context, { + body + }) + .then(({data, errors}) => { + expect(errors).to.be.undefined; + expect(data.createComment).to.have.property('comment').not.null; + expect(data.createComment.comment).to.have.property('status', status); + expect(data.createComment).to.have.property('errors').null; + + return ActionModel.find({ + item_id: data.createComment.comment.id, + action_type: 'FLAG' + }); + }) + .then((actions) => { + if (flagged) { + expect(actions).to.have.length(1); + } else { + expect(actions).to.have.length(0); + } + }); + }); + + }); + }); + }); + + describe('users with different roles', () => { + + beforeEach(() => AssetModel.create({id: '123'})); + + [ + {roles: [], tag: null}, + {roles: ['ADMIN'], tag: 'STAFF'}, + {roles: ['MODERATOR'], tag: 'STAFF'}, + {roles: ['ADMIN', 'MODERATOR'], tag: 'STAFF'} + ].forEach(({roles, tag}) => { + describe(`user.roles=${JSON.stringify(roles)}`, () => { + + it(`creates comment ${tag ? `with tag=${tag}` : 'without tags'}`, () => { + const context = new Context({user: new UserModel({roles})}); + + return graphql(schema, query, {}, context) + .then(({data, errors}) => { + expect(errors).to.be.undefined; + expect(data.createComment).to.have.property('comment').not.null; + expect(data.createComment).to.have.property('errors').null; + + if (tag) { + expect(data.createComment.comment).to.have.property('tags').length(1); + expect(data.createComment.comment.tags[0]).to.have.property('name', tag); + } else { + expect(data.createComment.comment).to.have.property('tags').length(0); + } + }); + }); + + }); + }); + + }); +}); diff --git a/test/routes/api/comments/index.js b/test/routes/api/comments/index.js deleted file mode 100644 index 50431159b..000000000 --- a/test/routes/api/comments/index.js +++ /dev/null @@ -1,269 +0,0 @@ -const passport = require('../../../passport'); - -const app = require('../../../../app'); -const chai = require('chai'); -const expect = chai.expect; - -// Setup chai. -chai.should(); -chai.use(require('chai-http')); - -const CommentModel = require('../../../../models/comment'); -const ActionModel = require('../../../../models/action'); - -const CommentsService = require('../../../../services/comments'); -const UsersService = require('../../../../services/users'); -const SettingsService = require('../../../../services/settings'); - -const settings = {id: '1', moderation: 'PRE', wordlist: {banned: ['bad words'], suspect: ['suspect words']}}; - -describe('/api/v1/comments', () => { - - // Ensure that the settings are always available. - beforeEach(() => SettingsService.init(settings)); - - describe('#get', () => { - const comments = [{ - body: 'comment 10', - asset_id: 'asset', - author_id: '123' - }, { - body: 'comment 20', - asset_id: 'asset', - author_id: '456' - }, { - body: 'comment 20', - asset_id: 'asset', - author_id: '456', - status: 'REJECTED', - status_history: [{ - type: 'REJECTED' - }] - }, { - body: 'comment 30', - asset_id: '456', - status: 'ACCEPTED', - status_history: [{ - type: 'ACCEPTED' - }] - }]; - - const users = [{ - username: 'Ana', - email: 'ana@gmail.com', - password: '123456789' - }, { - username: 'Maria', - email: 'maria@gmail.com', - password: '123456789' - }]; - - const actions = [{ - action_type: 'FLAG', - item_id: 'abc', - item_type: 'COMMENTS' - }, { - action_type: 'LIKE', - item_id: 'hij', - item_type: 'COMMENTS' - }]; - - beforeEach(() => { - return Promise.all([ - CommentModel.create(comments).then((newComments) => { - newComments.forEach((comment, i) => { - comments[i].id = comment.id; - }); - - actions[0].item_id = comments[0].id; - actions[1].item_id = comments[1].id; - - return ActionModel.create(actions); - }), - UsersService.createLocalUsers(users) - ]); - }); - - it('should return only the owner’s published comments if the user is not an admin', () => { - return chai.request(app) - .get('/api/v1/comments?user_id=456') - .set(passport.inject({id: '456', roles: []})) - .then(res => { - expect(res).to.have.status(200); - expect(res.body.comments).to.have.length(1); - expect(res.body.comments[0]).to.have.property('author_id', '456'); - }); - }); - - it('should fail if a non-admin requests comments not owned by them', () => { - return chai.request(app) - .get('/api/v1/comments?user_id=456') - .set(passport.inject({id: '123', roles: []})) - .then((res) => { - expect(res).to.be.empty; - }) - .catch((err) => { - expect(err).to.have.status(401); - }); - }); - - it('should return all the comments', () => { - return chai.request(app) - .get('/api/v1/comments') - .set(passport.inject({roles: ['ADMIN']})) - .then((res) => { - - expect(res).to.have.status(200); - - }); - }); - - it('should return all the rejected comments', () => { - return chai.request(app) - .get('/api/v1/comments?status=REJECTED') - .set(passport.inject({roles: ['ADMIN']})) - .then((res) => { - expect(res).to.have.status(200); - expect(res.body).to.have.property('comments'); - expect(res.body.comments).to.have.length(1); - expect(res.body.comments[0]).to.have.property('id', comments[2].id); - }); - }); - - it('should return all the approved comments', () => { - return chai.request(app) - .get('/api/v1/comments?status=ACCEPTED') - .set(passport.inject({roles: ['ADMIN']})) - .then((res) => { - expect(res).to.have.status(200); - expect(res.body.comments).to.have.length(1); - expect(res.body.comments[0]).to.have.property('id', comments[3].id); - }); - }); - - it('should return all the new comments', () => { - return chai.request(app) - .get('/api/v1/comments?status=NEW') - .set(passport.inject({roles: ['ADMIN']})) - .then((res) => { - expect(res).to.have.status(200); - expect(res.body.comments).to.have.length(2); - }); - }); - - it('should return all the flagged comments', () => { - return chai.request(app) - .get('/api/v1/comments?action_type=FLAG') - .set(passport.inject({roles: ['ADMIN']})) - .then((res) => { - expect(res).to.have.status(200); - - expect(res.body.comments).to.have.length(1); - expect(res.body.comments[0]).to.have.property('id', comments[0].id); - }); - }); - }); -}); - -describe('/api/v1/comments/:comment_id', () => { - const comments = [{ - id: 'abc', - body: 'comment 10', - asset_id: 'asset', - author_id: '123' - }, { - id: 'def', - body: 'comment 20', - asset_id: 'asset', - author_id: '456' - }, { - id: 'hij', - body: 'comment 30', - asset_id: '456' - }]; - - const users = [{ - username: 'Ana', - email: 'ana@gmail.com', - password: '123456789' - }, { - username: 'Maria', - email: 'maria@gmail.com', - password: '123456789' - }]; - - const actions = [{ - action_type: 'FLAG', - item_id: 'abc', - item_type: 'COMMENTS' - }, { - action_type: 'LIKE', - item_id: 'hij', - item_type: 'COMMENTS' - }]; - - beforeEach(() => { - return SettingsService.init(settings).then(() => { - return Promise.all([ - CommentModel.create(comments), - UsersService.createLocalUsers(users), - ActionModel.create(actions) - ]); - }); - }); - - describe('#get', () => { - - it('should return the right comment for the comment_id', () => { - return chai.request(app) - .get('/api/v1/comments/abc') - .set(passport.inject({roles: ['ADMIN']})) - .then((res) => { - expect(res).to.have.status(200); - expect(res).to.have.property('body'); - expect(res.body).to.have.property('body', 'comment 10'); - }); - }); - }); - - describe('#delete', () => { - it('it should remove comment', () => { - return chai.request(app) - .delete('/api/v1/comments/abc') - .set(passport.inject({roles: ['ADMIN']})) - .then((res) => { - expect(res).to.have.status(204); - return CommentsService.findById('abc'); - }) - .then((comment) => { - expect(comment).to.be.null; - }); - }); - }); - - describe('#put', () => { - it('it should update status', function() { - return chai.request(app) - .put('/api/v1/comments/abc/status') - .set(passport.inject({roles: ['ADMIN']})) - .send({status: 'accepted'}) - .then((res) => { - expect(res).to.have.status(204); - expect(res.body).to.be.empty; - }); - }); - - it('it should not allow a non-ADMIN to update status', () => { - return chai.request(app) - .put('/api/v1/comments/abc/status') - .set(passport.inject({roles: []})) - .send({status: 'accepted'}) - .then((res) => { - expect(res).to.be.empty; - }) - .catch((err) => { - expect(err).to.have.property('status', 401); - }); - }); - }); -}); diff --git a/test/routes/api/queue/index.js b/test/routes/api/queue/index.js deleted file mode 100644 index bca68f355..000000000 --- a/test/routes/api/queue/index.js +++ /dev/null @@ -1,123 +0,0 @@ -const passport = require('../../../passport'); - -const app = require('../../../../app'); -const chai = require('chai'); -const expect = chai.expect; - -// Setup chai. -chai.should(); -chai.use(require('chai-http')); - -const Comment = require('../../../../models/comment'); -const Action = require('../../../../models/action'); -const UsersService = require('../../../../services/users'); - -const SettingsService = require('../../../../services/settings'); -const settings = {id: '1', moderation: 'PRE', wordlist: {banned: ['banned'], suspect: ['suspect']}}; - -describe('/api/v1/queue', () => { - const comments = [{ - id: 'abc', - body: 'comment 10', - asset_id: 'asset', - author_id: '123', - status: 'REJECTED', - status_history: [{ - type: 'REJECTED' - }] - }, { - id: 'def', - body: 'comment 20', - asset_id: 'asset', - author_id: '456', - status: 'PREMOD', - status_history: [{ - type: 'PREMOD' - }] - }, { - id: 'hij', - body: 'comment 30', - asset_id: '456', - status: 'ACCEPTED', - status_history: [{ - type: 'ACCEPTED' - }] - }]; - - const users = [{ - username: 'Ana', - email: 'ana@gmail.com', - password: '123456789' - }, { - username: 'Maria', - email: 'maria@gmail.com', - password: '123456789' - }]; - - const actions = [{ - action_type: 'FLAG', - item_id: 'abc', - item_type: 'COMMENTS' - }, { - action_type: 'LIKE', - item_id: 'hij', - item_type: 'COMMENTS' - }, { - action_type: 'FLAG', - item_id: '123', - item_type: 'USERS' - }]; - - beforeEach(() => { - return SettingsService.init(settings).then(() => { - return UsersService.createLocalUsers(users) - .then((u) => { - comments[0].author_id = u[0].id; - comments[1].author_id = u[1].id; - comments[2].author_id = u[1].id; - - return Promise.all([ - Comment.create(comments), - u, - ...u.map((user) => UsersService.setStatus(user.id, 'PENDING')) - ]); - }) - .then(([c, u]) => { - actions[0].item_id = c[0].id; - actions[1].item_id = c[1].id; - actions[2].item_id = u[0].id; - - return Promise.all([ - Action.create(actions), - SettingsService.init(settings) - ]); - }); - }); - }); - - it('should return all the pending comments, users and actions', () => { - return chai.request(app) - .get('/api/v1/queue/comments/premod') - .set(passport.inject({roles: ['ADMIN']})) - .then((res) => { - 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('username'); - expect(res.body.actions[0]).to.have.property('action_type'); - }); - }); - - it('should return all pending users and actions', function(done){ - chai.request(app) - .get('/api/v1/queue/users/flagged') - .set(passport.inject({roles: ['ADMIN']})) - .end(function(err, res){ - expect(err).to.be.null; - expect(res).to.have.status(200); - expect(res.body.users[0]).to.have.property('username'); - expect(res.body.actions[0]).to.have.property('action_type'); - done(); - }); - }); -}); From 4cccf09a872d4b7bad5f2495669a3553a98ab1f6 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 17 Feb 2017 15:53:23 -0700 Subject: [PATCH 32/34] spelling... --- test/graph/mutations/createComment.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/graph/mutations/createComment.js b/test/graph/mutations/createComment.js index b80cdbab2..26775933c 100644 --- a/test/graph/mutations/createComment.js +++ b/test/graph/mutations/createComment.js @@ -8,7 +8,7 @@ const AssetModel = require('../../../models/asset'); const SettingsService = require('../../../services/settings'); const ActionModel = require('../../../models/action'); -describe('graph.mutation.createComment', () => { +describe('graph.mutations.createComment', () => { beforeEach(() => SettingsService.init()); const query = ` From afcdacff0dcbdb6be9d9594da70d4d6e43afce4a Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 17 Feb 2017 17:49:59 -0700 Subject: [PATCH 33/34] Added yarn.lock to Dockerfile --- Dockerfile | 4 ++-- package.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index a01aae254..11587cafb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,8 +14,8 @@ ENV TALK_PORT 5000 EXPOSE 5000 # Install app dependencies -COPY package.json /usr/src/app/ -RUN yarn install --production +COPY package.json yarn.lock /usr/src/app/ +RUN yarn install # Bundle app source COPY . /usr/src/app diff --git a/package.json b/package.json index 09ce50aa9..70b0e17b1 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,6 @@ }, "homepage": "https://github.com/coralproject/talk#readme", "dependencies": { - "apollo-client": "^0.8.3", "bcrypt": "^0.8.7", "body-parser": "^1.15.2", "cli-table": "^0.3.1", @@ -66,7 +65,6 @@ "graphql": "^0.8.2", "graphql-errors": "^2.1.0", "graphql-server-express": "^0.5.0", - "graphql-tag": "^1.2.3", "graphql-tools": "^0.9.0", "helmet": "^3.1.0", "inquirer": "^3.0.1", @@ -88,6 +86,7 @@ "uuid": "^2.0.3" }, "devDependencies": { + "apollo-client": "^0.8.3", "autoprefixer": "^6.5.2", "babel-core": "^6.21.0", "babel-eslint": "^7.1.0", @@ -122,6 +121,7 @@ "exports-loader": "^0.6.3", "fetch-mock": "^5.5.0", "graphql-docs": "^0.2.0", + "graphql-tag": "^1.2.3", "hammerjs": "^2.0.8", "ignore-styles": "^5.0.1", "immutable": "^3.8.1", From 301c8e829f7e985d66c6fbc7b0c1f571d6173b33 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 17 Feb 2017 17:56:44 -0700 Subject: [PATCH 34/34] Added production flag back --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 11587cafb..14d606c0a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,7 +15,7 @@ EXPOSE 5000 # Install app dependencies COPY package.json yarn.lock /usr/src/app/ -RUN yarn install +RUN yarn install --production # Bundle app source COPY . /usr/src/app