From 61195030577ec927c9cb490f22d28a71f806f695 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 23 Nov 2017 10:53:20 -0700 Subject: [PATCH] added support for user assigned_by fields to get resolved --- graph/mutators/user.js | 4 +- graph/resolvers/banned_status_history.js | 7 ++++ graph/resolvers/comment_status_history.js | 12 ++---- graph/resolvers/index.js | 32 ++++++++------- graph/resolvers/suspension_status_history.js | 7 ++++ graph/resolvers/username_status_history.js | 7 ++++ graph/resolvers/util.js | 41 ++++++++++++++++++- services/users.js | 12 +++--- test/server/graph/mutations/changeUsername.js | 32 +++++++++++++-- 9 files changed, 117 insertions(+), 37 deletions(-) create mode 100644 graph/resolvers/banned_status_history.js create mode 100644 graph/resolvers/suspension_status_history.js create mode 100644 graph/resolvers/username_status_history.js diff --git a/graph/mutators/user.js b/graph/mutators/user.js index e84dea302..3a08535d2 100644 --- a/graph/mutators/user.js +++ b/graph/mutators/user.js @@ -40,11 +40,11 @@ const stopIgnoringUser = ({user}, userToStopIgnoring) => { }; const changeUsername = async (ctx, id, username) => { - return UsersService.changeUsername(id, username); + return UsersService.changeUsername(id, username, ctx.user.id); }; const setUsername = async (ctx, id, username) => { - return UsersService.setUsername(id, username); + return UsersService.setUsername(id, username, ctx.user.id); }; module.exports = (ctx) => { diff --git a/graph/resolvers/banned_status_history.js b/graph/resolvers/banned_status_history.js new file mode 100644 index 000000000..bc05b5434 --- /dev/null +++ b/graph/resolvers/banned_status_history.js @@ -0,0 +1,7 @@ +const {decorateUserField} = require('./util'); + +const BannedStatusHistory = {}; + +decorateUserField(BannedStatusHistory, 'assigned_by'); + +module.exports = BannedStatusHistory; diff --git a/graph/resolvers/comment_status_history.js b/graph/resolvers/comment_status_history.js index d51e71752..41aa01466 100644 --- a/graph/resolvers/comment_status_history.js +++ b/graph/resolvers/comment_status_history.js @@ -1,13 +1,7 @@ -const {SEARCH_OTHER_USERS} = require('../../perms/constants'); +const {decorateUserField} = require('./util'); -const CommentStatusHistory = { - assigned_by({assigned_by}, _, {user, loaders: {Users}}) { - if (!user || !user.can(SEARCH_OTHER_USERS) || assigned_by == null) { - return null; - } +const CommentStatusHistory = {}; - return Users.getByID.load(assigned_by); - } -}; +decorateUserField(CommentStatusHistory, 'assigned_by'); module.exports = CommentStatusHistory; diff --git a/graph/resolvers/index.js b/graph/resolvers/index.js index 8296d41d5..5315074e2 100644 --- a/graph/resolvers/index.js +++ b/graph/resolvers/index.js @@ -1,56 +1,60 @@ const _ = require('lodash'); const debug = require('debug')('talk:graph:resolvers'); -const ActionSummary = require('./action_summary'); const Action = require('./action'); -const AssetActionSummary = require('./asset_action_summary'); +const ActionSummary = require('./action_summary'); const Asset = require('./asset'); -const CommentStatusHistory = require('./comment_status_history'); +const AssetActionSummary = require('./asset_action_summary'); +const BannedStatusHistory = require('./banned_status_history'); const Comment = require('./comment'); +const CommentStatusHistory = require('./comment_status_history'); const Cursor = require('./cursor'); const Date = require('./date'); -const FlagActionSummary = require('./flag_action_summary'); -const FlagAction = require('./flag_action'); const DontAgreeAction = require('./dont_agree_action'); const DontAgreeActionSummary = require('./dont_agree_action_summary'); +const FlagAction = require('./flag_action'); +const FlagActionSummary = require('./flag_action_summary'); const GenericUserError = require('./generic_user_error'); const RootMutation = require('./root_mutation'); const RootQuery = require('./root_query'); const Settings = require('./settings'); const Subscription = require('./subscription'); -const TagLink = require('./tag_link'); const Tag = require('./tag'); -const UserError = require('./user_error'); +const TagLink = require('./tag_link'); const User = require('./user'); +const UserError = require('./user_error'); const UserState = require('./user_state'); +const UsernameStatusHistory = require('./username_status_history'); const ValidationUserError = require('./validation_user_error'); const plugins = require('../../services/plugins'); // Provide the core resolvers. let resolvers = { - ActionSummary, Action, - AssetActionSummary, + ActionSummary, Asset, - CommentStatusHistory, + AssetActionSummary, + BannedStatusHistory, Comment, + CommentStatusHistory, Cursor, Date, - FlagActionSummary, - FlagAction, DontAgreeAction, DontAgreeActionSummary, + FlagAction, + FlagActionSummary, GenericUserError, RootMutation, RootQuery, Settings, Subscription, - TagLink, Tag, - UserError, + TagLink, User, + UserError, UserState, + UsernameStatusHistory, ValidationUserError, }; diff --git a/graph/resolvers/suspension_status_history.js b/graph/resolvers/suspension_status_history.js new file mode 100644 index 000000000..c826bcc67 --- /dev/null +++ b/graph/resolvers/suspension_status_history.js @@ -0,0 +1,7 @@ +const {decorateUserField} = require('./util'); + +const SuspensionStatusHistory = {}; + +decorateUserField(SuspensionStatusHistory, 'assigned_by'); + +module.exports = SuspensionStatusHistory; diff --git a/graph/resolvers/username_status_history.js b/graph/resolvers/username_status_history.js new file mode 100644 index 000000000..84b53cf97 --- /dev/null +++ b/graph/resolvers/username_status_history.js @@ -0,0 +1,7 @@ +const {decorateUserField} = require('./util'); + +const UsernameStatusHistory = {}; + +decorateUserField(UsernameStatusHistory, 'assigned_by'); + +module.exports = UsernameStatusHistory; diff --git a/graph/resolvers/util.js b/graph/resolvers/util.js index cf3162679..47ed55643 100644 --- a/graph/resolvers/util.js +++ b/graph/resolvers/util.js @@ -1,4 +1,8 @@ -const {ADD_COMMENT_TAG} = require('../../perms/constants'); +const { + ADD_COMMENT_TAG, + SEARCH_OTHER_USERS, +} = require('../../perms/constants'); +const property = require('lodash/property'); /** * Decorates the typeResolver with the tags field. @@ -22,7 +26,7 @@ const decorateWithTags = (typeResolver) => { */ const decorateWithPermissionCheck = (typeResolver, protect) => { for (const [field, permissions] of Object.entries(protect)) { - let fieldResolver = (obj) => obj[field]; + let fieldResolver = property(field); if (field in typeResolver) { fieldResolver = typeResolver[field]; } @@ -37,7 +41,40 @@ const decorateWithPermissionCheck = (typeResolver, protect) => { } }; +/** + * decorateUserField will decorate the user field accesses with correct + * permission checks. + * + * @param {Object} typeResolver the type resolver + * @param {String} field the field to decorate + */ +const decorateUserField = (typeResolver, field) => { + + // The default resolver for the user decorator is loading the user by id. + let fieldResolver = (obj, args, ctx) => + ctx.loaders.Users.getByID.get(obj[field]); + + // The resolver can be overridden however. This decorator will simply wrap the + // field with a permission check. + if (field in typeResolver) { + fieldResolver = typeResolver[field]; + } + + typeResolver[field] = (obj, args, ctx, info) => { + if ( + !ctx.user || + obj[field] === null || + (ctx.user.id !== obj[field] && !ctx.user.can(SEARCH_OTHER_USERS)) + ) { + return null; + } + + return fieldResolver(obj, args, ctx, info); + }; +}; + module.exports = { + decorateUserField, decorateWithTags, decorateWithPermissionCheck, }; diff --git a/services/users.js b/services/users.js index faeb6b667..a81714b84 100644 --- a/services/users.js +++ b/services/users.js @@ -209,7 +209,7 @@ class UsersService { return user; } - static async _setUsername(id, username, fromStatus, toStatus, resetAllowed = false) { + static async _setUsername(id, username, fromStatus, toStatus, assignedBy, resetAllowed = false) { try { const query = { id, @@ -228,7 +228,7 @@ class UsersService { $push: { 'status.username.history': { status: toStatus, - assigned_by: id, + assigned_by: assignedBy, created_at: Date.now() } } @@ -265,12 +265,12 @@ class UsersService { } } - static async setUsername(id, username) { - return UsersService._setUsername(id, username, 'UNSET', 'SET', true); + static async setUsername(id, username, assignedBy) { + return UsersService._setUsername(id, username, 'UNSET', 'SET', assignedBy, true); } - static async changeUsername(id, username) { - return UsersService._setUsername(id, username, 'REJECTED', 'CHANGED'); + static async changeUsername(id, username, assignedBy) { + return UsersService._setUsername(id, username, 'REJECTED', 'CHANGED', assignedBy); } /** diff --git a/test/server/graph/mutations/changeUsername.js b/test/server/graph/mutations/changeUsername.js index cacce9cc7..601080c60 100644 --- a/test/server/graph/mutations/changeUsername.js +++ b/test/server/graph/mutations/changeUsername.js @@ -26,6 +26,24 @@ describe('graph.mutations.changeUsername', () => { } } } + + query User { + me { + state { + status { + username { + history { + status + assigned_by { + username + } + created_at + } + } + } + } + } + } `; [ @@ -44,7 +62,7 @@ describe('graph.mutations.changeUsername', () => { let res = await graphql(schema, changeUsernameMutation, {}, ctx, { user_id: user.id, username, - }); + }, 'ChangeUsername'); if (res.errors && res.errors.length > 0) { console.error(res.errors); @@ -65,7 +83,7 @@ describe('graph.mutations.changeUsername', () => { res = await graphql(schema, changeUsernameMutation, {}, ctx, { user_id: user.id, username, - }); + }, 'ChangeUsername'); if (res.errors && res.errors.length > 0) { console.error(res.errors); @@ -74,9 +92,15 @@ describe('graph.mutations.changeUsername', () => { expect(res.errors).to.be.undefined; expect(res.data.changeUsername).to.be.null; - user = await UsersService.findById(user.id); + res = await graphql(schema, changeUsernameMutation, {}, ctx, {}, 'User'); - expect(user.status.username.status, 'CHANGED'); + if (res.errors && res.errors.length > 0) { + console.error(res.errors); + } + + expect(res.errors).to.be.undefined; + + expect(res.data.me.state.status.username.status, 'CHANGED'); }); }); });