From 1b7f78b562e42316ed9a9a408c5a059ce833cb01 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 8 Nov 2017 13:40:46 -0700 Subject: [PATCH] user queries --- graph/loaders/users.js | 65 +++++++--- graph/mutators/user.js | 2 + graph/setupFunctions.js | 220 +++++++++++++++----------------- graph/typeDefs.graphql | 4 + perms/constants/subscription.js | 1 + perms/reducers/subscription.js | 1 + 6 files changed, 163 insertions(+), 130 deletions(-) diff --git a/graph/loaders/users.js b/graph/loaders/users.js index dc58fffad..273db00f3 100644 --- a/graph/loaders/users.js +++ b/graph/loaders/users.js @@ -10,6 +10,47 @@ const { const UsersService = require('../../services/users'); const UserModel = require('../../models/user'); +const mergeState = (query, state) => { + const {status} = state; + + if (status) { + const {username, banned, suspended} = status; + + if (typeof username !== 'undefined' && username && username.length > 0) { + query.merge({ + 'status.username.status': { + $in: username + } + }); + } + + if (typeof banned !== 'undefined' && banned !== null) { + query.merge({ + 'status.banned.status': banned + }); + } + + if (typeof suspended !== 'undefined' && suspended !== null) { + if (suspended) { + query.merge({ + 'status.suspension.until': { + $gte: Date.now() + } + }); + } else { + query.merge({ + $or: [ + {'status.suspension.until': null}, + {'status.suspension.until': { + $lt: Date.now() + }} + ] + }); + } + } + } +}; + const genUserByIDs = async (context, ids) => { if (!ids || ids.length === 0) { return []; @@ -31,20 +72,16 @@ const genUserByIDs = async (context, ids) => { * @param {Object} context graph context * @param {Object} query query terms to apply to the users query */ -const getUsersByQuery = async ({user, loaders: {Actions}}, {ids, limit, cursor, statuses, action_type, sortOrder}) => { +const getUsersByQuery = async ({user, loaders: {Actions}}, {ids, limit, cursor, state, action_type, sortOrder}) => { let query = UserModel.find(); - if (action_type || statuses) { + if (action_type || state) { if (!user || !user.can(SEARCH_OTHER_USERS)) { return null; } - if (statuses) { - query = query.where({ - status: { - $in: statuses - } - }); + if (state) { + mergeState(query, state); } else { const userIds = await Actions.getByTypes({action_type, item_type: 'USERS'}); ids = ids ? union(ids, userIds) : userIds; @@ -115,25 +152,21 @@ const getUsersByQuery = async ({user, loaders: {Actions}}, {ids, limit, cursor, * @return {Promise} resolves to the counts of the users from the * query */ -const getCountByQuery = async ({loaders: {Actions}}, {action_type, statuses}) => { +const getCountByQuery = async ({loaders: {Actions}}, {action_type, state}) => { let query = UserModel.find(); if (action_type) { const userIds = await Actions.getByTypes({action_type, item_type: 'USERS'}); - query = query.find({ + query.merge({ id: { $in: userIds } }); } - if (statuses) { - query = query.where({ - status: { - $in: statuses - } - }); + if (state) { + mergeState(query, state); } return UserModel diff --git a/graph/mutators/user.js b/graph/mutators/user.js index fe2631186..2934bf7d7 100644 --- a/graph/mutators/user.js +++ b/graph/mutators/user.js @@ -13,6 +13,8 @@ const setUserUsernameStatus = async (ctx, id, status) => { const user = await UsersService.setUsernameStatus(id, status, ctx.user.id); if (status === 'REJECTED') { ctx.pubsub.publish('usernameRejected', user); + } else if (status === 'APPROVED') { + ctx.pubsub.publish('usernameApproved', user); } }; diff --git a/graph/setupFunctions.js b/graph/setupFunctions.js index 8c32b5bdd..86ae106b9 100644 --- a/graph/setupFunctions.js +++ b/graph/setupFunctions.js @@ -7,131 +7,123 @@ const { SUBSCRIBE_ALL_USER_SUSPENDED, SUBSCRIBE_ALL_USER_BANNED, SUBSCRIBE_ALL_USERNAME_REJECTED, + SUBSCRIBE_ALL_USERNAME_APPROVED, } = require('../perms/constants'); const merge = require('lodash/merge'); const debug = require('debug')('talk:graph:setupFunctions'); const plugins = require('../services/plugins'); +const setupFunctions = { + commentAdded: (options, args, comment, context) => { + + // Only privileged users can subscribe to all assets. + if (!args.asset_id && (!context.user || !context.user.can(SUBSCRIBE_ALL_COMMENT_ADDED))) { + return false; + } + + // If user subscribes for statuses other than NONE and/or ACCEPTED statuses, it needs + // special privileges. + if ( + (!args.statuses || args.statuses.some((status) => !['NONE', 'ACCEPTED'].includes(status))) && + (!context.user || !context.user.can(SUBSCRIBE_ALL_COMMENT_ADDED)) + ) { + return false; + } + + if (args.asset_id && comment.asset_id !== args.asset_id) { + return false; + } + + if (args.statuses && !args.statuses.includes(comment.status)) { + return false; + } + + return true; + }, + commentEdited: (options, args, comment, context) => { + if (!args.asset_id && (!context.user || !context.user.can(SUBSCRIBE_ALL_COMMENT_EDITED))) { + return false; + } + return !args.asset_id || comment.asset_id === args.asset_id; + }, + commentFlagged: (options, args, comment, context) => { + if (!context.user || !context.user.can(SUBSCRIBE_COMMENT_FLAGGED)) { + return false; + } + return !args.asset_id || comment.asset_id === args.asset_id; + }, + commentAccepted: (options, args, comment, context) => { + if (!context.user || !context.user.can(SUBSCRIBE_COMMENT_ACCEPTED)) { + return false; + } + return !args.asset_id || comment.asset_id === args.asset_id; + }, + commentRejected: (options, args) => (comment, context) => { + if (!context.user || !context.user.can(SUBSCRIBE_COMMENT_REJECTED)) { + return false; + } + return !args.asset_id || comment.asset_id === args.asset_id; + }, + userSuspended: (options, args, user, context) => { + if ( + !context.user + || args.user_id !== user.id && !context.user.can(SUBSCRIBE_ALL_USER_SUSPENDED) + ) { + return false; + } + return !args.user_id || user.id === args.user_id; + }, + userBanned: (options, args, user, context) => { + if ( + !context.user + || args.user_id !== user.id && !context.user.can(SUBSCRIBE_ALL_USER_BANNED) + ) { + return false; + } + return !args.user_id || user.id === args.user_id; + }, + usernameRejected: (options, args, user, context) => { + if ( + !context.user + || args.user_id !== user.id && !context.user.can(SUBSCRIBE_ALL_USERNAME_REJECTED) + ) { + return false; + } + return !args.user_id || user.id === args.user_id; + }, + usernameApproved: (options, args, user, context) => { + if ( + !context.user + || args.user_id !== user.id && !context.user.can(SUBSCRIBE_ALL_USERNAME_APPROVED) + ) { + return false; + } + return !args.user_id || user.id === args.user_id; + }, +}; + /** * Plugin support requires that we merge in existing setupFunctions with our new - * plugin based ones. This allows plugins to extend existing setupFunctions as well - * as provide new ones. + * plugin based ones. This allows plugins to extend existing setupFunctions as + * well as provide new ones. We'll remap our internal representation of the + * setupFunctions into the format needed by Apollo. */ -const setupFunctions = plugins.get('server', 'setupFunctions').reduce((acc, {plugin, setupFunctions}) => { +module.exports = plugins.get('server', 'setupFunctions').reduce((acc, {plugin, setupFunctions}) => { debug(`added plugin '${plugin.name}'`); return merge(acc, setupFunctions); -}, { - commentAdded: (options, args) => ({ - commentAdded: { - filter: (comment, context) => { +}, Object.keys(setupFunctions).map((key) => { + const filter = setupFunctions[key]; - // Only privileged users can subscribe to all assets. - if (!args.asset_id && (!context.user || !context.user.can(SUBSCRIBE_ALL_COMMENT_ADDED))) { - return false; - } - - // If user subscribes for statuses other than NONE and/or ACCEPTED statuses, it needs - // special privileges. - if ( - (!args.statuses || args.statuses.some((status) => !['NONE', 'ACCEPTED'].includes(status))) && - (!context.user || !context.user.can(SUBSCRIBE_ALL_COMMENT_ADDED)) - ) { - return false; - } - - if (args.asset_id && comment.asset_id !== args.asset_id) { - return false; - } - - if (args.statuses && !args.statuses.includes(comment.status)) { - return false; - } - - return true; + return { + [key]: (options, args) => ({ + [key]: { + filter: (user, ctx) => filter(options, args, user, ctx) } - }, - }), - commentEdited: (options, args) => ({ - commentEdited: { - filter: (comment, context) => { - if (!args.asset_id && (!context.user || !context.user.can(SUBSCRIBE_ALL_COMMENT_EDITED))) { - return false; - } - return !args.asset_id || comment.asset_id === args.asset_id; - } - }, - }), - commentFlagged: (options, args) => ({ - commentFlagged: { - filter: (comment, context) => { - if (!context.user || !context.user.can(SUBSCRIBE_COMMENT_FLAGGED)) { - return false; - } - return !args.asset_id || comment.asset_id === args.asset_id; - } - }, - }), - commentAccepted: (options, args) => ({ - commentAccepted: { - filter: (comment, context) => { - if (!context.user || !context.user.can(SUBSCRIBE_COMMENT_ACCEPTED)) { - return false; - } - return !args.asset_id || comment.asset_id === args.asset_id; - } - }, - }), - commentRejected: (options, args) => ({ - commentRejected: { - filter: (comment, context) => { - if (!context.user || !context.user.can(SUBSCRIBE_COMMENT_REJECTED)) { - return false; - } - return !args.asset_id || comment.asset_id === args.asset_id; - } - }, - }), - userSuspended: (options, args) => ({ - userSuspended: { - filter: (user, context) => { - if ( - !context.user - || args.user_id !== user.id && !context.user.can(SUBSCRIBE_ALL_USER_SUSPENDED) - ) { - return false; - } - return !args.user_id || user.id === args.user_id; - } - }, - }), - userBanned: (options, args) => ({ - userBanned: { - filter: (user, context) => { - if ( - !context.user - || args.user_id !== user.id && !context.user.can(SUBSCRIBE_ALL_USER_BANNED) - ) { - return false; - } - return !args.user_id || user.id === args.user_id; - } - }, - }), - usernameRejected: (options, args) => ({ - usernameRejected: { - filter: (user, context) => { - if ( - !context.user - || args.user_id !== user.id && !context.user.can(SUBSCRIBE_ALL_USERNAME_REJECTED) - ) { - return false; - } - return !args.user_id || user.id === args.user_id; - } - }, - }), -}); - -module.exports = setupFunctions; + }) + }; +}) + .reduce((setupFunction, setupFunctions) => { + return merge(setupFunctions, setupFunction); + }, {})); diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index 34f0b58b9..1d5c3d77f 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -1492,6 +1492,10 @@ type Subscription { # `user_id` must match id of current user except for # users with the `ADMIN` or `MODERATOR` role. usernameRejected(user_id: ID): User + + # Gen an update whenever a username has been approved. `user_id` must match id + # of current user except for users with the `ADMIN` or `MODERATOR` role. + usernameApproved(user_id: ID): User } ################################################################################ diff --git a/perms/constants/subscription.js b/perms/constants/subscription.js index 4b0e56571..6c56529ea 100644 --- a/perms/constants/subscription.js +++ b/perms/constants/subscription.js @@ -7,4 +7,5 @@ module.exports = { SUBSCRIBE_ALL_USER_SUSPENDED: 'SUBSCRIBE_ALL_USER_SUSPENDED', SUBSCRIBE_ALL_USER_BANNED: 'SUBSCRIBE_ALL_USER_BANNED', SUBSCRIBE_ALL_USERNAME_REJECTED: 'SUBSCRIBE_ALL_USERNAME_REJECTED', + SUBSCRIBE_ALL_USERNAME_APPROVED: 'SUBSCRIBE_ALL_USERNAME_APPROVED' }; diff --git a/perms/reducers/subscription.js b/perms/reducers/subscription.js index 65d93e61b..3a6268553 100644 --- a/perms/reducers/subscription.js +++ b/perms/reducers/subscription.js @@ -11,6 +11,7 @@ module.exports = (user, perm) => { case types.SUBSCRIBE_ALL_USER_SUSPENDED: case types.SUBSCRIBE_ALL_USER_BANNED: case types.SUBSCRIBE_ALL_USERNAME_REJECTED: + case types.SUBSCRIBE_ALL_USERNAME_APPROVED: return check(user, ['ADMIN', 'MODERATOR']); default: