From 44648fa901340434c61014ecb3b1d3d7320d6a59 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 10 Jan 2018 13:04:56 -0700 Subject: [PATCH 01/21] fixed perms issue --- perms/reducers/mutation.js | 6 +++--- test/server/graph/mutations/updateSettings.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/perms/reducers/mutation.js b/perms/reducers/mutation.js index a49a901fa..8614bad96 100644 --- a/perms/reducers/mutation.js +++ b/perms/reducers/mutation.js @@ -22,17 +22,17 @@ module.exports = (user, perm) => { case types.REMOVE_COMMENT_TAG: return check(user, ['ADMIN', 'MODERATOR', 'STAFF']); - case types.UPDATE_USER_ROLES: case types.SET_COMMENT_STATUS: case types.SET_USER_USERNAME_STATUS: case types.SET_USER_BAN_STATUS: case types.SET_USER_SUSPENSION_STATUS: - case types.UPDATE_CONFIG: - case types.UPDATE_SETTINGS: case types.UPDATE_ASSET_SETTINGS: case types.UPDATE_ASSET_STATUS: return check(user, ['ADMIN', 'MODERATOR']); + case types.UPDATE_CONFIG: + case types.UPDATE_SETTINGS: + case types.UPDATE_USER_ROLES: case types.CREATE_TOKEN: case types.REVOKE_TOKEN: return check(user, ['ADMIN']); diff --git a/test/server/graph/mutations/updateSettings.js b/test/server/graph/mutations/updateSettings.js index c660ea169..3f7eac701 100644 --- a/test/server/graph/mutations/updateSettings.js +++ b/test/server/graph/mutations/updateSettings.js @@ -27,8 +27,8 @@ describe('graph.mutations.updateSettings', () => { [ {error: 'NOT_AUTHORIZED', role: 'COMMENTER'}, + {error: 'NOT_AUTHORIZED', role: 'MODERATOR'}, {role: 'ADMIN'}, - {role: 'MODERATOR'}, ].forEach(({role, error}) => { it(`role = ${role}`, async () => { const user = new UserModel({role}); From 6e813e96638ef4f5414bbd93f95222cfc14a9398 Mon Sep 17 00:00:00 2001 From: liz Date: Thu, 11 Jan 2018 17:16:26 +0800 Subject: [PATCH 02/21] Translations refinement for plugins --- plugins/talk-plugin-auth/client/translations.yml | 6 +++--- plugins/talk-plugin-like/client/translations.yml | 4 ++-- plugins/talk-plugin-love/client/translations.yml | 8 ++++---- plugins/talk-plugin-respect/client/translations.yml | 8 ++++---- .../client/translations.yml | 2 +- .../client/translations.yml | 4 ++-- .../client/translations.yml | 2 +- .../client/translations.yml | 4 ++-- .../talk-plugin-sort-newest/client/translations.yml | 2 +- .../talk-plugin-sort-oldest/client/translations.yml | 2 +- .../talk-plugin-subscriber/client/translations.yml | 2 +- .../client/translations.yml | 12 ++++++------ 12 files changed, 28 insertions(+), 28 deletions(-) diff --git a/plugins/talk-plugin-auth/client/translations.yml b/plugins/talk-plugin-auth/client/translations.yml index 34227c435..cdc34688d 100644 --- a/plugins/talk-plugin-auth/client/translations.yml +++ b/plugins/talk-plugin-auth/client/translations.yml @@ -140,7 +140,7 @@ zh_CN: verify_email: "感谢您创建帐号。我们已向您提供的地址发送了封邮件,您可以通过邮件验证帐号。" verify_email2: "您参与社群前须验证帐号。" not_you: "不是你?" - logged_in_as: "登陆身份" + logged_in_as: "登录身份" facebook_sign_in: "使用 Facebook 帐号" facebook_sign_up: "使用 Facebook 帐号" logout: "登出" @@ -184,7 +184,7 @@ zh_TW: verify_email: "感謝您創建帳號。我們已向您提供的地址發送了封郵件,您可以通過郵件驗證帳號。" verify_email2: "您參與社群前須驗證帳號。" not_you: "不是你?" - logged_in_as: "登陸身份" + logged_in_as: "登錄身份" facebook_sign_in: "使用 Facebook 帳號" facebook_sign_up: "使用 Facebook 帳號" logout: "登出" @@ -220,4 +220,4 @@ zh_TW: special_characters: "用戶名只能包含字母、數字跟下劃線" username: "用戶名" write_your_username: "編輯您的用戶名" - your_username: "您的用戶名將隨您發表的所有評論一同出現。" \ No newline at end of file + your_username: "您的用戶名將隨您發表的所有評論一同出現。" diff --git a/plugins/talk-plugin-like/client/translations.yml b/plugins/talk-plugin-like/client/translations.yml index fb18c2d47..9daeb22cb 100644 --- a/plugins/talk-plugin-like/client/translations.yml +++ b/plugins/talk-plugin-like/client/translations.yml @@ -12,5 +12,5 @@ zh_CN: liked: "已喜欢" zh_TW: talk-plugin-like: - like: 讚 - liked: 已讚 \ No newline at end of file + like: 喜歡 + liked: 已喜歡 diff --git a/plugins/talk-plugin-love/client/translations.yml b/plugins/talk-plugin-love/client/translations.yml index 0b4141412..e234bef79 100644 --- a/plugins/talk-plugin-love/client/translations.yml +++ b/plugins/talk-plugin-love/client/translations.yml @@ -8,9 +8,9 @@ es: loved: Amé zh_CN: talk-plugin-love: - love: "爱" - loved: "已爱" + love: " 比心" + loved: "已比心" zh_TW: talk-plugin-love: - love: 喜歡 - loved: 已喜歡 \ No newline at end of file + love: 比心 + loved: 已比心 diff --git a/plugins/talk-plugin-respect/client/translations.yml b/plugins/talk-plugin-respect/client/translations.yml index 959d469ec..2ee208da0 100644 --- a/plugins/talk-plugin-respect/client/translations.yml +++ b/plugins/talk-plugin-respect/client/translations.yml @@ -8,9 +8,9 @@ es: respected: Respetado zh_CN: talk-plugin-respect: - respect: "尊重" - respected: "已尊重" + respect: "赞" + respected: "已赞" zh_TW: talk-plugin-respect: - respect: 尊重 - respected: 已尊重 \ No newline at end of file + respect: 讚 + respected: 已讚 diff --git a/plugins/talk-plugin-sort-most-liked/client/translations.yml b/plugins/talk-plugin-sort-most-liked/client/translations.yml index 46c35b9db..67fcec6ea 100644 --- a/plugins/talk-plugin-sort-most-liked/client/translations.yml +++ b/plugins/talk-plugin-sort-most-liked/client/translations.yml @@ -8,4 +8,4 @@ zh_CN: label: "最被喜欢在前" zh_TW: talk-plugin-sort-most-liked: - label: 被讚最多優先 \ No newline at end of file + label: 最被喜歡優先 diff --git a/plugins/talk-plugin-sort-most-loved/client/translations.yml b/plugins/talk-plugin-sort-most-loved/client/translations.yml index f7c053211..bc9283544 100644 --- a/plugins/talk-plugin-sort-most-loved/client/translations.yml +++ b/plugins/talk-plugin-sort-most-loved/client/translations.yml @@ -5,7 +5,7 @@ es: talk-plugin-sort-most-loved: zh_CN: talk-plugin-sort-most-loved: - label: "最多被爱在前" + label: "最多被比心在前" zh_TW: talk-plugin-sort-most-loved: - label: 被喜歡最多優先 \ No newline at end of file + label: 最多被比心在前 diff --git a/plugins/talk-plugin-sort-most-replied/client/translations.yml b/plugins/talk-plugin-sort-most-replied/client/translations.yml index fb6570a5c..ff95ed7dd 100644 --- a/plugins/talk-plugin-sort-most-replied/client/translations.yml +++ b/plugins/talk-plugin-sort-most-replied/client/translations.yml @@ -8,4 +8,4 @@ zh_CN: label: "最多回复在前" zh_TW: talk-plugin-sort-most-replied: - label: 評論最多優先 \ No newline at end of file + label: 最多回復在前 diff --git a/plugins/talk-plugin-sort-most-respected/client/translations.yml b/plugins/talk-plugin-sort-most-respected/client/translations.yml index c3889a8c2..6d514f839 100644 --- a/plugins/talk-plugin-sort-most-respected/client/translations.yml +++ b/plugins/talk-plugin-sort-most-respected/client/translations.yml @@ -5,7 +5,7 @@ es: talk-plugin-sort-most-respected: zh_CN: talk-plugin-sort-most-respected: - label: "最受尊重在前" + label: "最多被赞在前" zh_TW: talk-plugin-sort-most-respected: - label: 最受尊重優先 \ No newline at end of file + label: 最多被讚在前 diff --git a/plugins/talk-plugin-sort-newest/client/translations.yml b/plugins/talk-plugin-sort-newest/client/translations.yml index f483bff58..b172817dc 100644 --- a/plugins/talk-plugin-sort-newest/client/translations.yml +++ b/plugins/talk-plugin-sort-newest/client/translations.yml @@ -8,4 +8,4 @@ zh_CN: label: "最新发表在前" zh_TW: talk-plugin-sort-newest: - label: 最新優先 \ No newline at end of file + label: 最新發表在前 diff --git a/plugins/talk-plugin-sort-oldest/client/translations.yml b/plugins/talk-plugin-sort-oldest/client/translations.yml index 4bd8ac1af..bd30b488d 100644 --- a/plugins/talk-plugin-sort-oldest/client/translations.yml +++ b/plugins/talk-plugin-sort-oldest/client/translations.yml @@ -8,4 +8,4 @@ zh_CN: label: "最早发表在前" zh_TW: talk-plugin-sort-oldest: - label: 最早發表優先 \ No newline at end of file + label: 最早發表在前 diff --git a/plugins/talk-plugin-subscriber/client/translations.yml b/plugins/talk-plugin-subscriber/client/translations.yml index 2030ad337..f43eab042 100644 --- a/plugins/talk-plugin-subscriber/client/translations.yml +++ b/plugins/talk-plugin-subscriber/client/translations.yml @@ -9,4 +9,4 @@ zh_CN: subscriber: "订阅用户" zh_TW: talk-plugin-subscriber: - subscriber: "訂閱者" \ No newline at end of file + subscriber: "訂閱用戶" diff --git a/plugins/talk-plugin-toxic-comments/client/translations.yml b/plugins/talk-plugin-toxic-comments/client/translations.yml index 01ab3ef1e..a6796999a 100644 --- a/plugins/talk-plugin-toxic-comments/client/translations.yml +++ b/plugins/talk-plugin-toxic-comments/client/translations.yml @@ -19,20 +19,20 @@ zh_CN: talk-plugin-toxic-comments: unlikely: "极低可能" highly_likely: "极高可能" - possibly: "不太可能" - likely: "比较可能" - toxic_comment: "有毒评论" + possibly: "有可能" + likely: "很有可能" + toxic_comment: "不当评论" still_toxic: "此编辑后评论可能仍违反了我们的社群指引方针。我们的审查团队将马上处理您的评论。" zh_TW: error: COMMENT_IS_TOXIC: | 確定提交評論?該評論可能包含違反社區準則的言論。您可以編輯該評論或提交以待審核。 talk-plugin-toxic-comments: - unlikely: 不太可能 - highly_likely: 非常有可能 + unlikely: 極低可能 + highly_likely: 極高可能 possibly: 有可能 likely: 很有可能 toxic_comment: 不當評論 still_toxic: | 該評論經修改後仍有可能違反社區準則。 - 我們的審核團隊會儘快審核該您的評論。 \ No newline at end of file + 我們的審核團隊會儘快審核該您的評論。 From 89c929f970b015d871911d03674b2b9c360fd595 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Thu, 11 Jan 2018 11:16:30 +0100 Subject: [PATCH 03/21] Fix suspect label --- client/coral-admin/src/components/CommentLabels.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/coral-admin/src/components/CommentLabels.js b/client/coral-admin/src/components/CommentLabels.js index a40777c40..f39b6e550 100644 --- a/client/coral-admin/src/components/CommentLabels.js +++ b/client/coral-admin/src/components/CommentLabels.js @@ -22,7 +22,7 @@ function getUserFlaggedType(actions) { } function hasSuspectedWords(actions) { - return actions.some((action) => action.__typename === 'FlagAction' && action.reason === 'Matched suspect word filter'); + return actions.some((action) => action.__typename === 'FlagAction' && action.reason === 'SUSPECT_WORD'); } function hasHistoryFlag(actions) { From 11b38f7ea0780f60180fd01bed70659dcdf9f410 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Thu, 11 Jan 2018 13:55:09 +0100 Subject: [PATCH 04/21] Fix no status history error --- client/coral-admin/src/containers/UserDetailComment.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/coral-admin/src/containers/UserDetailComment.js b/client/coral-admin/src/containers/UserDetailComment.js index fe95f4ae4..479d28dc7 100644 --- a/client/coral-admin/src/containers/UserDetailComment.js +++ b/client/coral-admin/src/containers/UserDetailComment.js @@ -35,6 +35,9 @@ export default withFragments({ editing { edited } + status_history { + type + } ...${getDefinitionName(CommentLabels.fragments.comment)} ...${getDefinitionName(CommentDetails.fragments.comment)} } From 1337873adf6b6474eaadaf2ea7f007373afcaa27 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Thu, 11 Jan 2018 16:05:02 +0100 Subject: [PATCH 05/21] Remove unwanted scroll in configure --- .../coral-admin/src/routes/Configure/components/Configure.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/coral-admin/src/routes/Configure/components/Configure.css b/client/coral-admin/src/routes/Configure/components/Configure.css index 82760acff..f4bcb8b01 100644 --- a/client/coral-admin/src/routes/Configure/components/Configure.css +++ b/client/coral-admin/src/routes/Configure/components/Configure.css @@ -20,7 +20,7 @@ .mainContent { width: calc(100% - 300px); - padding: 10px 14px 80px 14px; + padding: 10px 14px 120px 14px; box-sizing: border-box; max-width: 718px; } From 210b0ab907d33a5b050e6339acf5af28b2da5119 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Thu, 11 Jan 2018 16:05:20 +0100 Subject: [PATCH 06/21] Only show scrollbars when necessary --- client/coral-admin/src/components/ToastContainer.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/coral-admin/src/components/ToastContainer.css b/client/coral-admin/src/components/ToastContainer.css index 2fcbd7f7d..100f97f89 100644 --- a/client/coral-admin/src/components/ToastContainer.css +++ b/client/coral-admin/src/components/ToastContainer.css @@ -207,7 +207,7 @@ .toastify__body { color: white; - overflow-x: scroll; + overflow-x: auto; font-size: 15px; font-weight: 400; } From 2269c91244ee6ff8b46ab240df11af32ca8ae262 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Thu, 11 Jan 2018 16:54:54 +0100 Subject: [PATCH 07/21] Restore shortcut functionality --- .../coral-admin/src/routes/Moderation/components/Moderation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/coral-admin/src/routes/Moderation/components/Moderation.js b/client/coral-admin/src/routes/Moderation/components/Moderation.js index e14df7a89..a7a7d16ac 100644 --- a/client/coral-admin/src/routes/Moderation/components/Moderation.js +++ b/client/coral-admin/src/routes/Moderation/components/Moderation.js @@ -176,7 +176,7 @@ class Moderation extends Component { hasNextPage={comments.hasNextPage} activeTab={activeTab} singleView={moderation.singleView} - selectedCommentId={this.state.selectedCommentId} + selectedCommentId={moderation.selectedCommentId} acceptComment={props.acceptComment} rejectComment={props.rejectComment} loadMore={this.loadMore} From 90f1ef050b268b28c8aba3f4938fb4ceca026efe Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Thu, 11 Jan 2018 18:41:53 +0100 Subject: [PATCH 08/21] Change Flags to Reports and make a bit bigger --- client/coral-admin/src/components/CommentDetails.css | 2 +- client/coral-framework/components/CommentDetail.css | 8 ++++---- .../client/components/FlagDetails.css | 6 +++--- .../client/components/UserFlagDetails.css | 6 +++--- plugins/talk-plugin-flag-details/client/translations.yml | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/client/coral-admin/src/components/CommentDetails.css b/client/coral-admin/src/components/CommentDetails.css index 1ee1c46dd..2727a8607 100644 --- a/client/coral-admin/src/components/CommentDetails.css +++ b/client/coral-admin/src/components/CommentDetails.css @@ -5,7 +5,7 @@ .moreDetail { position: absolute; - font-size: 12px; + font-size: 13px; font-weight: 500; color: black; right: 16px; diff --git a/client/coral-framework/components/CommentDetail.css b/client/coral-framework/components/CommentDetail.css index 4806b9522..f5bb33bf2 100644 --- a/client/coral-framework/components/CommentDetail.css +++ b/client/coral-framework/components/CommentDetail.css @@ -12,18 +12,18 @@ margin: 0; font-weight: 500; display: inline-block; - font-size: 12px; + font-size: 13px; line-height: 12px; margin-right: 7px; } .info { - font-size: 12px; + font-size: 13px; } .details { padding: 0 20px 16px; - font-size: 12px; + font-size: 13px; &:empty { display: none; @@ -32,6 +32,6 @@ .icon { vertical-align: middle; - font-size: 12px; + font-size: 13px; margin-right: 7px; } diff --git a/plugins/talk-plugin-flag-details/client/components/FlagDetails.css b/plugins/talk-plugin-flag-details/client/components/FlagDetails.css index 3b59653c2..1e8d03ac4 100644 --- a/plugins/talk-plugin-flag-details/client/components/FlagDetails.css +++ b/plugins/talk-plugin-flag-details/client/components/FlagDetails.css @@ -4,7 +4,7 @@ list-style: none; display: inline-block; padding: 0; - font-size: 12px; + font-size: 13px; margin: 0; } @@ -12,7 +12,7 @@ margin: 0; padding: 0; list-style: none; - font-size: 12px; + font-size: 13px; font-weight: 500; } @@ -20,7 +20,7 @@ margin-left:10px; padding: 0; list-style: none; - font-size: 12px; + font-size: 13px; font-weight: normal; color: #888; } diff --git a/plugins/talk-plugin-flag-details/client/components/UserFlagDetails.css b/plugins/talk-plugin-flag-details/client/components/UserFlagDetails.css index 3b59653c2..1e8d03ac4 100644 --- a/plugins/talk-plugin-flag-details/client/components/UserFlagDetails.css +++ b/plugins/talk-plugin-flag-details/client/components/UserFlagDetails.css @@ -4,7 +4,7 @@ list-style: none; display: inline-block; padding: 0; - font-size: 12px; + font-size: 13px; margin: 0; } @@ -12,7 +12,7 @@ margin: 0; padding: 0; list-style: none; - font-size: 12px; + font-size: 13px; font-weight: 500; } @@ -20,7 +20,7 @@ margin-left:10px; padding: 0; list-style: none; - font-size: 12px; + font-size: 13px; font-weight: normal; color: #888; } diff --git a/plugins/talk-plugin-flag-details/client/translations.yml b/plugins/talk-plugin-flag-details/client/translations.yml index f978ca7ee..df7a14fe9 100644 --- a/plugins/talk-plugin-flag-details/client/translations.yml +++ b/plugins/talk-plugin-flag-details/client/translations.yml @@ -1,6 +1,6 @@ en: talk-plugin-flag-details: - flags: Flags + flags: Reports es: talk-plugin-flag-details: flags: Reportes From 2bc60b12112272107eebbeed9c0e139bb5fdfa52 Mon Sep 17 00:00:00 2001 From: okbel Date: Thu, 11 Jan 2018 14:44:53 -0300 Subject: [PATCH 09/21] fixes --- .../src/components/AccountHistory.js | 41 +++++++++++-------- .../coral-admin/src/components/UserDetail.js | 4 +- .../coral-admin/src/containers/UserDetail.js | 3 ++ 3 files changed, 29 insertions(+), 19 deletions(-) diff --git a/client/coral-admin/src/components/AccountHistory.js b/client/coral-admin/src/components/AccountHistory.js index fd1b034d2..ec3bbca2f 100644 --- a/client/coral-admin/src/components/AccountHistory.js +++ b/client/coral-admin/src/components/AccountHistory.js @@ -13,20 +13,30 @@ const buildUserHistory = (userState = {}) => { .map((k) => userState.status[k].history)), 'created_at', 'desc'); }; -const buildActionResponse = (typename, status) => { - const actionResponses = { - 'UsernameStatusHistory' : `Username Status: ${status}`, - 'BannedStatusHistory': status ? 'User banned' : 'Ban removed', - 'SuspensionStatusHistory': status ? 'Account Suspended' : 'Suspension removed' - }; +const buildActionResponse = (typename, until, status) => { + switch (typename) { + case 'UsernameStatusHistory': + return `Username ${status}`; + case 'BannedStatusHistory': + return status ? 'User banned' : 'Ban removed'; + case 'SuspensionStatusHistory': + return until ? 'Account Suspended' : 'Suspension removed' ; + default: + return '-'; + } +}; - return actionResponses[typename]; +const getModerationValue = (userId, assignedBy = {}) => { + if (assignedBy && userId !== assignedBy.id) { + return assignedBy.username; + } + return 'SYSTEM'; }; class AccountHistory extends React.Component { render() { - const {userState} = this.props; - const userHistory = buildUserHistory(userState); + const {user} = this.props; + const userHistory = buildUserHistory(user.state); return (
@@ -36,16 +46,16 @@ class AccountHistory extends React.Component {
Moderation
{ - userHistory.map((h) => ( -
+ userHistory.map(({__typename, created_at, assigned_by, until, status}) => ( +
- {moment(new Date(h.created_at)).format('MMM DD, YYYY')} + {moment(new Date(created_at)).format('MMM DD, YYYY')}
- {buildActionResponse(h.__typename, h.status)} + {buildActionResponse(__typename, until, status)}
- {h.assigned_by ? h.assigned_by.username : 'SYSTEM'} + {getModerationValue(user.id, assigned_by)}
)) @@ -57,8 +67,7 @@ class AccountHistory extends React.Component { } AccountHistory.propTypes = { - history: PropTypes.array, - userState: PropTypes.object, + user: PropTypes.object.isRequired, }; export default AccountHistory; diff --git a/client/coral-admin/src/components/UserDetail.js b/client/coral-admin/src/components/UserDetail.js index 4b3d4e9bc..309946823 100644 --- a/client/coral-admin/src/components/UserDetail.js +++ b/client/coral-admin/src/components/UserDetail.js @@ -272,9 +272,7 @@ class UserDetail extends React.Component { /> - + diff --git a/client/coral-admin/src/containers/UserDetail.js b/client/coral-admin/src/containers/UserDetail.js index a23ad4c0e..b5173cdc7 100644 --- a/client/coral-admin/src/containers/UserDetail.js +++ b/client/coral-admin/src/containers/UserDetail.js @@ -171,6 +171,7 @@ export const withUserDetailQuery = withQuery(gql` until created_at assigned_by { + id username } } @@ -180,6 +181,7 @@ export const withUserDetailQuery = withQuery(gql` history { status assigned_by { + id username } created_at @@ -190,6 +192,7 @@ export const withUserDetailQuery = withQuery(gql` history { status assigned_by { + id username } created_at From 1dcf297a457d072b51d00a5558c8da663dad2c7b Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 11 Jan 2018 10:58:43 -0700 Subject: [PATCH 10/21] added email fixes --- bin/cli-users | 13 ++++--------- models/user.js | 21 +++++++++++++++++++++ routes/api/users/index.js | 6 +++--- services/passport.js | 7 ++++--- services/users.js | 23 ++++++++++------------- 5 files changed, 42 insertions(+), 28 deletions(-) diff --git a/bin/cli-users b/bin/cli-users index 71c51e44d..208db5053 100755 --- a/bin/cli-users +++ b/bin/cli-users @@ -105,8 +105,7 @@ function printUserAsTable(user) { table.push( {'ID': user.id.gray}, {'Username': user.username}, - {'Emails': user.profiles.filter(({provider}) => provider === 'local').map(({id})=> id) - .join(', ')}, + {'Emails': user.emails}, {'Tags': user.tags ? user.tags.map(({tag: {name}}) => name) : ''}, {'Role': user.role}, {'Verified': user.hasVerifiedEmail}, @@ -161,11 +160,7 @@ async function searchUsers() { } return data.users.nodes.map((user) => { - const emails = user.profiles - .filter(({provider}) => provider === 'local') - .map(({id})=> id) - .join(', '); - + const emails = user.emails.join(', '); return { name: `${user.username} (${emails}) ${user.id.gray} - ${user.role.gray}`, value: user.id, @@ -230,8 +225,8 @@ async function verifyUserEmail(userID, email) { } // Get all the user's email addresses. - const emails = user.profiles.filter(({provider}) => provider === 'local').map(({id}) => id); - if (!emails || emails.length === 0) { + const emails = user.emails; + if (emails.length === 0) { throw new Error('user did not have any email addresses'); } diff --git a/models/user.js b/models/user.js index 37edf4dde..70de303e7 100644 --- a/models/user.js +++ b/models/user.js @@ -279,6 +279,27 @@ UserSchema.method('can', function(...actions) { return can(this, ...actions); }); +/** + * firstEmail will return the first email on the user. + */ +UserSchema.virtual('firstEmail').get(function() { + const emails = this.emails; + if (emails.length === 0) { + return null; + } + + return emails[0]; +}); + +/** + * emails will return all the emails on a user. + */ +UserSchema.virtual('emails').get(function() { + return (this.profiles || []) + .filter(({provider}) => provider === 'local') + .map(({id}) => id); +}); + /** * hasVerifiedEmail will return true if at least one of the local email accounts * have their email verified. diff --git a/routes/api/users/index.js b/routes/api/users/index.js index fd71fecda..17ecfae75 100644 --- a/routes/api/users/index.js +++ b/routes/api/users/index.js @@ -79,13 +79,13 @@ router.post('/:user_id/email/confirm', authorization.needed('ADMIN', 'MODERATOR' } // Find the first local profile. - let localProfile = user.profiles.find((profile) => profile.provider === 'local'); - if (!localProfile) { + const email = user.firstEmail; + if (!email) { return next(errors.ErrMissingEmail); } // Send the email to the first local profile that was found. - await UsersService.sendEmailConfirmation(user, localProfile.id); + await UsersService.sendEmailConfirmation(user, email); res.status(204).end(); } catch (e) { diff --git a/services/passport.js b/services/passport.js index 1d5d0ddcf..36c98fc31 100644 --- a/services/passport.js +++ b/services/passport.js @@ -11,6 +11,7 @@ const uuid = require('uuid'); const debug = require('debug')('talk:services:passport'); const bowser = require('bowser'); const ms = require('ms'); +const _ = require('lodash'); // Create a redis client to use for authentication. const {createClientFactory} = require('./redis'); @@ -144,7 +145,7 @@ async function ValidateUserLogin(loginProfile, user, done) { // If the profile doesn't have a metadata field, or it does not have a // confirmed_at field, or that field is null, then send them back. - if (!profile.metadata || !profile.metadata.confirmed_at || profile.metadata.confirmed_at === null) { + if (_.get(profile, 'metadata.confirmed_at', null) === null) { return done(errors.ErrNotVerified); } } @@ -318,7 +319,7 @@ const CheckIfNeedsRecaptcha = (user, email) => { throw new Error('ID indicated by loginProfile is not on user object'); } - if (profile.metadata && profile.metadata.recaptcha_required) { + if (_.get(profile, 'metadata.recaptcha_required', false)) { return true; } @@ -501,7 +502,7 @@ passport.use(new LocalStrategy({ } // Define the loginProfile being used to perform an additional - // verificaiton. + // verification. let loginProfile = {id: email, provider: 'local'}; // Perform final steps to login the user. diff --git a/services/users.js b/services/users.js index 33c6047fa..03d10c771 100644 --- a/services/users.js +++ b/services/users.js @@ -339,7 +339,7 @@ class UsersService { } /** - * Sets or unsets the recaptcha_required flag on a user's local profile. + * Sets or removes the recaptcha_required flag on a user's local profile. */ static flagForRecaptchaRequirement(email, required) { return UserModel.update( @@ -436,20 +436,17 @@ class UsersService { } static async sendEmail(user, options) { - const localProfile = user.profiles.find( - (profile) => profile.provider === 'local' - ); - if (!localProfile) { - throw new Error('user does not have an email'); + const email = user.firstEmail; + if (!email) { + + // Rather than throwing an error here, we'll + console.warn(new Error('user does not have an email')); + return; } - const {id: to} = localProfile; - - options = merge(options, { - to, - }); - - return mailer.send(options); + return mailer.send(merge({}, options, { + to: email, + })); } static async changePassword(id, password) { From d8375620890bb44599b087aafa5c046e044b668b Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 11 Jan 2018 13:56:23 -0700 Subject: [PATCH 11/21] hides the user status histories and tokens from the auth endpoints, which were resulting in errors --- models/user.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/models/user.js b/models/user.js index 37edf4dde..cb52c312e 100644 --- a/models/user.js +++ b/models/user.js @@ -205,9 +205,13 @@ const UserSchema = new Schema({ toJSON: { transform: function (doc, ret) { - delete ret.password; - delete ret._id; delete ret.__v; + delete ret._id; + delete ret.password; + delete ret.status.username.history; + delete ret.status.banned.history; + delete ret.status.suspension.history; + delete ret.tokens; } } }); From 4d65052eef1ad9a47749902be19a8b1212d86d18 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 11 Jan 2018 14:00:26 -0700 Subject: [PATCH 12/21] allow moderators to view role --- graph/resolvers/user.js | 4 ++-- perms/constants/query.js | 1 + perms/reducers/query.js | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/graph/resolvers/user.js b/graph/resolvers/user.js index cfc72e6e4..7d0a0375a 100644 --- a/graph/resolvers/user.js +++ b/graph/resolvers/user.js @@ -4,7 +4,7 @@ const { SEARCH_ACTIONS, SEARCH_OTHER_USERS, SEARCH_OTHERS_COMMENTS, - UPDATE_USER_ROLES, + VIEW_USER_ROLE, LIST_OWN_TOKENS, VIEW_USER_STATUS, } = require('../../perms/constants'); @@ -68,7 +68,7 @@ const User = { role({id, role}, _, {user}) { // If the user is not an admin, only return the current user's roles. - if (user && (user.can(UPDATE_USER_ROLES) || user.id === id)) { + if (user && (user.can(VIEW_USER_ROLE) || user.id === id)) { return role; } diff --git a/perms/constants/query.js b/perms/constants/query.js index 0b73ed1f9..4cc07109c 100644 --- a/perms/constants/query.js +++ b/perms/constants/query.js @@ -8,4 +8,5 @@ module.exports = { VIEW_USER_STATUS: 'VIEW_USER_STATUS', VIEW_PROTECTED_SETTINGS: 'VIEW_PROTECTED_SETTINGS', LIST_OWN_TOKENS: 'LIST_OWN_TOKENS', + VIEW_USER_ROLE: 'VIEW_USER_ROLE', }; diff --git a/perms/reducers/query.js b/perms/reducers/query.js index 67824342b..7139f6f76 100644 --- a/perms/reducers/query.js +++ b/perms/reducers/query.js @@ -12,6 +12,7 @@ module.exports = (user, perm) => { case types.SEARCH_COMMENT_STATUS_HISTORY: case types.VIEW_USER_STATUS: case types.VIEW_PROTECTED_SETTINGS: + case types.VIEW_USER_ROLE: return check(user, ['ADMIN', 'MODERATOR']); case types.LIST_OWN_TOKENS: return check(user, ['ADMIN']); From 5865c3015504e49812e10b1b345c4682d1af3d9c Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 11 Jan 2018 14:20:51 -0700 Subject: [PATCH 13/21] added a plugin context --- plugins.js | 48 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/plugins.js b/plugins.js index 577289b25..0f0b4ab3d 100644 --- a/plugins.js +++ b/plugins.js @@ -4,6 +4,8 @@ const resolve = require('resolve'); const debug = require('debug')('talk:plugins'); const Joi = require('joi'); const amp = require('app-module-path'); +const pkg = require('./package.json'); +const config = require('./config'); const PLUGINS_JSON = process.env.TALK_PLUGINS_JSON; @@ -63,6 +65,16 @@ const hookSchemas = { }), }; +/** + * PluginContext provides server context for the global application. + */ +class PluginContext { + constructor() { + this.pkg = pkg; + this.config = config; + } +} + /** * isInternal checks to see if a given plugin is internal, and returns true * if it is. @@ -168,7 +180,8 @@ Object.keys(plugins).forEach((type) => itteratePlugins(plugins[type]).forEach((p * Stores a reference to a section for a section of Plugins. */ class PluginSection { - constructor(plugins) { + constructor(context, plugins) { + this.context = context; this.required = false; this.plugins = itteratePlugins(plugins); } @@ -195,43 +208,48 @@ class PluginSection { } /** - * This itterates over the section to provide all plugin hooks that are + * This iterates over the section to provide all plugin hooks that are * available. */ - hook(hook) { + hook(hookName) { // Load the plugin source if we haven't already. this.require(); return this.plugins - .filter(({module}) => hook in module) - .filter((plugin) => { + .filter(({module}) => hookName in module) + .map((plugin) => { + + // Optionally bind the plugin context to a function if it's one. + const hook = typeof plugin.module[hookName] === 'function' ? + plugin.module[hookName].bind(this.context) : + plugin.module[hookName]; // Validate the hook. - if (hook in hookSchemas) { - Joi.assert(plugin.module[hook], hookSchemas[hook], `Plugin '${plugin.name}' failed validation for the '${hook}' hook`); + if (hookName in hookSchemas) { + Joi.assert(hook, hookSchemas[hookName], `Plugin '${plugin.name}' failed validation for the '${hookName}' hook`); } - return true; - }) - .map((plugin) => ({ - plugin, - [hook]: plugin.module[hook] - })); + return { + plugin, + [hookName]: hook, + }; + }); } } -const NullPluginSection = new PluginSection([]); +const NullPluginSection = new PluginSection({}, []); /** * Stores references to all the plugins available on the application. */ class PluginManager { constructor(plugins) { + this.context = new PluginContext(); this.sections = {}; for (let section in plugins) { - this.sections[section] = new PluginSection(plugins[section]); + this.sections[section] = new PluginSection(this.context, plugins[section]); } } From 4eda547734a2ea41425c4b364f837a35c6c7bb28 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 11 Jan 2018 14:22:45 -0700 Subject: [PATCH 14/21] removed config from context --- plugins.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins.js b/plugins.js index 0f0b4ab3d..1d46cea1c 100644 --- a/plugins.js +++ b/plugins.js @@ -5,7 +5,6 @@ const debug = require('debug')('talk:plugins'); const Joi = require('joi'); const amp = require('app-module-path'); const pkg = require('./package.json'); -const config = require('./config'); const PLUGINS_JSON = process.env.TALK_PLUGINS_JSON; @@ -71,7 +70,6 @@ const hookSchemas = { class PluginContext { constructor() { this.pkg = pkg; - this.config = config; } } From 07826a9bf472cf0fe6bfa28316c56ca5617f0e65 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 11 Jan 2018 15:20:33 -0700 Subject: [PATCH 15/21] fixing spelling :( --- plugins.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/plugins.js b/plugins.js index 1d46cea1c..eeb2c0786 100644 --- a/plugins.js +++ b/plugins.js @@ -138,14 +138,14 @@ class Plugin { require() { if (typeof this.path === 'undefined') { - throw new Error(`plugin '${this.name}' is not local and is not resolvable, plugin reconsiliation may be required`); + throw new Error(`plugin '${this.name}' is not local and is not resolvable, plugin reconciliation may be required`); } try { this.module = require(this.path); } catch (e) { if (e && e.code && e.code === 'MODULE_NOT_FOUND' && isInternal(this.name)) { - console.error(new Error(`plugin '${this.name}' could not be loaded due to missing dependencies, plugin reconsiliation may be required`)); + console.error(new Error(`plugin '${this.name}' could not be loaded due to missing dependencies, plugin reconciliation may be required`)); throw e; } @@ -156,19 +156,19 @@ class Plugin { } /** - * Itterates over the plugins and gets the plugin path's, version, and name. + * Iterates over the plugins and gets the plugin path's, version, and name. * * @param {Array} plugins * @returns {Array} */ -const itteratePlugins = (plugins) => plugins.map((p) => new Plugin(p)); +const iteratePlugins = (plugins) => plugins.map((p) => new Plugin(p)); // Add each plugin folder to the allowed import path so that they can import our -// internal dependancies. -Object.keys(plugins).forEach((type) => itteratePlugins(plugins[type]).forEach((plugin) => { +// internal dependencies. +Object.keys(plugins).forEach((type) => iteratePlugins(plugins[type]).forEach((plugin) => { // The plugin may be remote, and therefore not installed. We check here if the - // plugin path is available before trying to monkeypatch it's require path. + // plugin path is available before trying to monkey patch it's require path. if (plugin.path) { amp.enableForDir(path.dirname(plugin.path)); } @@ -181,7 +181,7 @@ class PluginSection { constructor(context, plugins) { this.context = context; this.required = false; - this.plugins = itteratePlugins(plugins); + this.plugins = iteratePlugins(plugins); } require() { @@ -278,5 +278,5 @@ module.exports = { PluginManager, isInternal, pluginPath, - itteratePlugins + iteratePlugins }; From a4e8ea93bc840acfe2f55b6f05998e3014b19990 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 11 Jan 2018 15:24:46 -0700 Subject: [PATCH 16/21] fixed import typo --- bin/cli-plugins | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/cli-plugins b/bin/cli-plugins index 4f293b0c3..bf4014e51 100755 --- a/bin/cli-plugins +++ b/bin/cli-plugins @@ -21,7 +21,7 @@ const path = require('path'); const spawn = require('cross-spawn'); const semver = require('semver'); const resolve = require('resolve'); -const {plugins, itteratePlugins, isInternal} = require('../plugins'); +const {plugins, iteratePlugins, isInternal} = require('../plugins'); function existsInNodeModules(name) { try { @@ -71,7 +71,7 @@ function reconcilePackages({quiet = false, upgradeRemote = false}) { } for (let i in plugins) { - let section = itteratePlugins(plugins[i]); + let section = iteratePlugins(plugins[i]); for (let j in section) { let {name, version} = section[j]; From 7f02d4f39c228fae1c4ea7b4a384a2ee1febc00d Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 11 Jan 2018 15:54:35 -0700 Subject: [PATCH 17/21] fixed circle.yml --- circle.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/circle.yml b/circle.yml index 44bed4d82..cc8ea5725 100644 --- a/circle.yml +++ b/circle.yml @@ -62,10 +62,9 @@ deployment: - bash ./scripts/docker.sh deploy latest: - branches: - only: - - master - - next + branch: + - master + - next owner: coralproject commands: - bash ./scripts/docker.sh deploy From 876f4ef700d947095a879381d959810d63f9672e Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 11 Jan 2018 16:51:08 -0700 Subject: [PATCH 18/21] updated docs for 4.0 --- bin/cli-users | 2 +- docs/_config.yml | 2 +- docs/_data/nav.yaml | 4 + docs/_docs/01-01-talk-quickstart.md | 2 +- docs/_docs/01-02-installation-from-docker.md | 4 +- docs/_docs/01-03-installation-from-source.md | 2 +- docs/_docs/02-01-required-configuration.md | 2 +- docs/_docs/02-02-advanced-configuration.md | 22 +++++- docs/_docs/06-01-migrating-4.md | 80 ++++++++++++++++++++ routes/index.js | 7 ++ 10 files changed, 119 insertions(+), 8 deletions(-) create mode 100644 docs/_docs/06-01-migrating-4.md diff --git a/bin/cli-users b/bin/cli-users index 208db5053..fc50319b5 100755 --- a/bin/cli-users +++ b/bin/cli-users @@ -273,7 +273,7 @@ program .action(deleteUser); program - .command('search') + .command('list') .description('searches for a user based on their stored username and email') .action(searchUsers); diff --git a/docs/_config.yml b/docs/_config.yml index ce28d72c0..6bc934521 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -21,7 +21,7 @@ url: "https://coralproject.github.io" # the base hostname & protocol for your si google_analytics: UA-73335347-5 versions: node: 8+ - yarn: 1.0.1+ + yarn: 1.3.2+ mongodb: 3.2+ redis: 3.2.5+ docker: 17.06.2+ diff --git a/docs/_data/nav.yaml b/docs/_data/nav.yaml index 9138d6603..c765b0fb7 100644 --- a/docs/_data/nav.yaml +++ b/docs/_data/nav.yaml @@ -41,3 +41,7 @@ items: children: - title: FAQ url: /faq/ + - title: Migrating + children: + - title: Migrating to v4.0.0 + url: /migration/4/ diff --git a/docs/_docs/01-01-talk-quickstart.md b/docs/_docs/01-01-talk-quickstart.md index 811bfe5ed..e3c12cc10 100644 --- a/docs/_docs/01-01-talk-quickstart.md +++ b/docs/_docs/01-01-talk-quickstart.md @@ -147,7 +147,7 @@ will definitely not work unless you change those values as well. You can now start the application by running: ```bash -yarn dev-start +yarn watch:server ``` Continue onto the [Running](#running) section for details on how to complete the diff --git a/docs/_docs/01-02-installation-from-docker.md b/docs/_docs/01-02-installation-from-docker.md index fc699f127..3921a368f 100644 --- a/docs/_docs/01-02-installation-from-docker.md +++ b/docs/_docs/01-02-installation-from-docker.md @@ -121,7 +121,7 @@ your containerized infrastructure. The versioning of our Docker tags as well lets you do something like: ```docker -FROM coralproject/talk:3.5-onbuild +FROM coralproject/talk:4.0-onbuild ``` -Which would pin your image to `3.5` release's. \ No newline at end of file +Which would pin your image to `4.0` release's. \ No newline at end of file diff --git a/docs/_docs/01-03-installation-from-source.md b/docs/_docs/01-03-installation-from-source.md index 646a100a9..d6799df15 100644 --- a/docs/_docs/01-03-installation-from-source.md +++ b/docs/_docs/01-03-installation-from-source.md @@ -73,7 +73,7 @@ values as well. You can now start the application by running: ```bash -yarn dev-start +yarn watch:server ``` At this stage, you should refer to the [configuration]({{ "/configuration/" | relative_url }}) for diff --git a/docs/_docs/02-01-required-configuration.md b/docs/_docs/02-01-required-configuration.md index dc09a1707..da235ed90 100644 --- a/docs/_docs/02-01-required-configuration.md +++ b/docs/_docs/02-01-required-configuration.md @@ -8,7 +8,7 @@ Talk requires configuration in order to customize the installation. The default behavior is to load it's configuration from the environment, following the [12 Factor App Manifesto](https://12factor.net/){:target="_blank"}. In development, you can specify configuration in a file named `.env` and it will -be loaded into the environment when you run `yarn dev-start`. +be loaded into the environment when you run `yarn watch:server`. The following variables do not have defaults, and are **required** to start your instance of Talk: diff --git a/docs/_docs/02-02-advanced-configuration.md b/docs/_docs/02-02-advanced-configuration.md index d39fab4b8..e985f8f20 100644 --- a/docs/_docs/02-02-advanced-configuration.md +++ b/docs/_docs/02-02-advanced-configuration.md @@ -8,7 +8,7 @@ Talk requires configuration in order to customize the installation. The default behavior is to load its configuration from the environment, following the [12 Factor App Manifesto](https://12factor.net/){:target="_blank"}. In development, you can specify configuration in a file named `.env` and it will -be loaded into the environment when you run `yarn dev-start`. +be loaded into the environment when you run `yarn watch:server`. The following variables have defaults, and are _optional_ to start your instance of Talk: @@ -467,3 +467,23 @@ same as any other user in the system. (Default `FALSE`) The prefix for the subject of emails sent. An email with the specified subject of `Email Confirmation` would then be sent as `[Talk] Email Confirmation`. (Default `[Talk]`) + +## DISABLE_CREATE_MONGO_INDEXES + +When `TRUE`, Talk will not attempt to create any indices. This is recommended +for production systems that have ran Talk at least once during setup while unset +or set to `FALSE`. + +## TALK_SETTINGS_CACHE_TIME + +The duration of time that the settings object will be kept in the Redis cache, +parsed by [ms](https://www.npmjs.com/package/ms){:target="_blank"}. (Default +`1hr`) + +## APOLLO_ENGINE_KEY + +Used to set the key for use with +[Apollo Engine](https://www.apollographql.com/engine/){:target="_blank"} for +tracing of GraphQL requests. + +**Note: Apollo Engine is a premium service, charges may apply.** \ No newline at end of file diff --git a/docs/_docs/06-01-migrating-4.md b/docs/_docs/06-01-migrating-4.md new file mode 100644 index 000000000..5cd5d3cf7 --- /dev/null +++ b/docs/_docs/06-01-migrating-4.md @@ -0,0 +1,80 @@ +--- +title: Migrating to v4.0.0 +permalink: /migration/4/ +--- + +Since our `v3.*` release, this is the most significant set of changes introduced +into Talk yet as a major database migration, and template change is required to +run it. + +## Dependencies + +If you are running via source, once you update your code, it's always important +to run the following in order to update your dependencies: + +```bash +yarn +``` + +If you are running via Docker, you just have to replace your version number with +the desired version from Dockerhub. + +## Database Migrations + +We have introduced several new fields that require the database to be modified. +To run these migrations, ensure that all nodes of Talk are stopped. It is not +well defined what will happen if a Talk application begins writing data mid +migration. + +Running the following will start the migration process: + +```bash +./bin/cli migration run +``` +This will prompt you to perform a database backup before starting the migration +process. Data loss is entirely possible otherwise. +{: .code-aside} + +The migration itself may take some time to complete, as we're reformatting +documents rather than performing a nice table alter. If the process crashes +during the migration, simply re-run it. The migration operations are designed +to act atomically, and be idempotent to documents already updated. + +## Database Verifications + +In `v3.*`, we introduced the concept of "verifying the database". Some of our +operations update cached values that live along side the original document to +improve performance. Running the cli command for verifying the database's cache +ensures that all the cached values are up to date. + +Running the following will start the database verification process: + +```bash +./bin/cli verify db --fix +``` +You can notice the `--fix` option, without it, the tool should instead perform +a dry run of the operations it intends to perform. +{: .code-aside} + +This process, like the migration process, should take some time to complete on +large databases. + +Once you have updated your databases, that's all you have to do! Talk should now +function even better and faster with all the new features we poured into v4.0.0! + +## Template Change + +In `v4.0.0`, we introduced extensive support for compressing our javascript +bundles. To support this, we had to modify our routing. All static files are now +served out of a `/static` prefix, so you will have to change your embed code: + +**Old:** + +```https://your-talk-url.com/embed.js``` + +**New:** + +```https://your-talk-url.com/static/embed.js``` + +This should be changed in your embed code on the site where you are embedding +Talk. \ No newline at end of file diff --git a/routes/index.js b/routes/index.js index 3f7041654..272b4eea1 100644 --- a/routes/index.js +++ b/routes/index.js @@ -30,6 +30,13 @@ if (!DISABLE_STATIC_SERVER) { const public = path.resolve(path.join(__dirname, '../public')); router.use('/public', express.static(public)); + /** + * Redirect old embed calls. + */ + router.get('/embed.js', (req, res) => { + res.redirect(301, '/static/embed.js'); + }); + /** * Serve the directories under dist. */ From 204b442e2f2fe7bae6b8fb8e292b3ac004d580cd Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 11 Jan 2018 17:32:52 -0700 Subject: [PATCH 19/21] added deprecation warning --- routes/index.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/routes/index.js b/routes/index.js index 272b4eea1..9b1e81ca7 100644 --- a/routes/index.js +++ b/routes/index.js @@ -15,6 +15,7 @@ const staticMiddleware = require('express-static-gzip'); const {DISABLE_STATIC_SERVER} = require('../config'); const {createGraphOptions} = require('../graph'); const {passport} = require('../services/passport'); +const {MOUNT_PATH} = require('../url'); const router = express.Router(); @@ -34,7 +35,11 @@ if (!DISABLE_STATIC_SERVER) { * Redirect old embed calls. */ router.get('/embed.js', (req, res) => { - res.redirect(301, '/static/embed.js'); + const oldEmbed = path.resolve(MOUNT_PATH, 'embed.js'); + const newEmbed = path.resolve(MOUNT_PATH, 'static/embed.js'); + + console.warn(`deprecation warning: ${oldEmbed} will be phased out soon, please replace calls from ${oldEmbed} to ${newEmbed}`); + res.redirect(301, newEmbed); }); /** From bb9a93986e2f5f3dafd00e01b924fc531614c100 Mon Sep 17 00:00:00 2001 From: Kim Gardner Date: Thu, 11 Jan 2018 19:41:45 -0500 Subject: [PATCH 20/21] Small wording tweak --- docs/_docs/06-01-migrating-4.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/_docs/06-01-migrating-4.md b/docs/_docs/06-01-migrating-4.md index 5cd5d3cf7..b66921cf0 100644 --- a/docs/_docs/06-01-migrating-4.md +++ b/docs/_docs/06-01-migrating-4.md @@ -4,7 +4,7 @@ permalink: /migration/4/ --- Since our `v3.*` release, this is the most significant set of changes introduced -into Talk yet as a major database migration, and template change is required to +into Talk so far, as a major database migration and template change are both required to run it. ## Dependencies @@ -77,4 +77,4 @@ served out of a `/static` prefix, so you will have to change your embed code: ```https://your-talk-url.com/static/embed.js``` This should be changed in your embed code on the site where you are embedding -Talk. \ No newline at end of file +Talk. From 0abc2ca243c03fde22fc9a06066ecd5b8404e249 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 11 Jan 2018 20:00:34 -0700 Subject: [PATCH 21/21] replaced eslint:recommended with prettier --- app.js | 22 +- bin/cli | 13 +- bin/cli-assets | 44 +- bin/cli-jobs | 32 +- bin/cli-migration | 26 +- bin/cli-plugins | 194 +++-- bin/cli-serve | 8 +- bin/cli-settings | 6 +- bin/cli-setup | 86 +-- bin/cli-token | 22 +- bin/cli-users | 92 ++- bin/cli-verify | 25 +- .../client/components/MyPluginComponent.js | 4 +- bin/templates/plugin/client/index.js | 5 +- bin/util.js | 22 +- bin/verifications/database/action_counts.js | 96 +-- bin/verifications/database/comment_replies.js | 82 ++- bin/verifications/database/index.js | 5 +- client/coral-admin/src/AppRouter.js | 34 +- client/coral-admin/src/actions/auth.js | 77 +- .../coral-admin/src/actions/banUserDialog.js | 21 +- client/coral-admin/src/actions/community.js | 39 +- client/coral-admin/src/actions/config.js | 4 +- client/coral-admin/src/actions/configure.js | 10 +- client/coral-admin/src/actions/install.js | 137 ++-- client/coral-admin/src/actions/moderation.js | 25 +- client/coral-admin/src/actions/stories.js | 49 +- .../src/actions/suspendUserDialog.js | 22 +- client/coral-admin/src/actions/userDetail.js | 27 +- client/coral-admin/src/actions/users.js | 60 +- .../src/components/AccountHistory.js | 74 +- .../coral-admin/src/components/ActionsMenu.js | 29 +- .../src/components/ActionsMenuItem.js | 13 +- .../coral-admin/src/components/AdminLogin.js | 134 ++-- client/coral-admin/src/components/App.js | 2 +- .../src/components/ApproveButton.js | 11 +- .../src/components/BanUserDialog.js | 78 +- .../src/components/ButtonCopyToClipboard.js | 12 +- .../src/components/CommentAnimatedEdit.js | 10 +- .../src/components/CommentBodyHighlighter.js | 61 +- .../src/components/CommentDetails.js | 32 +- .../src/components/CommentLabels.js | 56 +- .../coral-admin/src/components/CountBadge.js | 8 +- client/coral-admin/src/components/Drawer.js | 60 +- .../coral-admin/src/components/EmptyCard.js | 8 +- .../coral-admin/src/components/FullLoading.js | 6 +- .../coral-admin/src/components/IfHasLink.js | 4 +- client/coral-admin/src/components/LoadMore.js | 17 +- client/coral-admin/src/components/Modal.js | 8 +- .../src/components/ModerationKeysModal.js | 73 +- client/coral-admin/src/components/NotFound.js | 9 +- .../src/components/RejectButton.js | 11 +- .../src/components/SuspendUserDialog.js | 109 +-- .../coral-admin/src/components/TagsInput.js | 10 +- .../src/components/ToastContainer.js | 4 +- .../coral-admin/src/components/UserDetail.js | 216 ++++-- .../src/components/UserDetailComment.js | 63 +- .../src/components/UserDetailCommentList.js | 74 +- .../src/components/UserInfoTooltip.js | 184 +++-- .../coral-admin/src/components/ui/Header.js | 138 ++-- .../src/components/ui/Indicator.js | 3 +- .../coral-admin/src/components/ui/Layout.js | 14 +- client/coral-admin/src/components/ui/Logo.js | 6 +- client/coral-admin/src/constants/actions.js | 3 +- client/coral-admin/src/constants/comments.js | 6 +- client/coral-admin/src/constants/install.js | 3 +- .../src/containers/BanUserDialog.js | 61 +- .../src/containers/CommentDetails.js | 11 +- .../src/containers/CommentLabels.js | 10 +- client/coral-admin/src/containers/Header.js | 44 +- client/coral-admin/src/containers/Layout.js | 59 +- .../src/containers/SuspendUserDialog.js | 74 +- .../coral-admin/src/containers/UserDetail.js | 183 +++-- .../src/containers/UserDetailComment.js | 6 +- client/coral-admin/src/graphql/index.js | 233 +++--- client/coral-admin/src/index.js | 24 +- client/coral-admin/src/reducers/auth.js | 105 +-- .../coral-admin/src/reducers/banUserDialog.js | 37 +- client/coral-admin/src/reducers/community.js | 134 ++-- client/coral-admin/src/reducers/config.js | 18 +- client/coral-admin/src/reducers/configure.js | 59 +- client/coral-admin/src/reducers/install.js | 196 ++--- client/coral-admin/src/reducers/moderation.js | 96 +-- client/coral-admin/src/reducers/stories.js | 103 ++- .../src/reducers/suspendUserDialog.js | 37 +- client/coral-admin/src/reducers/ui.js | 58 +- client/coral-admin/src/reducers/userDetail.js | 80 +- .../routes/Community/components/Community.js | 27 +- .../Community/components/CommunityMenu.js | 20 +- .../Community/components/FlaggedAccounts.js | 78 +- .../Community/components/FlaggedUser.js | 101 +-- .../src/routes/Community/components/People.js | 271 ++++--- .../components/RejectUsernameDialog.js | 124 ++-- .../routes/Community/containers/Community.js | 40 +- .../Community/containers/FlaggedAccounts.js | 56 +- .../Community/containers/FlaggedUser.js | 12 +- .../src/routes/Community/containers/People.js | 123 ++-- .../routes/Configure/components/Configure.js | 81 ++- .../Configure/components/ConfigurePage.js | 2 +- .../routes/Configure/components/Domainlist.js | 6 +- .../routes/Configure/components/EmbedLink.js | 32 +- .../components/ModerationSettings.js | 62 +- .../Configure/components/StreamSettings.js | 150 ++-- .../Configure/components/TechSettings.js | 49 +- .../routes/Configure/components/Wordlist.js | 11 +- .../routes/Configure/containers/Configure.js | 88 +-- .../containers/ModerationSettings.js | 31 +- .../Configure/containers/StreamSettings.js | 31 +- .../Configure/containers/TechSettings.js | 31 +- .../coral-admin/src/routes/Configure/index.js | 2 +- .../src/routes/Install/components/Install.js | 88 +-- .../components/Steps/AddOrganizationName.js | 18 +- .../components/Steps/CreateYourAccount.js | 30 +- .../Install/components/Steps/FinalStep.js | 12 +- .../Install/components/Steps/InitialStep.js | 15 +- .../components/Steps/InviteTeamMembers.js | 27 +- .../components/Steps/PermittedDomainsStep.js | 19 +- .../src/routes/Install/containers/Install.js | 55 +- .../coral-admin/src/routes/Install/index.js | 2 +- .../Moderation/components/AutoLoadMore.js | 4 +- .../routes/Moderation/components/Comment.js | 20 +- .../Moderation/components/Moderation.js | 36 +- .../Moderation/components/ModerationHeader.js | 33 +- .../Moderation/components/ModerationLayout.js | 6 +- .../Moderation/components/ModerationMenu.js | 42 +- .../Moderation/components/ModerationQueue.js | 65 +- .../Moderation/components/NotFoundAsset.js | 12 +- .../src/routes/Moderation/components/Story.js | 4 +- .../Moderation/components/StorySearch.js | 84 ++- .../routes/Moderation/components/ViewMore.js | 17 +- .../Moderation/components/ViewOptions.js | 24 +- .../routes/Moderation/containers/Comment.js | 9 +- .../Moderation/containers/Moderation.js | 155 ++-- .../Moderation/containers/StorySearch.js | 86 +-- .../src/routes/Moderation/graphql.js | 97 ++- .../routes/Moderation/hoc/withQueueConfig.js | 31 +- .../src/routes/Moderation/index.js | 4 +- .../src/routes/Stories/components/Stories.js | 120 +-- .../src/routes/Stories/containers/Stories.js | 96 +-- .../coral-admin/src/routes/Stories/index.js | 2 +- .../coral-admin/src/services/notification.js | 6 +- client/coral-admin/src/utils/index.js | 2 +- client/coral-docs/src/index.js | 4 +- client/coral-docs/src/services/fetcher.js | 4 +- client/coral-embed-stream/src/AppRouter.js | 8 +- .../coral-embed-stream/src/actions/asset.js | 58 +- client/coral-embed-stream/src/actions/auth.js | 195 ++--- .../coral-embed-stream/src/actions/config.js | 6 +- .../src/actions/configure.js | 6 +- .../coral-embed-stream/src/actions/embed.js | 6 +- .../coral-embed-stream/src/actions/stream.js | 49 +- .../src/components/BannedAccount.js | 7 +- .../src/components/DefaultQuestionBoxIcon.js | 4 +- .../src/components/Embed.js | 100 ++- .../src/components/ExtendableTab.js | 10 +- .../src/components/ExtendableTabPanel.js | 28 +- .../src/components/QuestionBox.js | 24 +- .../src/components/Toggleable.js | 25 +- .../src/containers/AutomaticAssetClosure.js | 10 +- .../src/containers/Embed.js | 118 +-- .../src/containers/ExtendableTabPanel.js | 45 +- .../coral-embed-stream/src/graphql/index.js | 28 +- .../coral-embed-stream/src/graphql/utils.js | 78 +- client/coral-embed-stream/src/index.js | 32 +- .../coral-embed-stream/src/reducers/asset.js | 28 +- .../coral-embed-stream/src/reducers/auth.js | 429 +++++------ .../coral-embed-stream/src/reducers/config.js | 16 +- .../src/reducers/configure.js | 53 +- .../coral-embed-stream/src/reducers/embed.js | 16 +- .../coral-embed-stream/src/reducers/index.js | 2 +- .../coral-embed-stream/src/reducers/stream.js | 94 +-- .../configure/components/AssetStatusInfo.js | 43 +- .../tabs/configure/components/Configure.js | 6 +- .../components/QuestionBoxBuilder.js | 55 +- .../src/tabs/configure/components/Settings.js | 34 +- .../configure/containers/AssetStatusInfo.js | 36 +- .../tabs/configure/containers/Configure.js | 23 +- .../src/tabs/configure/containers/Settings.js | 121 +-- .../tabs/stream/components/AllCommentsPane.js | 62 +- .../tabs/stream/components/ChangeUsername.js | 92 +-- .../src/tabs/stream/components/Comment.js | 354 +++++---- .../stream/components/CommentTombstone.js | 22 +- .../stream/components/CountdownSeconds.js | 32 +- .../components/EditableCommentContent.js | 106 +-- .../stream/components/IgnoreUserWizard.js | 39 +- .../stream/components/InactiveCommentLabel.js | 31 +- .../src/tabs/stream/components/LoadMore.js | 30 +- .../src/tabs/stream/components/NewCount.js | 16 +- .../src/tabs/stream/components/NoComments.js | 6 +- .../src/tabs/stream/components/Stream.js | 163 +++-- .../src/tabs/stream/components/StreamError.js | 6 +- .../stream/components/SuspendedAccount.js | 95 +-- .../src/tabs/stream/components/util.js | 9 +- .../tabs/stream/containers/ChangeUsername.js | 8 +- .../src/tabs/stream/containers/Comment.js | 40 +- .../src/tabs/stream/containers/Stream.js | 52 +- client/coral-embed/src/Snackbar.js | 4 +- client/coral-embed/src/Stream.js | 44 +- client/coral-embed/src/index.js | 18 +- .../coral-framework/actions/notification.js | 28 +- .../components/ClickOutside.js | 18 +- .../components/CommentAuthorName.js | 7 +- .../components/CommentContent.js | 18 +- .../components/CommentDetail.js | 12 +- .../components/CommentTimestamp.js | 4 +- .../components/ConfigureCard.js | 48 +- .../components/IfSlotIsEmpty.js | 26 +- .../components/IfSlotIsNotEmpty.js | 26 +- client/coral-framework/components/Markdown.js | 13 +- .../components/MarkdownEditor.js | 19 +- client/coral-framework/components/Popup.js | 18 +- .../components/RestrictedContent.js | 19 +- .../components/RestrictedMessageBox.js | 7 +- client/coral-framework/components/Slot.js | 50 +- .../components/StreamConfiguration.js | 34 +- .../components/TalkProvider.js | 10 +- .../components/__tests__/Markdown.spec.js | 8 +- client/coral-framework/components/index.js | 4 +- client/coral-framework/constants/url.js | 2 +- client/coral-framework/graphql/anywhere.js | 2 +- client/coral-framework/graphql/fragments.js | 7 +- client/coral-framework/graphql/mutations.js | 440 ++++++----- client/coral-framework/helpers/validate.js | 8 +- client/coral-framework/hocs/connect.js | 4 +- client/coral-framework/hocs/excludeIf.js | 19 +- client/coral-framework/hocs/index.js | 16 +- .../hocs/withCopyToClipboard.js | 27 +- client/coral-framework/hocs/withEmit.js | 7 +- client/coral-framework/hocs/withFragments.js | 108 +-- .../hocs/withMergedSettings.js | 13 +- client/coral-framework/hocs/withMutation.js | 260 ++++--- client/coral-framework/hocs/withQuery.js | 510 ++++++------- .../coral-framework/loaders/plugins-loader.js | 12 +- client/coral-framework/services/bootstrap.js | 68 +- client/coral-framework/services/client.js | 70 +- client/coral-framework/services/events.js | 12 +- client/coral-framework/services/graphql.js | 9 +- .../services/graphqlRegistry.js | 50 +- client/coral-framework/services/history.js | 6 +- client/coral-framework/services/i18n.js | 18 +- .../coral-framework/services/introspection.js | 6 +- .../coral-framework/services/notification.js | 6 +- client/coral-framework/services/perms.js | 9 +- client/coral-framework/services/plugins.js | 117 +-- client/coral-framework/services/pym.js | 4 +- client/coral-framework/services/rest.js | 8 +- client/coral-framework/services/storage.js | 77 +- client/coral-framework/services/store.js | 20 +- client/coral-framework/utils/index.js | 145 ++-- client/coral-framework/utils/url.js | 8 +- client/coral-framework/utils/user.js | 10 +- .../coral-settings/components/NotLoggedIn.js | 9 +- .../containers/ProfileContainer.js | 92 ++- .../containers/LoginContainer.js | 4 +- client/coral-ui/components/Alert.js | 2 +- client/coral-ui/components/Button.js | 10 +- client/coral-ui/components/Card.js | 7 +- client/coral-ui/components/Checkbox.js | 10 +- client/coral-ui/components/CoralLogo.js | 87 ++- client/coral-ui/components/Dialog.js | 14 +- client/coral-ui/components/Drawer.js | 16 +- client/coral-ui/components/Dropdown.js | 148 ++-- client/coral-ui/components/FabButton.js | 11 +- client/coral-ui/components/FlagLabel.js | 2 +- client/coral-ui/components/Icon.js | 4 +- client/coral-ui/components/Item.js | 9 +- client/coral-ui/components/Label.js | 4 +- client/coral-ui/components/List.js | 6 +- client/coral-ui/components/Option.js | 21 +- client/coral-ui/components/Paginate.js | 6 +- client/coral-ui/components/PopupMenu.js | 2 +- client/coral-ui/components/SnackBar.js | 12 +- client/coral-ui/components/Spinner.js | 18 +- client/coral-ui/components/Success.js | 14 +- client/coral-ui/components/Tab.js | 32 +- client/coral-ui/components/TabBar.js | 35 +- client/coral-ui/components/TabContent.js | 25 +- client/coral-ui/components/TabCount.js | 27 +- client/coral-ui/components/TabPane.js | 8 +- client/coral-ui/components/TextArea.js | 4 +- client/coral-ui/components/TextField.js | 22 +- client/coral-ui/components/Tooltip.js | 2 +- client/coral-ui/components/Wizard.js | 8 +- client/coral-ui/components/WizardNav.js | 31 +- client/coral-ui/index.js | 60 +- client/talk-plugin-commentbox/CommentBox.js | 144 ++-- client/talk-plugin-commentbox/CommentForm.js | 138 ++-- client/talk-plugin-commentbox/actions.js | 8 +- client/talk-plugin-commentbox/index.js | 2 +- client/talk-plugin-commentbox/reducer.js | 40 +- .../components/FlagButton.js | 219 +++--- .../components/FlagComment.js | 49 +- .../talk-plugin-flags/helpers/flagReasons.js | 4 +- client/talk-plugin-flags/index.js | 2 +- client/talk-plugin-history/Comment.js | 61 +- client/talk-plugin-history/CommentHistory.js | 43 +- client/talk-plugin-history/LoadMore.js | 12 +- client/talk-plugin-infobox/InfoBox.js | 8 +- .../__tests__/InfoBox.spec.js | 12 +- .../talk-plugin-moderation/ModerationLink.js | 28 +- client/talk-plugin-moderation/index.js | 2 +- client/talk-plugin-replies/ReplyBox.js | 36 +- client/talk-plugin-replies/ReplyButton.js | 17 +- client/talk-plugin-replies/index.js | 5 +- client/talk-plugin-tag-label/TagLabel.js | 6 +- config.js | 5 +- errors.js | 190 +++-- graph/connectors.js | 13 +- graph/context.js | 32 +- graph/errorHandler.js | 18 +- graph/hooks.js | 254 ++++--- graph/index.js | 11 +- graph/loaders/actions.js | 29 +- graph/loaders/assets.js | 103 ++- graph/loaders/comments.js | 290 +++++--- graph/loaders/index.js | 24 +- graph/loaders/settings.js | 12 +- graph/loaders/tags.js | 32 +- graph/loaders/users.js | 78 +- graph/loaders/util.js | 42 +- graph/mutators/action.js | 72 +- graph/mutators/asset.js | 35 +- graph/mutators/comment.js | 297 ++++---- graph/mutators/index.js | 24 +- graph/mutators/settings.js | 8 +- graph/mutators/tag.js | 29 +- graph/mutators/token.js | 21 +- graph/mutators/user.js | 48 +- graph/resolvers/action.js | 18 +- graph/resolvers/action_summary.js | 14 +- graph/resolvers/asset.js | 22 +- graph/resolvers/asset_action_summary.js | 14 +- graph/resolvers/banned_status_history.js | 2 +- graph/resolvers/comment.js | 30 +- graph/resolvers/comment_status_history.js | 2 +- graph/resolvers/cursor.js | 19 +- graph/resolvers/date.js | 17 +- graph/resolvers/flag_action.js | 7 +- graph/resolvers/flag_action_summary.js | 4 +- graph/resolvers/index.js | 10 +- graph/resolvers/root_mutation.js | 117 ++- graph/resolvers/root_query.js | 32 +- graph/resolvers/settings.js | 14 +- graph/resolvers/suspension_status_history.js | 2 +- graph/resolvers/tag.js | 4 +- graph/resolvers/tag_link.js | 6 +- graph/resolvers/user.js | 35 +- graph/resolvers/user_error.js | 4 +- graph/resolvers/user_state.js | 11 +- graph/resolvers/username_status_history.js | 2 +- graph/resolvers/util.js | 7 +- graph/schema.js | 16 +- graph/setupFunctions.js | 69 +- graph/subscriptions.js | 103 +-- graph/typeDefs.js | 7 +- graph/utils.js | 17 +- jest.config.js | 25 +- middleware/authentication.js | 40 +- middleware/authorization.js | 13 +- middleware/pubsub.js | 1 - middleware/staticTemplate.js | 18 +- migrations/1496771633_tags.js | 44 +- migrations/1507310316_flags.js | 30 +- migrations/1507322128_dontagree.js | 28 +- migrations/1510174676_user_status.js | 265 +++---- migrations/1511801783_user_roles.js | 12 +- models/action.js | 55 +- models/asset.js | 144 ++-- models/comment.js | 300 ++++---- models/enum/action_types.js | 5 +- models/enum/comment_status.js | 8 +- models/enum/item_types.js | 6 +- models/enum/moderation_options.js | 5 +- models/enum/user_roles.js | 7 +- models/enum/user_status_username.js | 1 - models/migration.js | 2 +- models/schema/tag.js | 71 +- models/schema/tag_link.js | 36 +- models/schema/token.js | 3 +- models/setting.js | 208 +++--- models/user.js | 418 ++++++----- nightwatch-browserstack.conf.js | 21 +- nightwatch.conf.js | 22 +- package.json | 4 +- perms/constants/index.js | 6 +- perms/constants/subscription.js | 2 +- perms/index.js | 23 +- perms/reducers/index.js | 4 +- perms/reducers/mutation.js | 61 +- perms/reducers/query.js | 32 +- perms/reducers/subscription.js | 28 +- perms/utils.js | 5 +- plugin-api/alpha/client/actions/index.js | 7 +- plugin-api/alpha/client/selectors/index.js | 5 +- plugin-api/beta/client/actions/admin.js | 2 +- .../beta/client/actions/notification.js | 2 +- plugin-api/beta/client/actions/stream.js | 2 +- .../beta/client/components/SortOption.js | 1 - plugin-api/beta/client/components/index.js | 38 +- plugin-api/beta/client/factories/index.js | 3 +- plugin-api/beta/client/hocs/index.js | 14 +- plugin-api/beta/client/hocs/withReaction.js | 687 +++++++++--------- plugin-api/beta/client/hocs/withSortOption.js | 57 +- plugin-api/beta/client/hocs/withTags.js | 219 +++--- plugin-api/beta/client/selectors/index.js | 2 +- plugin-api/beta/client/selectors/stream.js | 4 +- plugin-api/beta/client/services/index.js | 5 +- plugin-api/beta/server/getReactionConfig.js | 118 +-- plugins.js | 116 ++- .../client/components/ChangeUsername.js | 76 +- .../client/components/CreateUsernameDialog.js | 27 +- .../client/components/FakeComment.js | 29 +- .../client/components/ForgotContent.js | 47 +- .../client/components/ResendVerification.js | 29 +- .../client/components/SignDialog.js | 15 +- .../client/components/SignInButton.js | 24 +- .../client/components/SignInContainer.js | 84 +-- .../client/components/SignInContent.js | 40 +- .../client/components/SignUpContent.js | 56 +- .../client/components/UserBox.js | 40 +- plugins/talk-plugin-auth/client/index.js | 5 +- plugins/talk-plugin-auth/index.js | 1 - .../talk-plugin-author-menu/client/actions.js | 11 +- .../client/components/AuthorName.js | 21 +- .../client/components/Menu.js | 10 +- .../client/constants.js | 1 - .../client/containers/AuthorName.js | 72 +- .../talk-plugin-author-menu/client/index.js | 4 +- .../talk-plugin-author-menu/client/reducer.js | 53 +- .../client/components/CommentContent.js | 22 +- .../client/containers/CommentContent.js | 7 +- .../client/index.js | 4 +- plugins/talk-plugin-deep-reply-count/index.js | 62 +- plugins/talk-plugin-facebook-auth/index.js | 2 +- .../server/passport.js | 54 +- .../server/router.js | 21 +- .../client/actions.js | 2 +- .../client/components/Comment.js | 49 +- .../client/components/FeaturedButton.js | 30 +- .../client/components/FeaturedDialog.js | 30 +- .../client/components/InfoIcon.js | 6 +- .../client/components/LoadMore.js | 12 +- .../client/components/ModActionButton.js | 44 +- .../client/components/ModTag.js | 43 +- .../client/components/Tab.js | 10 +- .../client/components/TabPane.js | 33 +- .../client/components/Tag.js | 40 +- .../client/components/Tooltip.js | 6 +- .../client/containers/Comment.js | 14 +- .../client/containers/FeaturedButton.js | 2 +- .../client/containers/FeaturedDialog.js | 23 +- .../client/containers/ModActionButton.js | 21 +- .../client/containers/ModSubscription.js | 62 +- .../client/containers/ModTag.js | 29 +- .../client/containers/Tab.js | 18 +- .../client/containers/TabPane.js | 46 +- .../client/containers/Tag.js | 11 +- .../client/index.js | 69 +- .../client/reducer.js | 34 +- .../talk-plugin-featured-comments/index.js | 43 +- .../client/components/FlagDetails.js | 43 +- .../client/components/UserFlagDetails.js | 63 +- .../client/containers/FlagDetails.js | 17 +- .../client/containers/UserFlagDetails.js | 30 +- .../talk-plugin-flag-details/client/index.js | 2 +- .../client/components/IgnoreUserAction.js | 7 +- .../components/IgnoreUserConfirmation.js | 27 +- .../client/components/IgnoredUserSection.js | 8 +- .../client/containers/IgnoreUserAction.js | 28 +- .../containers/IgnoreUserConfirmation.js | 60 +- .../client/containers/IgnoredUserSection.js | 25 +- .../talk-plugin-ignore-user/client/index.js | 48 +- plugins/talk-plugin-like/client/LikeButton.js | 20 +- plugins/talk-plugin-like/client/index.js | 2 +- plugins/talk-plugin-like/index.js | 2 +- plugins/talk-plugin-love/client/LoveButton.js | 20 +- plugins/talk-plugin-love/client/index.js | 4 +- plugins/talk-plugin-love/index.js | 2 +- .../client/components/MemberSinceInfo.js | 14 +- .../client/containers/MemberSinceInfo.js | 17 +- .../talk-plugin-member-since/client/index.js | 13 +- .../client/actions.js | 13 +- .../client/components/ApproveCommentAction.js | 16 +- .../client/components/BanUserAction.js | 11 +- .../client/components/BanUserDialog.js | 21 +- .../client/components/Menu.js | 4 +- .../client/components/ModerationActions.js | 43 +- .../client/components/RejectCommentAction.js | 11 +- .../client/containers/ApproveCommentAction.js | 38 +- .../client/containers/BanUserAction.js | 37 +- .../client/containers/BanUserDialog.js | 50 +- .../client/containers/ModerationActions.js | 68 +- .../client/containers/RejectCommentAction.js | 33 +- .../client/index.js | 2 +- .../client/reducer.js | 65 +- .../talk-plugin-offtopic/client/actions.js | 4 +- .../client/components/OffTopicCheckbox.js | 27 +- .../client/components/OffTopicFilter.js | 13 +- .../client/components/OffTopicTag.js | 20 +- .../client/containers/OffTopicCheckbox.js | 16 +- .../client/containers/OffTopicFilter.js | 18 +- .../client/containers/OffTopicTag.js | 6 +- plugins/talk-plugin-offtopic/client/index.js | 4 +- .../talk-plugin-offtopic/client/reducer.js | 22 +- plugins/talk-plugin-offtopic/index.js | 8 +- .../client/components/PermalinkButton.js | 69 +- .../client/containers/PermalinkButton.js | 8 +- plugins/talk-plugin-permalink/client/index.js | 4 +- .../talk-plugin-remember-sort/client/index.js | 14 +- plugins/talk-plugin-respect/client/Icon.js | 4 +- .../client/RespectButton.js | 22 +- plugins/talk-plugin-respect/client/index.js | 4 +- plugins/talk-plugin-respect/index.js | 2 +- .../client/index.js | 10 +- plugins/talk-plugin-sort-most-liked/index.js | 3 +- .../client/index.js | 10 +- plugins/talk-plugin-sort-most-loved/index.js | 3 +- .../client/index.js | 10 +- .../talk-plugin-sort-most-replied/index.js | 3 +- .../client/index.js | 10 +- .../talk-plugin-sort-most-respected/index.js | 3 +- .../talk-plugin-sort-newest/client/index.js | 16 +- plugins/talk-plugin-sort-newest/index.js | 3 +- .../talk-plugin-sort-oldest/client/index.js | 16 +- plugins/talk-plugin-sort-oldest/index.js | 3 +- .../client/components/SubscriberBadge.js | 8 +- .../client/containers/SubscriberBadge.js | 13 +- .../talk-plugin-subscriber/client/index.js | 4 +- plugins/talk-plugin-subscriber/index.js | 8 +- .../client/components/CheckToxicityHook.js | 16 +- .../client/components/ToxicDetail.js | 16 +- .../client/components/ToxicLabel.js | 6 +- .../client/containers/CheckToxicityHook.js | 9 +- .../client/containers/ToxicCommentDetail.js | 11 +- .../containers/ToxicCommentFlagDetail.js | 11 +- .../client/containers/ToxicDetail.js | 9 +- .../client/containers/ToxicLabel.js | 11 +- .../client/utils.js | 6 +- plugins/talk-plugin-toxic-comments/index.js | 7 +- .../server/config.js | 8 +- .../server/errors.js | 2 +- .../server/hooks.js | 15 +- .../server/perspective.js | 61 +- .../server/resolvers.js | 5 +- .../client/actions.js | 2 +- .../client/components/Category.js | 12 +- .../client/components/Menu.js | 27 +- .../client/components/ViewingOptions.js | 26 +- .../client/constants.js | 1 - .../client/containers/ViewingOptions.js | 25 +- .../client/index.js | 4 +- .../client/reducer.js | 28 +- routes/api/account/index.js | 75 +- routes/api/assets/index.js | 221 +++--- routes/api/auth/index.js | 15 +- routes/api/index.js | 2 +- routes/api/settings/index.js | 18 +- routes/api/setup/index.js | 11 +- routes/api/users/index.js | 49 +- routes/assets/index.js | 9 +- routes/index.js | 52 +- scripts/e2e.js | 95 +-- scripts/generateIntrospectionResult.js | 20 +- secrets.js | 76 +- serve.js | 75 +- services/actions.js | 215 +++--- services/assets.js | 128 ++-- services/cache.js | 73 +- services/comments.js | 177 +++-- services/domain_list.js | 12 +- services/env.js | 1 - services/events/constants.js | 2 +- services/events/index.js | 12 +- services/i18n.js | 31 +- services/jwt.js | 57 +- services/karma.js | 106 +-- services/kue.js | 41 +- services/limit.js | 3 +- services/mailer.js | 56 +- services/metadata.js | 23 +- services/migration.js | 65 +- services/mongoose.js | 12 +- services/passport.js | 402 +++++----- services/plugins.js | 2 +- services/pubsub.js | 4 +- services/redis.js | 43 +- services/scraper.js | 75 +- services/settings.js | 45 +- services/setup.js | 26 +- services/subscriptions.js | 9 +- services/tags.js | 130 ++-- services/timeago.js | 2 +- services/tokens.js | 50 +- services/users.js | 86 +-- services/utils.js | 1 - services/wordlist.js | 68 +- test/.eslintrc.json | 20 +- test/client/setupJest.js | 4 +- test/e2e/globals.js | 10 +- test/e2e/page_objects/admin.js | 239 +++--- test/e2e/page_objects/embedStream.js | 188 ++--- test/e2e/page_objects/install.js | 17 +- test/e2e/page_objects/popup.js | 55 +- test/e2e/specs/.eslintrc.json | 2 +- test/e2e/specs/01_install.js | 51 +- test/e2e/specs/02_admin.js | 21 +- test/e2e/specs/03_embedStream.js | 79 +- test/e2e/specs/04_userStatus.js | 46 +- test/e2e/specs/05_banUser.js | 72 +- test/e2e/specs/06_suspendUser.js | 69 +- test/e2e/utils/SortedWindowHandler.js | 23 +- test/helpers/kue.js | 1 - test/helpers/mongoose.js | 20 +- test/helpers/redis.js | 7 +- test/server/graph/context.js | 13 +- test/server/graph/mutations/addTag.js | 48 +- test/server/graph/mutations/changeUsername.js | 61 +- test/server/graph/mutations/createComment.js | 283 +++++--- test/server/graph/mutations/editComment.js | 127 ++-- test/server/graph/mutations/ignoreUser.js | 116 ++- test/server/graph/mutations/removeTag.js | 74 +- .../graph/mutations/setUserBanStatus.js | 108 ++- .../mutations/setUserSuspensionStatus.js | 132 +++- .../graph/mutations/setUserUsernameStatus.js | 92 ++- .../graph/mutations/updateAssetSettings.js | 40 +- .../graph/mutations/updateAssetStatus.js | 33 +- test/server/graph/mutations/updateSettings.js | 53 +- test/server/graph/queries/asset.js | 68 +- test/server/graph/queries/settings.js | 22 +- test/server/graph/queries/user.js | 31 +- test/server/middleware/authorization.js | 15 +- .../migrations/1510174676_user_status.js | 51 +- test/server/passport.js | 11 +- test/server/routes/api/assets/index.js | 65 +- test/server/routes/api/auth/index.js | 63 +- test/server/routes/api/settings/index.js | 29 +- test/server/routes/api/user/index.js | 40 +- test/server/services/actions.js | 104 +-- test/server/services/assets.js | 155 ++-- test/server/services/comments.js | 238 +++--- test/server/services/domain_list.js | 31 +- test/server/services/settings.js | 53 +- test/server/services/tags.js | 56 +- test/server/services/tokens.js | 26 +- test/server/services/users.js | 181 +++-- test/server/services/wordlist.js | 63 +- url.js | 11 +- webpack.config.js | 191 +++-- yarn.lock | 199 +++-- 649 files changed, 16235 insertions(+), 13008 deletions(-) diff --git a/app.js b/app.js index c0962d3aa..225b5b480 100644 --- a/app.js +++ b/app.js @@ -6,11 +6,11 @@ const merge = require('lodash/merge'); const helmet = require('helmet'); const plugins = require('./services/plugins'); const compression = require('compression'); -const {HELMET_CONFIGURATION} = require('./config'); -const {MOUNT_PATH} = require('./url'); +const { HELMET_CONFIGURATION } = require('./config'); +const { MOUNT_PATH } = require('./url'); const routes = require('./routes'); const debug = require('debug')('talk:app'); -const {ENABLE_TRACING, APOLLO_ENGINE_KEY, PORT} = require('./config'); +const { ENABLE_TRACING, APOLLO_ENGINE_KEY, PORT } = require('./config'); const app = express(); @@ -26,7 +26,7 @@ app.use((req, res, next) => { //============================================================================== // Inject server route plugins. -plugins.get('server', 'app').forEach(({plugin, app: callback}) => { +plugins.get('server', 'app').forEach(({ plugin, app: callback }) => { debug(`added plugin '${plugin.name}'`); // Pass the app to the plugin to mount it's routes. @@ -43,11 +43,11 @@ if (process.env.NODE_ENV !== 'test') { } if (ENABLE_TRACING && APOLLO_ENGINE_KEY) { - const {Engine} = require('apollo-engine'); + const { Engine } = require('apollo-engine'); const engine = new Engine({ engineConfig: { - apiKey: APOLLO_ENGINE_KEY + apiKey: APOLLO_ENGINE_KEY, }, graphqlPort: PORT, endpoint: `${MOUNT_PATH}api/v1/graph/ql`, @@ -64,9 +64,13 @@ app.set('trust proxy', 1); // Enable a suite of security good practices through helmet. We disable // frameguard to allow crossdomain injection of the embed. -app.use(helmet(merge(HELMET_CONFIGURATION, { - frameguard: false, -}))); +app.use( + helmet( + merge(HELMET_CONFIGURATION, { + frameguard: false, + }) + ) +); // Compress the responses if appropriate. app.use(compression()); diff --git a/bin/cli b/bin/cli index 707f41032..6237d4740 100755 --- a/bin/cli +++ b/bin/cli @@ -6,7 +6,7 @@ require('./util'); const program = require('commander'); -const {head, map} = require('lodash'); +const { head, map } = require('lodash'); const Matcher = require('did-you-mean'); program @@ -19,7 +19,10 @@ program .command('users', 'work with the application auth') .command('migration', 'provides utilities for migrating the database') .command('verify', 'provides utilities for performing data verification') - .command('plugins', 'provides utilities for interacting with the plugin system') + .command( + 'plugins', + 'provides utilities for interacting with the plugin system' + ) .parse(process.argv); // If the command wasn't found, output help. @@ -29,9 +32,11 @@ if (!commands.includes(command)) { const m = new Matcher(commands); const similarCommands = m.list(command); - console.error(`cli '${command}' is not a talk cli command. See 'cli --help'.`); + console.error( + `cli '${command}' is not a talk cli command. See 'cli --help'.` + ); if (similarCommands.length > 0) { - const sc = similarCommands.map(({value}) => `\t${value}\n`).join(''); + const sc = similarCommands.map(({ value }) => `\t${value}\n`).join(''); console.error(`\nThe most similar commands are\n${sc}`); } process.exit(1); diff --git a/bin/cli-assets b/bin/cli-assets index 5c1780c5d..d14df320b 100755 --- a/bin/cli-assets +++ b/bin/cli-assets @@ -16,30 +16,24 @@ const scraper = require('../services/scraper'); const inquirer = require('inquirer'); // Register the shutdown criteria. -util.onshutdown([ - () => mongoose.disconnect() -]); +util.onshutdown([() => mongoose.disconnect()]); /** * Lists all the assets registered in the database. */ async function listAssets() { try { - let assets = await AssetModel.find({}).sort({'created_at': 1}); + let assets = await AssetModel.find({}).sort({ created_at: 1 }); let table = new Table({ - head: [ - 'ID', - 'Title', - 'URL' - ] + head: ['ID', 'Title', 'URL'], }); - assets.forEach((asset) => { + assets.forEach(asset => { table.push([ asset.id, asset.title ? asset.title : '', - asset.url ? asset.url : '' + asset.url ? asset.url : '', ]); }); @@ -61,13 +55,13 @@ async function refreshAssets(ageString) { $or: [ { scraped: { - $lte: age - } + $lte: age, + }, }, { - scraped: null - } - ] + scraped: null, + }, + ], }); // Queue all the assets for scraping. @@ -95,7 +89,6 @@ async function updateURL(assetID, assetURL) { async function merge(srcID, dstID) { try { - // Grab the assets... let [srcAsset, dstAsset] = await AssetsService.findByIDs([srcID, dstID]); if (!srcAsset || !dstAsset) { @@ -103,21 +96,22 @@ async function merge(srcID, dstID) { } // Count the affected resources... - let srcCommentCount = await CommentModel.find({asset_id: srcID}).count(); + let srcCommentCount = await CommentModel.find({ asset_id: srcID }).count(); - console.log(`Now going to update ${srcCommentCount} comments and delete the source Asset[${srcID}].`); + console.log( + `Now going to update ${srcCommentCount} comments and delete the source Asset[${srcID}].` + ); - let {confirm} = await inquirer.prompt([ + let { confirm } = await inquirer.prompt([ { type: 'confirm', name: 'confirm', message: 'Proceed with merge', - default: false - } + default: false, + }, ]); if (confirm) { - // Perform the merge! await AssetsService.merge(srcID, dstID); } else { @@ -152,7 +146,9 @@ program program .command('merge ') - .description('merges two assets together by moving comments from src to dst and deleting the src asset') + .description( + 'merges two assets together by moving comments from src to dst and deleting the src asset' + ) .action(merge); program.parse(process.argv); diff --git a/bin/cli-jobs b/bin/cli-jobs index 26eeab147..672a5a70d 100755 --- a/bin/cli-jobs +++ b/bin/cli-jobs @@ -11,20 +11,15 @@ const mailer = require('../services/mailer'); const mongoose = require('../services/mongoose'); const kue = require('../services/kue'); -util.onshutdown([ - () => mongoose.disconnect(), -]); +util.onshutdown([() => mongoose.disconnect()]); /** * Starts the job processor. */ function processJobs() { - // The scraper only needs to shutdown when the scraper has actually been // started. - util.onshutdown([ - () => kue.Task.shutdown() - ]); + util.onshutdown([() => kue.Task.shutdown()]); // Start the scraper processor. scraper.process(); @@ -39,13 +34,15 @@ function processJobs() { * @return {Promise} */ function removeJob(job) { - return new Promise((resolve, reject) => job.remove((err) => { - if (err) { - return reject(err); - } + return new Promise((resolve, reject) => + job.remove(err => { + if (err) { + return reject(err); + } - return resolve(job); - })); + return resolve(job); + }) + ); } /** @@ -82,17 +79,13 @@ async function getJobBatch(n, includeStuck) { * Cleans up the jobs that are in the queue. */ async function cleanupJobs(options) { - // The scraper only needs to shutdown when the scraper has actually been // started. - util.onshutdown([ - () => kue.Task.shutdown() - ]); + util.onshutdown([() => kue.Task.shutdown()]); const n = 100; try { - // Connect to redis by establishing a queue. kue.Task.connect(); @@ -100,9 +93,8 @@ async function cleanupJobs(options) { let jobs = await getJobBatch(n, options.stuck); while (jobs.length > 0) { - // Remove all the jobs. - await Promise.all(jobs.map((job) => removeJob(job))); + await Promise.all(jobs.map(job => removeJob(job))); jobCount += jobs.length; diff --git a/bin/cli-migration b/bin/cli-migration index 18a8c012a..ca93360ad 100755 --- a/bin/cli-migration +++ b/bin/cli-migration @@ -11,13 +11,10 @@ const mongoose = require('../services/mongoose'); const MigrationService = require('../services/migration'); // Register shutdown hooks. -util.onshutdown([ - () => mongoose.disconnect() -]); +util.onshutdown([() => mongoose.disconnect()]); async function createMigration(name) { try { - // Create the migration. await MigrationService.create(name); @@ -29,20 +26,20 @@ async function createMigration(name) { } async function runMigrations() { - try { - - let {backedUp} = await inquirer.prompt([ + let { backedUp } = await inquirer.prompt([ { type: 'confirm', name: 'backedUp', message: 'Did you perform a database backup', - default: false - } + default: false, + }, ]); if (!backedUp) { - throw new Error('Please backup your databases prior to migrations occuring'); + throw new Error( + 'Please backup your databases prior to migrations occuring' + ); } // Get the migrations to run. @@ -50,21 +47,20 @@ async function runMigrations() { console.log('Now going to run the following migrations:\n'); - for (let {filename} of migrations) { + for (let { filename } of migrations) { console.log(`\tmigrations/${filename}`); } - let {confirm} = await inquirer.prompt([ + let { confirm } = await inquirer.prompt([ { type: 'confirm', name: 'confirm', message: 'Proceed with migrations', - default: false - } + default: false, + }, ]); if (confirm) { - // Run the migrations. await MigrationService.run(migrations); } else { diff --git a/bin/cli-plugins b/bin/cli-plugins index bf4014e51..8609ca5ff 100755 --- a/bin/cli-plugins +++ b/bin/cli-plugins @@ -21,11 +21,11 @@ const path = require('path'); const spawn = require('cross-spawn'); const semver = require('semver'); const resolve = require('resolve'); -const {plugins, iteratePlugins, isInternal} = require('../plugins'); +const { plugins, iteratePlugins, isInternal } = require('../plugins'); function existsInNodeModules(name) { try { - resolve.sync(name, {basedir: dir}); + resolve.sync(name, { basedir: dir }); return true; } catch (e) { @@ -39,13 +39,13 @@ function versionMatch(name, version) { resolve.sync(name, { basedir: dir, - packageFilter: (pkg) => { + packageFilter: pkg => { if (pkg && pkg.version && semver.satisfies(pkg.version, version)) { matched = true; } return pkg; - } + }, }); return matched; @@ -56,7 +56,7 @@ function versionMatch(name, version) { const EXTERNAL = /^\w[a-z\-0-9.]+$/; // Match "react", "path", "fs", "lodash.random", etc. -function reconcilePackages({quiet = false, upgradeRemote = false}) { +function reconcilePackages({ quiet = false, upgradeRemote = false }) { const fetchable = []; const local = []; const upgradable = []; @@ -74,10 +74,11 @@ function reconcilePackages({quiet = false, upgradeRemote = false}) { let section = iteratePlugins(plugins[i]); for (let j in section) { - let {name, version} = section[j]; + let { name, version } = section[j]; let namespaced = name.charAt(0) === '@'; - let dep = name.split('/') + let dep = name + .split('/') .slice(0, namespaced ? 2 : 1) .join('/'); @@ -91,7 +92,7 @@ function reconcilePackages({quiet = false, upgradeRemote = false}) { console.log(` l ${name}`); } - local.push({name, version}); + local.push({ name, version }); continue; } @@ -99,9 +100,8 @@ function reconcilePackages({quiet = false, upgradeRemote = false}) { if (!quiet) { console.log(` m ${name}`); } - fetchable.push({name, version}); + fetchable.push({ name, version }); } else if (!versionMatch(dep, version)) { - // A plugin was found, yet the current version does not match the // current version installed. We should warn if upgradeRemote is // not enabled that it is currently not supported. @@ -115,14 +115,14 @@ function reconcilePackages({quiet = false, upgradeRemote = false}) { console.log(` oe ${name} (package upgrade may be required)`); - upgradable.push({name, version}); + upgradable.push({ name, version }); } else { if (!quiet) { console.log(` e ${name}`); } if (upgradeRemote) { - upgradable.push({name, version}); + upgradable.push({ name, version }); } } } @@ -132,33 +132,44 @@ function reconcilePackages({quiet = false, upgradeRemote = false}) { console.log(); } - return {local, fetchable, upgradable}; + return { local, fetchable, upgradable }; } -async function reconcileRemotePlugins({skipLocal, dryRun, upgradeRemote}) { - console.log(`\n[${skipLocal ? '1/2' : '2/3'}] ${emoji.get('mag')} Reconciling plugins...`.yellow); - const {fetchable, upgradable} = reconcilePackages({upgradeRemote}); +async function reconcileRemotePlugins({ skipLocal, dryRun, upgradeRemote }) { + console.log( + `\n[${skipLocal ? '1/2' : '2/3'}] ${emoji.get( + 'mag' + )} Reconciling plugins...`.yellow + ); + const { fetchable, upgradable } = reconcilePackages({ upgradeRemote }); - console.log(`[${skipLocal ? '2/2' : '3/3'}] ${emoji.get('truck')} Fetching plugins...\n`.yellow); + console.log( + `[${skipLocal ? '2/2' : '3/3'}] ${emoji.get('truck')} Fetching plugins...\n` + .yellow + ); if (fetchable.length > 0) { - - console.log(`$ yarn add --ignore-scripts ${fetchable.map(({name, version}) => `${name}@${version}`.cyan)}`); + console.log( + `$ yarn add --ignore-scripts ${fetchable.map( + ({ name, version }) => `${name}@${version}`.cyan + )}` + ); if (!dryRun) { - let args = [ 'add', '--ignore-scripts', - ...fetchable.map(({name, version}) => `${name}@${version}`) + ...fetchable.map(({ name, version }) => `${name}@${version}`), ]; let output = spawn.sync('yarn', args, { - stdio: ['ignore', 'pipe', 'inherit'] + stdio: ['ignore', 'pipe', 'inherit'], }); if (output.status) { - throw new Error('Could not install external plugins, errors occured during install'); + throw new Error( + 'Could not install external plugins, errors occured during install' + ); } console.log(output.stdout.toString()); @@ -166,36 +177,45 @@ async function reconcileRemotePlugins({skipLocal, dryRun, upgradeRemote}) { } if (upgradable.length > 0) { - console.log(`$ yarn upgrade ${upgradable.map(({name, version}) => `${name}@${version}`.cyan)}`); + console.log( + `$ yarn upgrade ${upgradable.map( + ({ name, version }) => `${name}@${version}`.cyan + )}` + ); if (!dryRun) { - let args = [ 'upgrade', - ...upgradable.map(({name, version}) => `${name}@${version}`) + ...upgradable.map(({ name, version }) => `${name}@${version}`), ]; let output = spawn.sync('yarn', args, { - stdio: ['ignore', 'pipe', 'inherit'] + stdio: ['ignore', 'pipe', 'inherit'], }); if (output.status) { - throw new Error('Could not install external plugins, errors occured during install'); + throw new Error( + 'Could not install external plugins, errors occured during install' + ); } console.log(output.stdout.toString()); } } - return {upgradable, fetchable}; + return { upgradable, fetchable }; } -async function reconcileLocalPlugins({skipRemote, dryRun}) { - console.log(`\n[${skipRemote ? '1/1' : '1/3'}] ${emoji.get('pick')} Installing local plugin dependencies...\n`.yellow); - const {local} = reconcilePackages({quiet: true}); +async function reconcileLocalPlugins({ skipRemote, dryRun }) { + console.log( + `\n[${skipRemote ? '1/1' : '1/3'}] ${emoji.get( + 'pick' + )} Installing local plugin dependencies...\n`.yellow + ); + const { local } = reconcilePackages({ quiet: true }); for (let i in local) { - let {name} = local[i]; + let { name } = local[i]; if (!fs.existsSync(path.join(dir, 'plugins', name, 'package.json'))) { continue; @@ -210,11 +230,13 @@ async function reconcileLocalPlugins({skipRemote, dryRun}) { let output = spawn.sync('yarn', args, { stdio: ['ignore', 'pipe', 'inherit'], - cwd: wd + cwd: wd, }); if (output.status) { - throw new Error('Could not install local plugin dependencies, errors occured during install'); + throw new Error( + 'Could not install local plugin dependencies, errors occured during install' + ); } console.log(output.stdout.toString()); @@ -225,7 +247,12 @@ async function reconcileLocalPlugins({skipRemote, dryRun}) { // This traverses the local plugins and installs any dependencies listed there, // this only is really needed for plugins that are installed via docker because // core plugins will have their dependencies already included in core. -async function reconcilePluginDeps({skipLocal, skipRemote, dryRun, upgradeRemote}) { +async function reconcilePluginDeps({ + skipLocal, + skipRemote, + dryRun, + upgradeRemote, +}) { let startTime = new Date(); // We don't need to do anything if we skip everything.... @@ -235,14 +262,19 @@ async function reconcilePluginDeps({skipLocal, skipRemote, dryRun, upgradeRemote // Traverse local plugins and install dependencies if enabled. if (!skipLocal) { - await reconcileLocalPlugins({skipRemote, dryRun}); + await reconcileLocalPlugins({ skipRemote, dryRun }); } // Locate any external plugins and install them. if (!skipRemote) { let results = []; try { - results = await reconcileRemotePlugins({skipLocal, skipRemote, dryRun, upgradeRemote}); + results = await reconcileRemotePlugins({ + skipLocal, + skipRemote, + dryRun, + upgradeRemote, + }); } catch (e) { throw e; } @@ -262,7 +294,9 @@ async function reconcilePluginDeps({skipLocal, skipRemote, dryRun, upgradeRemote } else if (results.fetchable.length === 0) { message = `Upgraded ${results.upgradable.length} new plugins.`; } else { - message = `Fetched ${results.fetchable.length} new plugins, upgraded ${results.upgradable.length} plugins.`; + message = `Fetched ${results.fetchable.length} new plugins, upgraded ${ + results.upgradable.length + } plugins.`; } console.log(`\n${status} ${message}`); @@ -279,8 +313,7 @@ async function createSeedPlugin() { function pluginNameExists(pluginName) { const pluginNames = fs.readdirSync(pluginsDir); - return !!pluginNames - .filter((pn) => pn === pluginName).length; + return !!pluginNames.filter(pn => pn === pluginName).length; } let answers = await inquirer.prompt([ @@ -288,8 +321,7 @@ async function createSeedPlugin() { type: 'input', name: 'pluginName', message: 'Plugin Name:', - validate: (input) => { - + validate: input => { if (pluginNameExists(input)) { return 'Please, choose another name. That name already exists'; } @@ -299,23 +331,23 @@ async function createSeedPlugin() { } return 'Plugin Name is required.'; - } + }, }, { type: 'confirm', name: 'server', - message: 'Is this plugin extending the server capabilities?' + message: 'Is this plugin extending the server capabilities?', }, { type: 'confirm', name: 'client', - message: 'Is this plugin extending the client capabilities?' + message: 'Is this plugin extending the client capabilities?', }, { type: 'confirm', name: 'addPluginsJson', - message: 'Should we add it to the plugins.json?' - } + message: 'Should we add it to the plugins.json?', + }, ]); //============================================================================== @@ -326,41 +358,41 @@ async function createSeedPlugin() { const newPluginPath = path.join(pluginsDir, answers.pluginName); if (fs.existsSync(seedPlugin)) { - if (answers.server && answers.client) { - // This is a server-side and client-side plugin!, let's copy the template fs.copySync(seedPlugin, newPluginPath); - } else { + } else { + fs.copySync(seedPlugin, newPluginPath, { + filter: p => { + // Allowing plugin folder and files with no subfolders + const rootRx = /plugin$|plugin\/[^/]*(\.).{2,3}/gim; + if ( + rootRx.test(p) && + (fs.lstatSync(p).isDirectory() || fs.lstatSync(p).isFile()) + ) { + return true; + } - fs.copySync(seedPlugin, newPluginPath, {filter: (p) => { + // If it's a client-side plugin, copying client folder + if (answers.client) { + return /client/.test(p); + } - // Allowing plugin folder and files with no subfolders - const rootRx = /plugin$|plugin\/[^/]*(\.).{2,3}/igm; - if (rootRx.test(p) && (fs.lstatSync(p).isDirectory() || fs.lstatSync(p).isFile())) { - return true; - } - - // If it's a client-side plugin, copying client folder - if (answers.client) { - return /client/.test(p); - } - - // If it's a server-side plugin, copying server folder - if (answers.server) { - return /server/.test(p); - } - - }}); + // If it's a server-side plugin, copying server folder + if (answers.server) { + return /server/.test(p); + } + }, + }); } // Let's add this to the plugins.json if (answers.addPluginsJson) { const pluginsJson = path.resolve(__dirname, '..', 'plugins.json'); - fs.readJson(pluginsJson) - .then((j) => { - + fs + .readJson(pluginsJson) + .then(j => { // This is a client-side plugin, let's push this. if (answers.client) { j.client.push(answers.pluginName); @@ -377,14 +409,17 @@ async function createSeedPlugin() { fs.writeFileSync(pluginsJson, output); } }) - .catch((err) => { + .catch(err => { console.error(err); }); } - console.log(`✨ Yay! Plugin created! Find your plugin: ${answers.pluginName} in the ./plugins folder`); + console.log( + `✨ Yay! Plugin created! Find your plugin: ${ + answers.pluginName + } in the ./plugins folder` + ); } - } //============================================================================== @@ -403,9 +438,14 @@ program program .command('reconcile') - .description('reconciles local plugin dependencies and downloads external plugins') + .description( + 'reconciles local plugin dependencies and downloads external plugins' + ) .option('-u, --upgrade-remote', 'upgrades remote dependencies') - .option('-d, --dry-run', 'does not actually change anything on the filesystem acts only as a simulation') + .option( + '-d, --dry-run', + 'does not actually change anything on the filesystem acts only as a simulation' + ) .option('--skip-local', 'skips the local dependancy reconciliation') .option('--skip-remote', 'skips the remote plugin reconciliation') .action(reconcilePluginDeps); diff --git a/bin/cli-serve b/bin/cli-serve index 3e356d829..b8dbd4594 100755 --- a/bin/cli-serve +++ b/bin/cli-serve @@ -10,12 +10,14 @@ const serve = require('../serve'); program .option('-j, --jobs', 'enable job processing on this thread') - .option('-w, --websockets', 'enable the websocket (subscriptions) handler on this thread') + .option( + '-w, --websockets', + 'enable the websocket (subscriptions) handler on this thread' + ) .parse(process.argv); // Start serving. -serve({jobs: program.jobs, websockets: program.websockets}).catch((err) => { +serve({ jobs: program.jobs, websockets: program.websockets }).catch(err => { console.error(err); util.shutdown(1); }); - diff --git a/bin/cli-settings b/bin/cli-settings index 6ffacbeaf..7f449e0d5 100755 --- a/bin/cli-settings +++ b/bin/cli-settings @@ -16,12 +16,12 @@ async function changeOrgName() { try { let settings = await SettingsService.retrieve(); - let {organizationName} = await inquirer.prompt([ + let { organizationName } = await inquirer.prompt([ { name: 'organizationName', message: 'Organization Name', - default: settings.organizationName - } + default: settings.organizationName, + }, ]); if (settings.organizationName !== organizationName) { diff --git a/bin/cli-setup b/bin/cli-setup index 0f14add55..b0f5e2efa 100755 --- a/bin/cli-setup +++ b/bin/cli-setup @@ -16,9 +16,7 @@ const UsersService = require('../services/users'); const errors = require('../errors'); // Register the shutdown criteria. -util.onshutdown([ - () => mongoose.disconnect() -]); +util.onshutdown([() => mongoose.disconnect()]); //============================================================================== // Setting up the program command line arguments. @@ -34,19 +32,15 @@ program //============================================================================== const performSetup = async () => { - // Get the current settings, we are expecing an error here. try { - // Try to get the settings. await SettingsService.retrieve(); // We should NOT have gotten a settings object, this means that the // application is already setup. Error out here. throw errors.ErrSettingsInit; - } catch (e) { - // If the error is `not init`, then we're good, otherwise, it's something // else. if (e !== errors.ErrSettingsNotInit) { @@ -66,7 +60,9 @@ const performSetup = async () => { // Create the base settings model. let settings = new SettingModel(); - console.log('\nWe\'ll ask you some questions in order to setup your installation of Talk.\n'); + console.log( + "\nWe'll ask you some questions in order to setup your installation of Talk.\n" + ); let answers = await inquirer.prompt([ { @@ -74,31 +70,31 @@ const performSetup = async () => { name: 'organizationName', message: 'Organization Name', default: settings.organizationName, - validate: (input) => { + validate: input => { if (input && input.length > 0) { return true; } return 'Organization Name is required.'; - } + }, }, { type: 'list', choices: MODERATION_OPTIONS, name: 'moderation', default: settings.moderation, - message: 'Select a moderation mode' + message: 'Select a moderation mode', }, { type: 'confirm', name: 'requireEmailConfirmation', default: settings.requireEmailConfirmation, - message: 'Should emails always be confirmed' + message: 'Should emails always be confirmed', }, ]); // Update the settings that were changed. - Object.keys(answers).forEach((key) => { + Object.keys(answers).forEach(key => { if (answers[key] !== undefined) { settings[key] = answers[key]; } @@ -109,97 +105,93 @@ const performSetup = async () => { type: 'confirm', name: 'inputWhitelistedDomains', default: true, - message: 'Would you like to specify a whitelisted domain' + message: 'Would you like to specify a whitelisted domain', }, { type: 'input', name: 'whitelistedDomain', message: 'Whitelisted Domain', - when: ({inputWhitelistedDomains}) => inputWhitelistedDomains, - validate: (input) => { + when: ({ inputWhitelistedDomains }) => inputWhitelistedDomains, + validate: input => { if (input && input.length > 0) { return true; } return 'Whitelisted Domain cannot be empty.'; - } - } + }, + }, ]); if (answers.inputWhitelistedDomains) { settings.domains.whitelist = [answers.whitelistedDomain]; } - console.log('\nWe\'ll ask you some questions about your first admin user.\n'); + console.log("\nWe'll ask you some questions about your first admin user.\n"); let user = await inquirer.prompt([ { type: 'input', name: 'username', message: 'Username', - filter: (username) => { - return UsersService - .isValidUsername(username, false) - .catch((err) => { - throw err.message; - }); - } + filter: username => { + return UsersService.isValidUsername(username, false).catch(err => { + throw err.message; + }); + }, }, { name: 'email', message: 'Email', format: 'email', - validate: (value) => { + validate: value => { if (value && value.length >= 3) { return true; } return 'Email is required'; - } + }, }, { name: 'password', message: 'Password', type: 'password', - filter: (password) => { - return UsersService - .isValidPassword(password) - .catch((err) => { - throw err.message; - }); - } + filter: password => { + return UsersService.isValidPassword(password).catch(err => { + throw err.message; + }); + }, }, { name: 'confirmPassword', message: 'Confirm Password', type: 'password', - filter: (confirmPassword, {password}) => { + filter: (confirmPassword, { password }) => { if (password !== confirmPassword) { return Promise.reject(new Error('Passwords do not match')); } - return UsersService - .isValidPassword(confirmPassword) - .catch((err) => { - throw err.message; - }); - } + return UsersService.isValidPassword(confirmPassword).catch(err => { + throw err.message; + }); + }, }, ]); - let {user: newUser} = await SetupService.setup({ + let { user: newUser } = await SetupService.setup({ settings: settings.toObject(), user: { email: user.email, username: user.username, - password: user.password - } + password: user.password, + }, }); console.log('Settings created.'); console.log(`User ${newUser.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.'); + console.log( + '\nWe recommend adding TALK_INSTALL_LOCK=TRUE to your environment to turn off the dynamic setup.' + ); }; // Start tthe setup process. @@ -207,7 +199,7 @@ performSetup() .then(() => { util.shutdown(); }) - .catch((e) => { + .catch(e => { console.error(e); util.shutdown(1); }); diff --git a/bin/cli-token b/bin/cli-token index dc1b09e6f..b131728e6 100755 --- a/bin/cli-token +++ b/bin/cli-token @@ -11,28 +11,18 @@ const TokensService = require('../services/tokens'); const Table = require('cli-table'); // Register the shutdown criteria. -util.onshutdown([ - () => mongoose.disconnect() -]); +util.onshutdown([() => mongoose.disconnect()]); async function listTokens(userID) { try { let tokens = await TokensService.list(userID); let table = new Table({ - head: [ - 'ID', - 'Name', - 'Status' - ] + head: ['ID', 'Name', 'Status'], }); - tokens.forEach((token) => { - table.push([ - token.id, - token.name, - token.active ? 'Active' : 'Revoked' - ]); + tokens.forEach(token => { + table.push([token.id, token.name, token.active ? 'Active' : 'Revoked']); }); console.log(table.toString()); @@ -46,7 +36,6 @@ async function listTokens(userID) { async function revokeToken(tokenID) { try { - await TokensService.revoke(null, tokenID); console.log(`Revoked Token[${tokenID}]`); @@ -60,8 +49,7 @@ async function revokeToken(tokenID) { async function createToken(userID, tokenName) { try { - - let {pat: {id}, jwt} = await TokensService.create(userID, tokenName); + let { pat: { id }, jwt } = await TokensService.create(userID, tokenName); console.log(`Created Token[${id}] for User[${userID}] = ${jwt}`); diff --git a/bin/cli-users b/bin/cli-users index fc50319b5..64d51e3b4 100755 --- a/bin/cli-users +++ b/bin/cli-users @@ -7,15 +7,18 @@ const util = require('./util'); const program = require('commander'); const inquirer = require('inquirer'); -const {graphql} = require('graphql'); -const {stripIndent} = require('common-tags'); +const { graphql } = require('graphql'); +const { stripIndent } = require('common-tags'); const Table = require('cli-table'); // Make things colorful! require('colors'); // Register the autocomplete plugin. -inquirer.registerPrompt('autocomplete', require('inquirer-autocomplete-prompt')); +inquirer.registerPrompt( + 'autocomplete', + require('inquirer-autocomplete-prompt') +); const schema = require('../graph/schema'); const Context = require('../graph/context'); @@ -28,19 +31,15 @@ const mongoose = require('../services/mongoose'); const databaseVerifications = require('./verifications/database'); // Register the shutdown criteria. -util.onshutdown([ - () => mongoose.disconnect() -]); +util.onshutdown([() => mongoose.disconnect()]); /** * Deletes a user and cleans up their associated verifications. */ async function deleteUser(userID) { - try { - // Find the user we're removing. - const user = await UserModel.findOne({id: userID}); + const user = await UserModel.findOne({ id: userID }); if (!user) { throw new Error(`user with id ${userID} not found`); } @@ -54,7 +53,7 @@ async function deleteUser(userID) { This might take a long time if there is a lot of data, please confirm that you want to continue. `); - const {confirm} = await inquirer.prompt({ + const { confirm } = await inquirer.prompt({ type: 'confirm', name: 'confirm', message: 'Continue', @@ -64,27 +63,25 @@ async function deleteUser(userID) { return util.shutdown(); } - console.warn('Removing user\'s actions'); + console.warn("Removing user's actions"); // Remove all the user's actions. - await ActionModel - .where({user_id: user.id}) - .setOptions({multi: true}) + await ActionModel.where({ user_id: user.id }) + .setOptions({ multi: true }) .remove(); - console.warn('Removing user\'s comments'); + console.warn("Removing user's comments"); // Remove all the user's comments. - await CommentModel - .where({author_id: user.id}) - .setOptions({multi: true}) + await CommentModel.where({ author_id: user.id }) + .setOptions({ multi: true }) .remove(); console.warn('Updating the database indexes'); // Update the counts that might have changed. for (const verification of databaseVerifications) { - await verification({fix: true, limit: Infinity, batch: 1000}); + await verification({ fix: true, limit: Infinity, batch: 1000 }); } console.warn('Removing the user'); @@ -103,15 +100,19 @@ function printUserAsTable(user) { let table = new Table({}); table.push( - {'ID': user.id.gray}, - {'Username': user.username}, - {'Emails': user.emails}, - {'Tags': user.tags ? user.tags.map(({tag: {name}}) => name) : ''}, - {'Role': user.role}, - {'Verified': user.hasVerifiedEmail}, - {'Username': user.status.username.status}, - {'Banned': user.banned}, - {'Suspension': user.suspended ? `Until ${user.status.suspension.until}` : false}, + { ID: user.id.gray }, + { Username: user.username }, + { Emails: user.emails }, + { Tags: user.tags ? user.tags.map(({ tag: { name } }) => name) : '' }, + { Role: user.role }, + { Verified: user.hasVerifiedEmail }, + { Username: user.status.username.status }, + { Banned: user.banned }, + { + Suspension: user.suspended + ? `Until ${user.status.suspension.until}` + : false, + } ); console.log(table.toString()); @@ -148,7 +149,7 @@ async function searchUsers() { value = ''; } - const {data, errors} = await graphql(schema, searchQuery, {}, ctx, { + const { data, errors } = await graphql(schema, searchQuery, {}, ctx, { value, }); if (errors && errors.length > 0) { @@ -159,18 +160,20 @@ async function searchUsers() { return []; } - return data.users.nodes.map((user) => { + return data.users.nodes.map(user => { const emails = user.emails.join(', '); return { - name: `${user.username} (${emails}) ${user.id.gray} - ${user.role.gray}`, + name: `${user.username} (${emails}) ${user.id.gray} - ${ + user.role.gray + }`, value: user.id, }; }); - } + }, }); - const {userID} = answers; - const user = await UserModel.findOne({id: userID}); + const { userID } = answers; + const user = await UserModel.findOne({ id: userID }); printUserAsTable(user); util.shutdown(0); @@ -187,13 +190,13 @@ async function searchUsers() { */ async function setUserRole(userID) { try { - const {role} = await inquirer.prompt([ + const { role } = await inquirer.prompt([ { name: 'role', message: 'User Role', type: 'list', - choices: USER_ROLES - } + choices: USER_ROLES, + }, ]); await UsersService.setRole(userID, role); @@ -204,7 +207,6 @@ async function setUserRole(userID) { console.error(err); util.shutdown(1); } - } /** @@ -217,9 +219,8 @@ async function setUserRole(userID) { */ async function verifyUserEmail(userID, email) { try { - // Get the user. - const user = await UserModel.findOne({id: userID}); + const user = await UserModel.findOne({ id: userID }); if (!user) { throw new Error(`user with ID ${userID} cannot be found`); } @@ -231,23 +232,20 @@ async function verifyUserEmail(userID, email) { } if (!email && emails.length === 1) { - // The email wasn't passed, and there is only one option. email = emails[0]; - } else if (!emails.includes(email)){ - + } else if (!emails.includes(email)) { // The email passed doesn't belong to this user. throw new Error(`user does not have the email ${email}`); } else if (emails.length > 1) { - // The email wasn't passed, and there is more than one choice. const answers = await inquirer.prompt([ { name: 'email', message: 'Select Email to Verify', type: 'list', - choices: emails - } + choices: emails, + }, ]); email = answers.email; @@ -284,7 +282,7 @@ program program .command('verify ') - .description('verifies the given user\'s email address') + .description("verifies the given user's email address") .action(verifyUserEmail); program.parse(process.argv); diff --git a/bin/cli-verify b/bin/cli-verify index 5a55f2bd2..e32c7d169 100755 --- a/bin/cli-verify +++ b/bin/cli-verify @@ -10,17 +10,18 @@ const mongoose = require('../services/mongoose'); const databaseVerifications = require('./verifications/database'); // Register the shutdown criteria. -util.onshutdown([ - () => mongoose.disconnect() -]); +util.onshutdown([() => mongoose.disconnect()]); -async function database({fix = false, limit = Infinity, batch = 1000}) { +async function database({ fix = false, limit = Infinity, batch = 1000 }) { try { for (const verification of databaseVerifications) { - await verification({fix, limit, batch}); + await verification({ fix, limit, batch }); } } catch (err) { - console.error(`Failed to process all the ${databaseVerifications.length} verifications`, err); + console.error( + `Failed to process all the ${databaseVerifications.length} verifications`, + err + ); util.shutdown(1); return; } @@ -36,8 +37,16 @@ program .command('db') .description('verifies the database integrity') .option('-f, --fix', 'fix the problems found with database inconsistencies') - .option('-l, --limit [size]', 'limit the amount of documents to process in a single pass, this will ensure only a maximum number of batch operations are issued [default: inf]', parseInt) - .option('-b, --batch [size]', 'batch size to process verifications and repairs of documents [default: 1000]', parseInt) + .option( + '-l, --limit [size]', + 'limit the amount of documents to process in a single pass, this will ensure only a maximum number of batch operations are issued [default: inf]', + parseInt + ) + .option( + '-b, --batch [size]', + 'batch size to process verifications and repairs of documents [default: 1000]', + parseInt + ) .action(database); program.parse(process.argv); diff --git a/bin/templates/plugin/client/components/MyPluginComponent.js b/bin/templates/plugin/client/components/MyPluginComponent.js index 89e274e1a..98488b133 100644 --- a/bin/templates/plugin/client/components/MyPluginComponent.js +++ b/bin/templates/plugin/client/components/MyPluginComponent.js @@ -1,12 +1,12 @@ import React from 'react'; -import {CoralLogo} from 'plugin-api/beta/client/components/ui'; +import { CoralLogo } from 'plugin-api/beta/client/components/ui'; import styles from './MyPluginComponent.css'; class MyPluginComponent extends React.Component { render() { return (
- +

Plugin created by Talk CLI

diff --git a/bin/templates/plugin/client/index.js b/bin/templates/plugin/client/index.js index acd0910be..72a7fe6a7 100644 --- a/bin/templates/plugin/client/index.js +++ b/bin/templates/plugin/client/index.js @@ -1,4 +1,3 @@ - /** This is a client index example file and it could look like this: @@ -21,6 +20,6 @@ import MyPluginComponent from './components/MyPluginComponent'; export default { slots: { - stream: [MyPluginComponent] - } + stream: [MyPluginComponent], + }, }; diff --git a/bin/util.js b/bin/util.js index 546607c20..0a9adb966 100644 --- a/bin/util.js +++ b/bin/util.js @@ -1,10 +1,9 @@ - // Setup the environment. require('../services/env'); const debug = require('debug')('talk:util'); -const util = module.exports = {}; +const util = (module.exports = {}); /** * Stores an array of functions that should be executed in the event that the @@ -18,20 +17,22 @@ util.toshutdown = []; * @param {Number} [defaultCode=0] default return code upon sucesfull shutdown. */ util.shutdown = (defaultCode = 0, signal = null) => { - if (signal) { debug(`Reached ${signal} signal`); } debug(`${util.toshutdown.length} jobs now being called`); - Promise - .all(util.toshutdown.map((func) => func ? func(signal) : null).filter((func) => func)) + Promise.all( + util.toshutdown + .map(func => (func ? func(signal) : null)) + .filter(func => func) + ) .then(() => { debug('Shutdown complete, now exiting'); process.exit(defaultCode); }) - .catch((err) => { + .catch(err => { console.error(err); process.exit(1); @@ -44,8 +45,7 @@ util.shutdown = (defaultCode = 0, signal = null) => { * @param {Array} jobs Array of promise capable shutdown functions that are * executed. */ -util.onshutdown = (jobs) => { - +util.onshutdown = jobs => { debug(`${jobs.length} jobs registered to be called during shutdown`); // Add the new jobs to shutdown to the object reference. @@ -55,13 +55,13 @@ util.onshutdown = (jobs) => { // Attach to the SIGTERM + SIGINT handles to ensure a clean shutdown in the // event that we have an external event. SIGUSR2 is called when the app is asked // to be 'killed', same procedure here. -process.on('SIGTERM', () => util.shutdown(0, 'SIGTERM')); -process.on('SIGINT', () => util.shutdown(0, 'SIGINT')); +process.on('SIGTERM', () => util.shutdown(0, 'SIGTERM')); +process.on('SIGINT', () => util.shutdown(0, 'SIGINT')); process.once('SIGUSR2', () => util.shutdown(0, 'SIGUSR2')); // Makes the script crash on unhandled rejections instead of silently // ignoring them. In the future, promise rejections that are not handled will // terminate the Node.js process with a non-zero exit code. -process.on('unhandledRejection', (err) => { +process.on('unhandledRejection', err => { throw err; }); diff --git a/bin/verifications/database/action_counts.js b/bin/verifications/database/action_counts.js index 2aa47a9a7..684c17bca 100644 --- a/bin/verifications/database/action_counts.js +++ b/bin/verifications/database/action_counts.js @@ -1,28 +1,24 @@ const UserModel = require('../../../models/user'); const CommentModel = require('../../../models/comment'); const ActionsService = require('../../../services/actions'); -const {arrayJoinBy} = require('../../../graph/loaders/util'); -const {get} = require('lodash'); +const { arrayJoinBy } = require('../../../graph/loaders/util'); +const { get } = require('lodash'); const debug = require('debug')('talk:cli:verify'); -const MODELS = [ - UserModel, - CommentModel, -]; +const MODELS = [UserModel, CommentModel]; async function processBatch(Model, documents) { - // Get an array of all the document id's. - const documentIDs = documents.map(({id}) => id); + const documentIDs = documents.map(({ id }) => id); // Store all the operations on this batch in this array that we'll return // later. const operations = []; // Get the action summaries for this batch. - const totalActionSummaries = await ActionsService - .getActionSummaries(documentIDs) - .then(arrayJoinBy(documentIDs, 'item_id')); + const totalActionSummaries = await ActionsService.getActionSummaries( + documentIDs + ).then(arrayJoinBy(documentIDs, 'item_id')); // Iterate over the documents. for (let i = 0; i < documents.length; i++) { @@ -49,8 +45,10 @@ async function processBatch(Model, documents) { const ACTION_COUNT_FIELD = `${ACTION_TYPE}_${GROUP_ID}`; // Check that the action summaries match the cached counts. - if (get(document, ['action_counts', ACTION_COUNT_FIELD]) !== actionSummary.count) { - + if ( + get(document, ['action_counts', ACTION_COUNT_FIELD]) !== + actionSummary.count + ) { // Batch updates for those changes. ops.push({ [`action_counts.${ACTION_COUNT_FIELD}`]: actionSummary.count, @@ -60,27 +58,28 @@ async function processBatch(Model, documents) { // Group all the action summaries together from all the different group // ids. - const groupedActionSummaries = actionSummaries.reduce((acc, actionSummary) => { + const groupedActionSummaries = actionSummaries.reduce( + (acc, actionSummary) => { + // action_type is already snake cased (as it would have had to be when it + // was inserted in the database). + const ACTION_TYPE = actionSummary.action_type.toLowerCase(); - // action_type is already snake cased (as it would have had to be when it - // was inserted in the database). - const ACTION_TYPE = actionSummary.action_type.toLowerCase(); + if (!(ACTION_TYPE in acc)) { + acc[ACTION_TYPE] = 0; + } - if (!(ACTION_TYPE in acc)) { - acc[ACTION_TYPE] = 0; - } + acc[ACTION_TYPE] += actionSummary.count; - acc[ACTION_TYPE] += actionSummary.count; - - return acc; - }, {}); + return acc; + }, + {} + ); for (const ACTION_COUNT_FIELD of Object.keys(groupedActionSummaries)) { const count = groupedActionSummaries[ACTION_COUNT_FIELD]; // Check that the action summaries match the cached counts. if (get(document, ['action_counts', ACTION_COUNT_FIELD]) !== count) { - // Batch updates for those changes. ops.push({ [`action_counts.${ACTION_COUNT_FIELD}`]: count, @@ -94,7 +93,7 @@ async function processBatch(Model, documents) { operations.push({ updateOne: { filter: { - id: document.id + id: document.id, }, update: { $set: Object.assign({}, ...ops), @@ -107,23 +106,21 @@ async function processBatch(Model, documents) { return operations; } -module.exports = async ({fix, batch}) => { +module.exports = async ({ fix, batch }) => { for (const Model of MODELS) { - const cursor = Model - .collection + const cursor = Model.collection .find({}) .project({ id: 1, - action_counts: 1 + action_counts: 1, }) - .sort({created_at: 1}); + .sort({ created_at: 1 }); let operations = []; let documents = []; // While there are documents to process. while (await cursor.hasNext()) { - // Load the document. const document = await cursor.next(); @@ -133,7 +130,6 @@ module.exports = async ({fix, batch}) => { // Check to see if the length of the documents array requires us to // process it. if (documents.length > batch) { - // Process this batch. let batchOperations = await processBatch(Model, documents); @@ -147,7 +143,6 @@ module.exports = async ({fix, batch}) => { // Check to see if there are any documents left over. if (documents.length > 0) { - // Process this batch. let batchOperations = await processBatch(Model, documents); @@ -157,24 +152,43 @@ module.exports = async ({fix, batch}) => { const OPERATIONS_LENGTH = operations.length; - console.log(`action_counts.js: ${OPERATIONS_LENGTH} ${Model.collection.name} need their action counts fixed.`); + console.log( + `action_counts.js: ${OPERATIONS_LENGTH} ${ + Model.collection.name + } need their action counts fixed.` + ); // If fix was enabled, execute the batch writes. if (OPERATIONS_LENGTH > 0) { if (fix) { - debug(`action_counts.js: fixing ${OPERATIONS_LENGTH} ${Model.collection.name}...`); + debug( + `action_counts.js: fixing ${OPERATIONS_LENGTH} ${ + Model.collection.name + }...` + ); while (operations.length) { - let result = await Model.collection.bulkWrite(operations.splice(0, batch)); + let result = await Model.collection.bulkWrite( + operations.splice(0, batch) + ); - debug(`action_counts.js: fixed batch of ${result.modifiedCount} ${Model.collection.name}.`); + debug( + `action_counts.js: fixed batch of ${result.modifiedCount} ${ + Model.collection.name + }.` + ); } - console.log(`action_counts.js: applied all ${OPERATIONS_LENGTH} fixes to ${Model.collection.name}.`); + console.log( + `action_counts.js: applied all ${OPERATIONS_LENGTH} fixes to ${ + Model.collection.name + }.` + ); } else { - console.warn('Skipping fixing, --fix was not enabled, pass --fix to fix these errors'); + console.warn( + 'Skipping fixing, --fix was not enabled, pass --fix to fix these errors' + ); } } } }; - diff --git a/bin/verifications/database/comment_replies.js b/bin/verifications/database/comment_replies.js index 03a3a4ac6..309023c75 100644 --- a/bin/verifications/database/comment_replies.js +++ b/bin/verifications/database/comment_replies.js @@ -1,15 +1,15 @@ const CommentModel = require('../../../models/comment'); -const {singleJoinBy} = require('../../../graph/loaders/util'); +const { singleJoinBy } = require('../../../graph/loaders/util'); const debug = require('debug')('talk:cli:verify'); -const getBatch = async (limit, offset) => CommentModel - .find({}) - .select({'id': 1, 'action_counts': 1, 'reply_count': 1}) - .limit(limit) - .skip(offset) - .sort('created_at'); +const getBatch = async (limit, offset) => + CommentModel.find({}) + .select({ id: 1, action_counts: 1, reply_count: 1 }) + .limit(limit) + .skip(offset) + .sort('created_at'); -module.exports = async ({fix, limit, batch}) => { +module.exports = async ({ fix, limit, batch }) => { let operations = []; // Count how many comments there are to process. @@ -23,35 +23,33 @@ module.exports = async ({fix, limit, batch}) => { // Keep processing documents until there are is none left. while (offset < totalCount) { - // Get a batch of comments. comments = await getBatch(batch, offset); - commentIDs = comments.map(({id}) => id); + commentIDs = comments.map(({ id }) => id); // Get their reply counts. - let allReplyCounts = await CommentModel - .aggregate([ - { - $match: { - parent_id: { - $in: commentIDs, - }, - status: { - $in: ['NONE', 'ACCEPTED'] - } - } + let allReplyCounts = await CommentModel.aggregate([ + { + $match: { + parent_id: { + $in: commentIDs, + }, + status: { + $in: ['NONE', 'ACCEPTED'], + }, }, - { - $group: { - _id: '$parent_id', - count: { - $sum: 1 - } - } - } - ]) + }, + { + $group: { + _id: '$parent_id', + count: { + $sum: 1, + }, + }, + }, + ]) .then(singleJoinBy(commentIDs, '_id')) - .then((results) => results.map((result) => result ? result.count : 0)); + .then(results => results.map(result => (result ? result.count : 0))); // Loop over the comments, with their action summaries. for (let i = 0; i < comments.length; i++) { @@ -75,7 +73,7 @@ module.exports = async ({fix, limit, batch}) => { operations.push({ updateOne: { filter: { - id: comment.id + id: comment.id, }, update: { $set: Object.assign({}, ...commentOperations), @@ -88,10 +86,17 @@ module.exports = async ({fix, limit, batch}) => { debug(`Processed batch of ${comments.length} comments.`); if (operations.length >= limit) { - debug(`Queued operations are ${operations.length}, reached limit of ${limit}, not processing any more.`); + debug( + `Queued operations are ${ + operations.length + }, reached limit of ${limit}, not processing any more.` + ); if (operations.length > limit) { - debug(`${operations.length - limit} operations have been truncated to enforce the limit`); + debug( + `${operations.length - + limit} operations have been truncated to enforce the limit` + ); } break; @@ -103,7 +108,10 @@ module.exports = async ({fix, limit, batch}) => { const OPERATIONS_LENGTH = operations.length; if (limit < Infinity && offset + comments.length < totalCount) { - console.log(`Processed ${offset + comments.length}/${totalCount} comments because we reached the update limit of ${limit}.`); + console.log( + `Processed ${offset + + comments.length}/${totalCount} comments because we reached the update limit of ${limit}.` + ); } else { console.log(`Processed all ${totalCount} comments.`); } @@ -124,7 +132,9 @@ module.exports = async ({fix, limit, batch}) => { console.log(`Applied all ${OPERATIONS_LENGTH} fixes.`); } else { - console.warn('Skipping fixing, --fix was not enabled, pass --fix to fix these errors'); + console.warn( + 'Skipping fixing, --fix was not enabled, pass --fix to fix these errors' + ); } } }; diff --git a/bin/verifications/database/index.js b/bin/verifications/database/index.js index 2a599cfac..58a46bd85 100644 --- a/bin/verifications/database/index.js +++ b/bin/verifications/database/index.js @@ -7,7 +7,4 @@ // async ({fix = false, batch = 1000}) => {} // // where their options are derived. -module.exports = [ - require('./comment_replies'), - require('./action_counts'), -]; +module.exports = [require('./comment_replies'), require('./action_counts')]; diff --git a/client/coral-admin/src/AppRouter.js b/client/coral-admin/src/AppRouter.js index 362143d38..76291ac56 100644 --- a/client/coral-admin/src/AppRouter.js +++ b/client/coral-admin/src/AppRouter.js @@ -1,44 +1,44 @@ import React from 'react'; import PropTypes from 'prop-types'; -import {Router, Route, IndexRedirect, IndexRoute} from 'react-router'; +import { Router, Route, IndexRedirect, IndexRoute } from 'react-router'; import Configure from 'routes/Configure'; import Install from 'routes/Install'; import Stories from 'routes/Stories'; import Community from 'routes/Community/containers/Community'; -import {ModerationLayout, Moderation} from 'routes/Moderation'; +import { ModerationLayout, Moderation } from 'routes/Moderation'; import Layout from 'containers/Layout'; const routes = (
- - - - - + + + + + {/* Community Routes */} - - - + + + - - + + - + {/* Moderation Routes */} - + - + - - + + diff --git a/client/coral-admin/src/actions/auth.js b/client/coral-admin/src/actions/auth.js index 422258f50..332d756c0 100644 --- a/client/coral-admin/src/actions/auth.js +++ b/client/coral-admin/src/actions/auth.js @@ -7,26 +7,29 @@ import jwtDecode from 'jwt-decode'; // SIGN IN //============================================================================== -export const handleLogin = (email, password, recaptchaResponse) => (dispatch, _, {rest, client, storage}) => { - dispatch({type: actions.LOGIN_REQUEST}); +export const handleLogin = (email, password, recaptchaResponse) => ( + dispatch, + _, + { rest, client, storage } +) => { + dispatch({ type: actions.LOGIN_REQUEST }); const params = { method: 'POST', body: { email, - password - } + password, + }, }; if (recaptchaResponse) { params.headers = { - 'X-Recaptcha-Response': recaptchaResponse + 'X-Recaptcha-Response': recaptchaResponse, }; } return rest('/auth/local', params) - .then(({user, token}) => { - + .then(({ user, token }) => { if (!user) { if (!bowser.safari && !bowser.ios && storage) { storage.removeItem('token'); @@ -38,19 +41,19 @@ export const handleLogin = (email, password, recaptchaResponse) => (dispatch, _, client.resetWebsocket(); dispatch(checkLoginSuccess(user)); }) - .catch((error) => { + .catch(error => { console.error(error); - const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString(); + const errorMessage = error.translation_key + ? t(`error.${error.translation_key}`) + : error.toString(); if (error.translation_key === 'NOT_AUTHORIZED') { - // invalid credentials dispatch({ type: actions.LOGIN_FAILURE, - message: t('error.email_password') + message: t('error.email_password'), }); - } - else if (error.translation_key === 'LOGIN_MAXIMUM_EXCEEDED') { + } else if (error.translation_key === 'LOGIN_MAXIMUM_EXCEEDED') { dispatch({ type: actions.LOGIN_MAXIMUM_EXCEEDED, message: t(`error.${error.translation_key}`), @@ -69,27 +72,32 @@ export const handleLogin = (email, password, recaptchaResponse) => (dispatch, _, //============================================================================== const forgotPasswordRequest = () => ({ - type: actions.FETCH_FORGOT_PASSWORD_REQUEST + type: actions.FETCH_FORGOT_PASSWORD_REQUEST, }); const forgotPasswordSuccess = () => ({ - type: actions.FETCH_FORGOT_PASSWORD_SUCCESS + type: actions.FETCH_FORGOT_PASSWORD_SUCCESS, }); -const forgotPasswordFailure = (error) => ({ +const forgotPasswordFailure = error => ({ type: actions.FETCH_FORGOT_PASSWORD_FAILURE, error, }); -export const requestPasswordReset = (email) => (dispatch, _, {rest}) => { +export const requestPasswordReset = email => (dispatch, _, { rest }) => { dispatch(forgotPasswordRequest(email)); const redirectUri = location.href; - return rest('/account/password/reset', {method: 'POST', body: {email, loc: redirectUri}}) + return rest('/account/password/reset', { + method: 'POST', + body: { email, loc: redirectUri }, + }) .then(() => dispatch(forgotPasswordSuccess())) - .catch((error) => { + .catch(error => { console.error(error); - const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString(); + const errorMessage = error.translation_key + ? t(`error.${error.translation_key}`) + : error.toString(); dispatch(forgotPasswordFailure(errorMessage)); }); }; @@ -99,24 +107,24 @@ export const requestPasswordReset = (email) => (dispatch, _, {rest}) => { //============================================================================== const checkLoginRequest = () => ({ - type: actions.CHECK_LOGIN_REQUEST + type: actions.CHECK_LOGIN_REQUEST, }); const checkLoginSuccess = (user, isAdmin) => ({ type: actions.CHECK_LOGIN_SUCCESS, user, - isAdmin + isAdmin, }); -const checkLoginFailure = (error) => ({ +const checkLoginFailure = error => ({ type: actions.CHECK_LOGIN_FAILURE, - error + error, }); -export const checkLogin = () => (dispatch, _, {rest, client, storage}) => { +export const checkLogin = () => (dispatch, _, { rest, client, storage }) => { dispatch(checkLoginRequest()); return rest('/auth') - .then(({user}) => { + .then(({ user }) => { if (!user) { if (!bowser.safari && !bowser.ios && storage) { storage.removeItem('token'); @@ -127,9 +135,11 @@ export const checkLogin = () => (dispatch, _, {rest, client, storage}) => { client.resetWebsocket(); dispatch(checkLoginSuccess(user)); }) - .catch((error) => { + .catch(error => { console.error(error); - const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString(); + const errorMessage = error.translation_key + ? t(`error.${error.translation_key}`) + : error.toString(); dispatch(checkLoginFailure(errorMessage)); }); }; @@ -138,8 +148,8 @@ export const checkLogin = () => (dispatch, _, {rest, client, storage}) => { // LOGOUT //============================================================================== -export const logout = () => (dispatch, _, {rest, client, storage}) => { - return rest('/auth', {method: 'DELETE'}).then(() => { +export const logout = () => (dispatch, _, { rest, client, storage }) => { + return rest('/auth', { method: 'DELETE' }).then(() => { if (storage) { storage.removeItem('token'); } @@ -147,7 +157,7 @@ export const logout = () => (dispatch, _, {rest, client, storage}) => { // Reset the websocket. client.resetWebsocket(); - dispatch({type: actions.LOGOUT}); + dispatch({ type: actions.LOGOUT }); }); }; @@ -155,11 +165,10 @@ export const logout = () => (dispatch, _, {rest, client, storage}) => { // AUTH TOKEN //============================================================================== -export const handleAuthToken = (token) => (dispatch, _, {storage}) => { +export const handleAuthToken = token => (dispatch, _, { storage }) => { if (storage) { storage.setItem('exp', jwtDecode(token).exp); storage.setItem('token', token); } - dispatch({type: 'HANDLE_AUTH_TOKEN'}); + dispatch({ type: 'HANDLE_AUTH_TOKEN' }); }; - diff --git a/client/coral-admin/src/actions/banUserDialog.js b/client/coral-admin/src/actions/banUserDialog.js index 8134318cb..aac904a2b 100644 --- a/client/coral-admin/src/actions/banUserDialog.js +++ b/client/coral-admin/src/actions/banUserDialog.js @@ -1,6 +1,19 @@ -import {SHOW_BAN_USER_DIALOG, HIDE_BAN_USER_DIALOG} from '../constants/banUserDialog'; +import { + SHOW_BAN_USER_DIALOG, + HIDE_BAN_USER_DIALOG, +} from '../constants/banUserDialog'; -export const showBanUserDialog = ({userId, username, commentId, commentStatus}) => - ({type: SHOW_BAN_USER_DIALOG, userId, username, commentId, commentStatus}); +export const showBanUserDialog = ({ + userId, + username, + commentId, + commentStatus, +}) => ({ + type: SHOW_BAN_USER_DIALOG, + userId, + username, + commentId, + commentStatus, +}); -export const hideBanUserDialog = () => ({type: HIDE_BAN_USER_DIALOG}); +export const hideBanUserDialog = () => ({ type: HIDE_BAN_USER_DIALOG }); diff --git a/client/coral-admin/src/actions/community.js b/client/coral-admin/src/actions/community.js index 669ff2f95..d2b8f1a3f 100644 --- a/client/coral-admin/src/actions/community.js +++ b/client/coral-admin/src/actions/community.js @@ -10,54 +10,61 @@ import { SHOW_BANUSER_DIALOG, HIDE_BANUSER_DIALOG, SHOW_REJECT_USERNAME_DIALOG, - HIDE_REJECT_USERNAME_DIALOG + HIDE_REJECT_USERNAME_DIALOG, } from '../constants/community'; import t from 'coral-framework/services/i18n'; -export const fetchUsers = (query = {}) => (dispatch, _, {rest}) => { +export const fetchUsers = (query = {}) => (dispatch, _, { rest }) => { dispatch(requestFetchUsers()); rest(`/users?${queryString.stringify(query)}`) - .then(({result, page, count, limit, totalPages}) =>{ + .then(({ result, page, count, limit, totalPages }) => { dispatch({ type: FETCH_USERS_SUCCESS, users: result, page, count, limit, - totalPages + totalPages, }); }) - .catch((error) => { + .catch(error => { console.error(error); - const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString(); - dispatch({type: FETCH_USERS_FAILURE, error: errorMessage}); + const errorMessage = error.translation_key + ? t(`error.${error.translation_key}`) + : error.toString(); + dispatch({ type: FETCH_USERS_FAILURE, error: errorMessage }); }); }; const requestFetchUsers = () => ({ - type: FETCH_USERS_REQUEST + type: FETCH_USERS_REQUEST, }); -export const updateSorting = (sort) => ({ +export const updateSorting = sort => ({ type: SORT_UPDATE, - sort + sort, }); -export const setPage = (page) => ({ +export const setPage = page => ({ type: SET_PAGE, page, }); -export const setSearchValue = (value) => ({ +export const setSearchValue = value => ({ type: SET_SEARCH_VALUE, value, }); // Ban User Dialog -export const showBanUserDialog = (user) => ({type: SHOW_BANUSER_DIALOG, user}); -export const hideBanUserDialog = () => ({type: HIDE_BANUSER_DIALOG}); +export const showBanUserDialog = user => ({ type: SHOW_BANUSER_DIALOG, user }); +export const hideBanUserDialog = () => ({ type: HIDE_BANUSER_DIALOG }); // Reject Username Dialog -export const showRejectUsernameDialog = (user) => ({type: SHOW_REJECT_USERNAME_DIALOG, user}); -export const hideRejectUsernameDialog = () => ({type: HIDE_REJECT_USERNAME_DIALOG}); +export const showRejectUsernameDialog = user => ({ + type: SHOW_REJECT_USERNAME_DIALOG, + user, +}); +export const hideRejectUsernameDialog = () => ({ + type: HIDE_REJECT_USERNAME_DIALOG, +}); diff --git a/client/coral-admin/src/actions/config.js b/client/coral-admin/src/actions/config.js index 4f3528be0..5e2995e85 100644 --- a/client/coral-admin/src/actions/config.js +++ b/client/coral-admin/src/actions/config.js @@ -1,7 +1,7 @@ export const CONFIG_UPDATED = 'CONFIG_UPDATED'; -export const fetchConfig = () => (dispatch) => { +export const fetchConfig = () => dispatch => { let json = document.getElementById('data'); let data = JSON.parse(json.textContent); - dispatch({type: CONFIG_UPDATED, data}); + dispatch({ type: CONFIG_UPDATED, data }); }; diff --git a/client/coral-admin/src/actions/configure.js b/client/coral-admin/src/actions/configure.js index 128de75bc..acc30be1b 100644 --- a/client/coral-admin/src/actions/configure.js +++ b/client/coral-admin/src/actions/configure.js @@ -1,13 +1,13 @@ import * as actions from 'constants/configure'; -export const updatePending = ({updater, errorUpdater}) => { - return {type: actions.UPDATE_PENDING, updater, errorUpdater}; +export const updatePending = ({ updater, errorUpdater }) => { + return { type: actions.UPDATE_PENDING, updater, errorUpdater }; }; export const clearPending = () => { - return {type: actions.CLEAR_PENDING}; + return { type: actions.CLEAR_PENDING }; }; -export const setActiveSection = (section) => { - return {type: actions.SET_ACTIVE_SECTION, section}; +export const setActiveSection = section => { + return { type: actions.SET_ACTIVE_SECTION, section }; }; diff --git a/client/coral-admin/src/actions/install.js b/client/coral-admin/src/actions/install.js index 02d562d65..2f8c8c250 100644 --- a/client/coral-admin/src/actions/install.js +++ b/client/coral-admin/src/actions/install.js @@ -3,17 +3,17 @@ import validate from 'coral-framework/helpers/validate'; import errorMsj from 'coral-framework/helpers/error'; import t from 'coral-framework/services/i18n'; -export const nextStep = () => ({type: actions.NEXT_STEP}); -export const previousStep = () => ({type: actions.PREVIOUS_STEP}); -export const goToStep = (step) => ({type: actions.GO_TO_STEP, step}); +export const nextStep = () => ({ type: actions.NEXT_STEP }); +export const previousStep = () => ({ type: actions.PREVIOUS_STEP }); +export const goToStep = step => ({ type: actions.GO_TO_STEP, step }); -const installRequest = () => ({type: actions.INSTALL_REQUEST}); -const installSuccess = () => ({type: actions.INSTALL_SUCCESS}); -const installFailure = (error) => ({type: actions.INSTALL_FAILURE, error}); +const installRequest = () => ({ type: actions.INSTALL_REQUEST }); +const installSuccess = () => ({ type: actions.INSTALL_SUCCESS }); +const installFailure = error => ({ type: actions.INSTALL_FAILURE, error }); -const addError = (name, error) => ({type: actions.ADD_ERROR, name, error}); -const hasError = (error) => ({type: actions.HAS_ERROR, error}); -const clearErrors = () => ({type: actions.CLEAR_ERRORS}); +const addError = (name, error) => ({ type: actions.ADD_ERROR, name, error }); +const hasError = error => ({ type: actions.HAS_ERROR, error }); +const clearErrors = () => ({ type: actions.CLEAR_ERRORS }); const validation = (formData, dispatch, next) => { if (!(formData != null)) { @@ -21,24 +21,21 @@ const validation = (formData, dispatch, next) => { return; } - const validKeys = Object.keys(formData) - .filter((name) => name !== 'domains'); + const validKeys = Object.keys(formData).filter(name => name !== 'domains'); // Required Validation - const empty = validKeys - .filter((name) => { - const cond = !formData[name].length; + const empty = validKeys.filter(name => { + const cond = !formData[name].length; - if (cond) { + if (cond) { + // Adding Error + dispatch(addError(name, 'This field is required.')); + } else { + dispatch(addError(name, '')); + } - // Adding Error - dispatch(addError(name, 'This field is required.')); - } else { - dispatch(addError(name, '')); - } - - return cond; - }); + return cond; + }); if (empty.length) { dispatch(hasError()); @@ -46,19 +43,17 @@ const validation = (formData, dispatch, next) => { } // RegExp Validation - const validation = validKeys - .filter((name) => { - const cond = !validate[name](formData[name]); - if (cond) { - + const validation = validKeys.filter(name => { + const cond = !validate[name](formData[name]); + if (cond) { // Adding Error - dispatch(addError(name, errorMsj[name])); - } else { - dispatch(addError(name, '')); - } + dispatch(addError(name, errorMsj[name])); + } else { + dispatch(addError(name, '')); + } - return cond; - }); + return cond; + }); if (validation.length) { dispatch(hasError()); @@ -67,20 +62,21 @@ const validation = (formData, dispatch, next) => { // Confirm Validation const prefixLength = 'confirm'.length; - const confirm = validKeys - .filter((name) => { - if (!name.startsWith('confirm')) { - return false; - } + const confirm = validKeys.filter(name => { + if (!name.startsWith('confirm')) { + return false; + } - // Check that 'confirmX' equals 'X'. - const other = name.substr(prefixLength, 1).toLowerCase() + name.substr(prefixLength + 1); - const cond = formData[other] !== formData[name]; - if (cond) { - dispatch(addError(name, errorMsj[name])); - } - return cond; - }); + // Check that 'confirmX' equals 'X'. + const other = + name.substr(prefixLength, 1).toLowerCase() + + name.substr(prefixLength + 1); + const cond = formData[other] !== formData[name]; + if (cond) { + dispatch(addError(name, errorMsj[name])); + } + return cond; + }); if (confirm.length) { dispatch(hasError()); @@ -105,41 +101,62 @@ export const submitUser = () => (dispatch, getState) => { }); }; -export const finishInstall = () => (dispatch, getState, {rest}) => { +export const finishInstall = () => (dispatch, getState, { rest }) => { const data = getState().install.data; dispatch(installRequest()); - return rest('/setup', {method: 'POST', body: data}) + return rest('/setup', { method: 'POST', body: data }) .then(() => { dispatch(installSuccess()); dispatch(nextStep()); }) - .catch((error) => { + .catch(error => { console.error(error); - const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString(); + const errorMessage = error.translation_key + ? t(`error.${error.translation_key}`) + : error.toString(); dispatch(installFailure(errorMessage)); }); }; -export const updateSettingsFormData = (name, value) => ({type: actions.UPDATE_FORMDATA_SETTINGS, name, value}); -export const updateUserFormData = (name, value) => ({type: actions.UPDATE_FORMDATA_USER, name, value}); -export const updatePermittedDomains = (value) => ({type: actions.UPDATE_PERMITTED_DOMAINS_SETTINGS, value}); +export const updateSettingsFormData = (name, value) => ({ + type: actions.UPDATE_FORMDATA_SETTINGS, + name, + value, +}); +export const updateUserFormData = (name, value) => ({ + type: actions.UPDATE_FORMDATA_USER, + name, + value, +}); +export const updatePermittedDomains = value => ({ + type: actions.UPDATE_PERMITTED_DOMAINS_SETTINGS, + value, +}); -const checkInstallRequest = () => ({type: actions.CHECK_INSTALL_REQUEST}); -const checkInstallSuccess = (installed) => ({type: actions.CHECK_INSTALL_SUCCESS, installed}); -const checkInstallFailure = (error) => ({type: actions.CHECK_INSTALL_FAILURE, error}); +const checkInstallRequest = () => ({ type: actions.CHECK_INSTALL_REQUEST }); +const checkInstallSuccess = installed => ({ + type: actions.CHECK_INSTALL_SUCCESS, + installed, +}); +const checkInstallFailure = error => ({ + type: actions.CHECK_INSTALL_FAILURE, + error, +}); -export const checkInstall = (next) => async (dispatch, _, {rest}) => { +export const checkInstall = next => async (dispatch, _, { rest }) => { dispatch(checkInstallRequest()); try { - const {installed} = await rest('/setup'); + const { installed } = await rest('/setup'); dispatch(checkInstallSuccess(installed)); if (installed) { next(); } } catch (error) { console.error(error); - const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString(); + const errorMessage = error.translation_key + ? t(`error.${error.translation_key}`) + : error.toString(); dispatch(checkInstallFailure(errorMessage)); } }; diff --git a/client/coral-admin/src/actions/moderation.js b/client/coral-admin/src/actions/moderation.js index 18e31091f..cd09b04b2 100644 --- a/client/coral-admin/src/actions/moderation.js +++ b/client/coral-admin/src/actions/moderation.js @@ -1,41 +1,40 @@ import * as actions from 'constants/moderation'; -export const toggleModal = (open) => ({type: actions.TOGGLE_MODAL, open}); -export const singleView = () => ({type: actions.SINGLE_VIEW}); +export const toggleModal = open => ({ type: actions.TOGGLE_MODAL, open }); +export const singleView = () => ({ type: actions.SINGLE_VIEW }); // hide shortcuts note -export const hideShortcutsNote = () => (dispatch, _, {storage}) => { +export const hideShortcutsNote = () => (dispatch, _, { storage }) => { try { if (storage) { storage.setItem('coral:shortcutsNote', 'hide'); } } catch (e) { - // above will fail in Safari private mode } - dispatch({type: actions.HIDE_SHORTCUTS_NOTE}); + dispatch({ type: actions.HIDE_SHORTCUTS_NOTE }); }; -export const setSortOrder = (order) => ({ +export const setSortOrder = order => ({ type: actions.SET_SORT_ORDER, - order + order, }); -export const toggleStorySearch = (active) => ({ - type: active ? actions.SHOW_STORY_SEARCH : actions.HIDE_STORY_SEARCH +export const toggleStorySearch = active => ({ + type: active ? actions.SHOW_STORY_SEARCH : actions.HIDE_STORY_SEARCH, }); -export const storySearchChange = (value) => ({ +export const storySearchChange = value => ({ type: actions.STORY_SEARCH_CHANGE_VALUE, - value + value, }); export const clearState = () => ({ - type: actions.MODERATION_CLEAR_STATE + type: actions.MODERATION_CLEAR_STATE, }); -export const selectCommentId = (id) => ({ +export const selectCommentId = id => ({ type: actions.MODERATION_SELECT_COMMENT, id, }); diff --git a/client/coral-admin/src/actions/stories.js b/client/coral-admin/src/actions/stories.js index 2524b0c3c..ab610ae20 100644 --- a/client/coral-admin/src/actions/stories.js +++ b/client/coral-admin/src/actions/stories.js @@ -10,7 +10,7 @@ import { UPDATE_ASSET_STATE_REQUEST, UPDATE_ASSET_STATE_SUCCESS, UPDATE_ASSET_STATE_FAILURE, - UPDATE_ASSETS + UPDATE_ASSETS, } from '../constants/stories'; import t from 'coral-framework/services/i18n'; @@ -21,53 +21,58 @@ import t from 'coral-framework/services/i18n'; // Fetch a page of assets // Get comments to fill each of the three lists on the mod queue -export const fetchAssets = (query = {}) => (dispatch, _, {rest}) => { - dispatch({type: FETCH_ASSETS_REQUEST}); +export const fetchAssets = (query = {}) => (dispatch, _, { rest }) => { + dispatch({ type: FETCH_ASSETS_REQUEST }); return rest(`/assets?${queryString.stringify(query)}`) - .then(({result, page, count, limit, totalPages}) => - dispatch({type: FETCH_ASSETS_SUCCESS, + .then(({ result, page, count, limit, totalPages }) => + dispatch({ + type: FETCH_ASSETS_SUCCESS, assets: result, page, count, limit, totalPages, - })) - .catch((error) => { + }) + ) + .catch(error => { console.error(error); - const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString(); - dispatch({type: FETCH_ASSETS_FAILURE, error: errorMessage}); + const errorMessage = error.translation_key + ? t(`error.${error.translation_key}`) + : error.toString(); + dispatch({ type: FETCH_ASSETS_FAILURE, error: errorMessage }); }); }; // Update an asset state // Get comments to fill each of the three lists on the mod queue -export const updateAssetState = (id, closedAt) => (dispatch, _, {rest}) => { - dispatch({type: UPDATE_ASSET_STATE_REQUEST, id, closedAt}); - return rest(`/assets/${id}/status`, {method: 'PUT', body: {closedAt}}) - .then(() => dispatch({type: UPDATE_ASSET_STATE_SUCCESS})) - .catch((error) => { +export const updateAssetState = (id, closedAt) => (dispatch, _, { rest }) => { + dispatch({ type: UPDATE_ASSET_STATE_REQUEST, id, closedAt }); + return rest(`/assets/${id}/status`, { method: 'PUT', body: { closedAt } }) + .then(() => dispatch({ type: UPDATE_ASSET_STATE_SUCCESS })) + .catch(error => { console.error(error); - const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString(); - dispatch({type: UPDATE_ASSET_STATE_FAILURE, error: errorMessage}); + const errorMessage = error.translation_key + ? t(`error.${error.translation_key}`) + : error.toString(); + dispatch({ type: UPDATE_ASSET_STATE_FAILURE, error: errorMessage }); }); }; -export const updateAssets = (assets) => (dispatch) => { - dispatch({type: UPDATE_ASSETS, assets}); +export const updateAssets = assets => dispatch => { + dispatch({ type: UPDATE_ASSETS, assets }); }; -export const setPage = (page) => ({ +export const setPage = page => ({ type: SET_PAGE, page, }); -export const setSearchValue = (value) => ({ +export const setSearchValue = value => ({ type: SET_SEARCH_VALUE, value, }); -export const setCriteria = (criteria) => ({ +export const setCriteria = criteria => ({ type: SET_CRITERIA, criteria, }); - diff --git a/client/coral-admin/src/actions/suspendUserDialog.js b/client/coral-admin/src/actions/suspendUserDialog.js index 06913147d..f81b76dd3 100644 --- a/client/coral-admin/src/actions/suspendUserDialog.js +++ b/client/coral-admin/src/actions/suspendUserDialog.js @@ -1,7 +1,19 @@ -import {SHOW_SUSPEND_USER_DIALOG, HIDE_SUSPEND_USER_DIALOG} from '../constants/suspendUserDialog.js'; +import { + SHOW_SUSPEND_USER_DIALOG, + HIDE_SUSPEND_USER_DIALOG, +} from '../constants/suspendUserDialog.js'; -export const showSuspendUserDialog = ({userId, username, commentId, commentStatus}) => - ({type: SHOW_SUSPEND_USER_DIALOG, userId, username, commentId, commentStatus}); - -export const hideSuspendUserDialog = () => ({type: HIDE_SUSPEND_USER_DIALOG}); +export const showSuspendUserDialog = ({ + userId, + username, + commentId, + commentStatus, +}) => ({ + type: SHOW_SUSPEND_USER_DIALOG, + userId, + username, + commentId, + commentStatus, +}); +export const hideSuspendUserDialog = () => ({ type: HIDE_SUSPEND_USER_DIALOG }); diff --git a/client/coral-admin/src/actions/userDetail.js b/client/coral-admin/src/actions/userDetail.js index 1d0a20def..f34f8e7a8 100644 --- a/client/coral-admin/src/actions/userDetail.js +++ b/client/coral-admin/src/actions/userDetail.js @@ -1,28 +1,37 @@ import * as actions from 'constants/userDetail'; -export const viewUserDetail = (userId) => ({type: actions.VIEW_USER_DETAIL, userId}); -export const hideUserDetail = () => ({type: actions.HIDE_USER_DETAIL}); +export const viewUserDetail = userId => ({ + type: actions.VIEW_USER_DETAIL, + userId, +}); +export const hideUserDetail = () => ({ type: actions.HIDE_USER_DETAIL }); -export const changeTab = (tab) => { +export const changeTab = tab => { let statuses = null; if (tab === 'rejected') { statuses = ['REJECTED']; } - return {type: actions.CHANGE_TAB_USER_DETAIL, tab, statuses}; + return { type: actions.CHANGE_TAB_USER_DETAIL, tab, statuses }; }; -export const clearUserDetailSelections = () => ({type: actions.CLEAR_USER_DETAIL_SELECTIONS}); +export const clearUserDetailSelections = () => ({ + type: actions.CLEAR_USER_DETAIL_SELECTIONS, +}); export const toggleSelectCommentInUserDetail = (id, active) => { return { - type: active ? actions.SELECT_USER_DETAIL_COMMENT : actions.UNSELECT_USER_DETAIL_COMMENT, - id + type: active + ? actions.SELECT_USER_DETAIL_COMMENT + : actions.UNSELECT_USER_DETAIL_COMMENT, + id, }; }; export const toggleSelectAllCommentInUserDetail = (ids, active) => { return { - type: active ? actions.SELECT_ALL_USER_DETAIL_COMMENT : actions.CLEAR_USER_DETAIL_SELECTIONS, - ids + type: active + ? actions.SELECT_ALL_USER_DETAIL_COMMENT + : actions.CLEAR_USER_DETAIL_SELECTIONS, + ids, }; }; diff --git a/client/coral-admin/src/actions/users.js b/client/coral-admin/src/actions/users.js index 994a79a79..c538d7384 100644 --- a/client/coral-admin/src/actions/users.js +++ b/client/coral-admin/src/actions/users.js @@ -6,38 +6,56 @@ import t from 'coral-framework/services/i18n'; */ // change status of a user export const userStatusUpdate = (status, userId, commentId) => { - return (dispatch, _, {rest}) => { - dispatch({type: userTypes.UPDATE_STATUS_REQUEST}); - return rest(`/users/${userId}/status`, {method: 'POST', body: {status: status, comment_id: commentId}}) - .then((res) => dispatch({type: userTypes.UPDATE_STATUS_SUCCESS, res})) - .catch((error) => { + return (dispatch, _, { rest }) => { + dispatch({ type: userTypes.UPDATE_STATUS_REQUEST }); + return rest(`/users/${userId}/status`, { + method: 'POST', + body: { status: status, comment_id: commentId }, + }) + .then(res => dispatch({ type: userTypes.UPDATE_STATUS_SUCCESS, res })) + .catch(error => { console.error(error); - const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString(); - dispatch({type: userTypes.UPDATE_STATUS_FAILURE, error: errorMessage}); + const errorMessage = error.translation_key + ? t(`error.${error.translation_key}`) + : error.toString(); + dispatch({ + type: userTypes.UPDATE_STATUS_FAILURE, + error: errorMessage, + }); }); }; }; // change status of a user export const sendNotificationEmail = (userId, subject, body) => { - return (dispatch, _, {rest}) => { - return rest(`/users/${userId}/email`, {method: 'POST', body: {subject, body}}) - .catch((error) => { - console.error(error); - const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString(); - dispatch({type: userTypes.USER_EMAIL_FAILURE, error: errorMessage}); - }); + return (dispatch, _, { rest }) => { + return rest(`/users/${userId}/email`, { + method: 'POST', + body: { subject, body }, + }).catch(error => { + console.error(error); + const errorMessage = error.translation_key + ? t(`error.${error.translation_key}`) + : error.toString(); + dispatch({ type: userTypes.USER_EMAIL_FAILURE, error: errorMessage }); + }); }; }; // let a user edit their username -export const enableUsernameEdit = (userId) => { - return (dispatch, _, {rest}) => { - return rest(`/users/${userId}/username-enable`, {method: 'POST'}) - .catch((error) => { +export const enableUsernameEdit = userId => { + return (dispatch, _, { rest }) => { + return rest(`/users/${userId}/username-enable`, { method: 'POST' }).catch( + error => { console.error(error); - const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString(); - dispatch({type: userTypes.USERNAME_ENABLE_FAILURE, error: errorMessage}); - }); + const errorMessage = error.translation_key + ? t(`error.${error.translation_key}`) + : error.toString(); + dispatch({ + type: userTypes.USERNAME_ENABLE_FAILURE, + error: errorMessage, + }); + } + ); }; }; diff --git a/client/coral-admin/src/components/AccountHistory.js b/client/coral-admin/src/components/AccountHistory.js index ec3bbca2f..04df552be 100644 --- a/client/coral-admin/src/components/AccountHistory.js +++ b/client/coral-admin/src/components/AccountHistory.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import {murmur3} from 'murmurhash-js'; +import { murmur3 } from 'murmurhash-js'; import styles from './AccountHistory.css'; import cn from 'classnames'; import flatten from 'lodash/flatten'; @@ -8,21 +8,27 @@ import orderBy from 'lodash/orderBy'; import moment from 'moment'; const buildUserHistory = (userState = {}) => { - return orderBy(flatten(Object.keys(userState.status) - .filter((k) => k !== '__typename') - .map((k) => userState.status[k].history)), 'created_at', 'desc'); + return orderBy( + flatten( + Object.keys(userState.status) + .filter(k => k !== '__typename') + .map(k => userState.status[k].history) + ), + 'created_at', + 'desc' + ); }; const buildActionResponse = (typename, until, status) => { switch (typename) { - case 'UsernameStatusHistory': - return `Username ${status}`; - case 'BannedStatusHistory': - return status ? 'User banned' : 'Ban removed'; - case 'SuspensionStatusHistory': - return until ? 'Account Suspended' : 'Suspension removed' ; - default: - return '-'; + case 'UsernameStatusHistory': + return `Username ${status}`; + case 'BannedStatusHistory': + return status ? 'User banned' : 'Ban removed'; + case 'SuspensionStatusHistory': + return until ? 'Account Suspended' : 'Suspension removed'; + default: + return '-'; } }; @@ -35,31 +41,55 @@ const getModerationValue = (userId, assignedBy = {}) => { class AccountHistory extends React.Component { render() { - const {user} = this.props; + const { user } = this.props; const userHistory = buildUserHistory(user.state); return (
-
+
Date
Action
Moderation
- { - userHistory.map(({__typename, created_at, assigned_by, until, status}) => ( -
-
+ {userHistory.map( + ({ __typename, created_at, assigned_by, until, status }) => ( +
+
{moment(new Date(created_at)).format('MMM DD, YYYY')}
-
+
{buildActionResponse(__typename, until, status)}
-
+
{getModerationValue(user.id, assigned_by)}
- )) - } + ) + )}
); diff --git a/client/coral-admin/src/components/ActionsMenu.js b/client/coral-admin/src/components/ActionsMenu.js index ef4e46573..fed143859 100644 --- a/client/coral-admin/src/components/ActionsMenu.js +++ b/client/coral-admin/src/components/ActionsMenu.js @@ -1,9 +1,9 @@ import React from 'react'; import PropTypes from 'prop-types'; -import {Button, Icon} from 'coral-ui'; -import {Menu} from 'react-mdl'; +import { Button, Icon } from 'coral-ui'; +import { Menu } from 'react-mdl'; import cn from 'classnames'; -import {findDOMNode} from 'react-dom'; +import { findDOMNode } from 'react-dom'; import styles from './ActionsMenu.css'; import t from 'coral-framework/services/i18n'; @@ -13,36 +13,41 @@ let count = 0; class ActionsMenu extends React.Component { id = `actions-dropdown-${count++}`; menu = null; - state = {open: false}; + state = { open: false }; timeout = null; componentWillUnmount() { clearTimeout(this.timeout); } - handleRef = (ref) => { + handleRef = ref => { this.menu = ref ? findDOMNode(ref).parentNode : null; - } + }; syncOpenState = () => { clearTimeout(this.timeout); this.timeout = setTimeout(() => { - this.setState({open: this.menu.className.indexOf('is-visible') >= 0}); + this.setState({ open: this.menu.className.indexOf('is-visible') >= 0 }); }, 150); }; render() { - const {className = '', buttonClassNames = '', label = ''} = this.props; + const { className = '', buttonClassNames = '', label = '' } = this.props; return ( -
+
+ onClick={this.handleSignIn} + > + Sign In +

- Forgot your password? { - e.preventDefault(); - this.setState({requestPassword: true}); - }}>Request a new one. + Forgot your password?{' '} + { + e.preventDefault(); + this.setState({ requestPassword: true }); + }} + > + Request a new one. +

- { - loginMaxExceeded && + {loginMaxExceeded && ( - } + verifyCallback={this.onRecaptchaVerify} + /> + )} ); - const requestPasswordForm = ( - this.props.passwordRequestSuccess - ?

{ + const requestPasswordForm = this.props.passwordRequestSuccess ? ( +

{ location.href = location.href; - }}> - {this.props.passwordRequestSuccess} Sign in - -

- :
- this.setState({email: e.target.value})} /> - - + }} + > + {this.props.passwordRequestSuccess}{' '} + + Sign in + + +

+ ) : ( +
+ this.setState({ email: e.target.value })} + /> + + ); return (

Team sign in

-

Sign in to interact with your community.

- { this.state.requestPassword ? requestPasswordForm : signInForm } +

+ Sign in to interact with your community. +

+ {this.state.requestPassword ? requestPasswordForm : signInForm}
); diff --git a/client/coral-admin/src/components/App.js b/client/coral-admin/src/components/App.js index 1a15c72c3..52535f91a 100644 --- a/client/coral-admin/src/components/App.js +++ b/client/coral-admin/src/components/App.js @@ -5,7 +5,7 @@ import 'material-design-lite'; import AppRouter from '../AppRouter'; export default class App extends React.Component { - render () { + render() { return (
diff --git a/client/coral-admin/src/components/ApproveButton.js b/client/coral-admin/src/components/ApproveButton.js index 8ab455510..97a674221 100644 --- a/client/coral-admin/src/components/ApproveButton.js +++ b/client/coral-admin/src/components/ApproveButton.js @@ -3,15 +3,19 @@ import PropTypes from 'prop-types'; import cn from 'classnames'; import styles from './ApproveButton.css'; -import {Icon} from 'coral-ui'; +import { Icon } from 'coral-ui'; import t from 'coral-framework/services/i18n'; -const ApproveButton = ({active, minimal, onClick, className}) => { +const ApproveButton = ({ active, minimal, onClick, className }) => { const text = active ? t('modqueue.approved') : t('modqueue.approve'); return ( -
@@ -73,22 +63,19 @@ class BanUserDialog extends React.Component { } renderStep1() { - const { - onCancel, - onPerform, - } = this.props; - const {message} = this.state; + const { onCancel, onPerform } = this.props; + const { message } = this.state; return (
-

- {t('bandialog.notify_ban_headline')} -

+

{t('bandialog.notify_ban_headline')}

{t('bandialog.notify_ban_description')}

- {t('bandialog.write_a_message')} + + {t('bandialog.write_a_message')} +