initial pass at status support

This commit is contained in:
Wyatt Johnson
2017-11-02 17:16:57 -06:00
parent 1f3722edc1
commit 76a255fb7b
49 changed files with 1112 additions and 1598 deletions
-10
View File
@@ -1,5 +1,4 @@
const ActionsService = require('../../services/actions');
const UsersService = require('../../services/users');
const errors = require('../../errors');
const {CREATE_ACTION, DELETE_ACTION} = require('../../perms/constants');
@@ -31,15 +30,6 @@ const createAction = async ({user = {}, pubsub, loaders: {Comments}}, {item_id,
metadata
});
if (item_type === 'USERS' && action_type === 'FLAG') {
// Set the user as pending if it was a user flag and user has no Admin, Staff or Moderation roles
let user = await UsersService.findById(item_id);
if(!user.isStaff()){
await UsersService.setStatus(item_id, 'PENDING');
}
}
if (comment) {
pubsub.publish('commentFlagged', comment);
}
+88 -30
View File
@@ -1,29 +1,82 @@
const errors = require('../../errors');
const UserModel = require('../../models/user');
const UsersService = require('../../services/users');
const {SET_USER_STATUS, SUSPEND_USER, REJECT_USERNAME} = require('../../perms/constants');
const {
SET_USER_USERNAME_STATUS,
SET_USER_BAN_STATUS,
SET_USER_SUSPENSION_STATUS,
} = require('../../perms/constants');
const setUserStatus = async ({pubsub}, {id, status}) => {
const result = await UsersService.setStatus(id, status);
if (result && result.status === 'BANNED') {
pubsub.publish('userBanned', result);
const setUserUsernameStatus = async (ctx, id, status) => {
const user = await UserModel.findOneAndUpdate({id}, {
$set: {
'status.username.status': status
},
$push: {
'status.username.history': {
status,
assigned_by: ctx.user.id,
created_at: Date.now()
}
}
}, {
new: true
});
if (user === null) {
throw errors.ErrNotFound;
}
if (status === 'REJECTED') {
ctx.pubsub.publish('usernameRejected', user);
}
return result;
};
const suspendUser = async ({pubsub}, {id, message, until}) => {
const result = await UsersService.suspendUser(id, message, until);
if (result) {
pubsub.publish('userSuspended', result);
const setUserBanStatus = async (ctx, id, status) => {
const user = await UserModel.findOneAndUpdate({id}, {
$set: {
'status.banned.status': status
},
$push: {
'status.banned.history': {
status,
assigned_by: ctx.user.id,
created_at: Date.now()
}
}
}, {
new: true
});
if (user === null) {
throw errors.ErrNotFound;
}
if (user.banned) {
ctx.pubsub.publish('userBanned', user);
}
return result;
};
const rejectUsername = async ({pubsub}, {id, message}) => {
const result = await UsersService.rejectUsername(id, message);
if (result) {
pubsub.publish('usernameRejected', result);
const setUserSuspensionStatus = async (ctx, id, until) => {
const user = await UserModel.findOneAndUpdate({id}, {
$set: {
'status.suspension.until': until
},
$push: {
'status.suspension.history': {
until,
assigned_by: ctx.user.id,
created_at: Date.now()
}
}
}, {
new: true
});
if (user === null) {
throw errors.ErrNotFound;
}
if (user.suspended) {
ctx.pubsub.publish('userSuspended', user);
}
return result;
};
const ignoreUser = ({user}, userToIgnore) => {
@@ -34,27 +87,32 @@ const stopIgnoringUser = ({user}, userToStopIgnoring) => {
return UsersService.stopIgnoringUsers(user.id, [userToStopIgnoring.id]);
};
module.exports = (context) => {
module.exports = (ctx) => {
let mutators = {
User: {
setUserStatus: () => Promise.reject(errors.ErrNotAuthorized),
suspendUser: () => Promise.reject(errors.ErrNotAuthorized),
rejectUsername: () => Promise.reject(errors.ErrNotAuthorized),
ignoreUser: (action) => ignoreUser(context, action),
stopIgnoringUser: (action) => stopIgnoringUser(context, action),
ignoreUser: () => Promise.reject(errors.ErrNotAuthorized),
stopIgnoringUser: () => Promise.reject(errors.ErrNotAuthorized),
setUserUsernameStatus: () => Promise.reject(errors.ErrNotAuthorized),
setUserBanStatus: () => Promise.reject(errors.ErrNotAuthorized),
setUserSuspensionStatus: () => Promise.reject(errors.ErrNotAuthorized),
}
};
if (context.user && context.user.can(SET_USER_STATUS)) {
mutators.User.setUserStatus = (action) => setUserStatus(context, action);
}
if (ctx.user) {
mutators.User.ignoreUser = (action) => ignoreUser(ctx, action);
mutators.User.stopIgnoringUser = (action) => stopIgnoringUser(ctx, action);
if (context.user && context.user.can(SUSPEND_USER)) {
mutators.User.suspendUser = (action) => suspendUser(context, action);
}
if (ctx.user.can(SET_USER_USERNAME_STATUS)) {
mutators.User.setUserUsernameStatus = (id, status) => setUserUsernameStatus(ctx, id, status);
}
if (context.user && context.user.can(REJECT_USERNAME)) {
mutators.User.rejectUsername = (action) => rejectUsername(context, action);
if (ctx.user.can(SET_USER_BAN_STATUS)) {
mutators.User.setUserBanStatus = (id, status) => setUserBanStatus(ctx, id, status);
}
if (ctx.user.can(SET_USER_SUSPENSION_STATUS)) {
mutators.User.setUserSuspensionStatus = (id, until) => setUserSuspensionStatus(ctx, id, until);
}
}
return mutators;
+9 -6
View File
@@ -19,14 +19,17 @@ const RootMutation = {
deleteAction: async (_, {id}, {mutators: {Action}}) => {
await Action.delete({id});
},
setUserStatus: async (_, {id, status}, {mutators: {User}}) => {
await User.setUserStatus({id, status});
approveUsername: async (_, {id}, {mutators: {User}}) => {
await User.setUserUsernameStatus(id, 'APPROVED');
},
suspendUser: async (_, {input: {id, message, until}}, {mutators: {User}}) => {
await User.suspendUser({id, message, until});
rejectUsername: async (_, {id}, {mutators: {User}}) => {
await User.setUserUsernameStatus(id, 'REJECTED');
},
rejectUsername: async (_, {input: {id, message}}, {mutators: {User}}) => {
await User.rejectUsername({id, message});
setUserSuspensionStatus: async (_, {input: {id, until}}, {mutators: {User}}) => {
await User.setUserSuspensionStatus(id, until);
},
setUserBanStatus: async (_, {input: {id, status}}, {mutators: {User}}) => {
await User.setUserBanStatus(id, status);
},
ignoreUser: async (_, {id}, {mutators: {User}}) => {
await User.ignoreUser({id});
+6 -7
View File
@@ -6,7 +6,6 @@ const {
SEARCH_OTHERS_COMMENTS,
UPDATE_USER_ROLES,
SEARCH_COMMENT_METRICS,
VIEW_SUSPENSION_INFO,
LIST_OWN_TOKENS
} = require('../../perms/constants');
@@ -84,12 +83,12 @@ const User = {
}
},
suspension({id, suspension}, _, {user}) {
if (user.id !== id && !user.can(VIEW_SUSPENSION_INFO)) {
return null;
}
return suspension;
}
// suspension({id, suspension}, _, {user}) {
// if (user.id !== id && !user.can(VIEW_SUSPENSION_INFO)) {
// return null;
// }
// return suspension;
// }
};
// Decorate the User type resolver with a tags field.
+3 -3
View File
@@ -27,13 +27,13 @@ const setupFunctions = plugins.get('server', 'setupFunctions').reduce((acc, {plu
commentAdded: {
filter: (comment, context) => {
// Only priviledged users can subscribe to all assets.
// 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 scubsscribes for statuses other than NONE and/or ACCEPTED statuses, it needs
// special priviledges.
// 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))
+139 -55
View File
@@ -64,8 +64,99 @@ type UserProfile {
provider: String!
}
type SuspensionInfo {
# USER_STATUS_USERNAME is the different states that a username can be in.
enum USER_STATUS_USERNAME {
# UNSET is used when the username can be changed, and does not necessarily
# require moderator action to become active. This can be used when the user
# signs up with a social login and has the option of setting their own
# username.
UNSET
# SET is used when the username has been set for the first time, but cannot
# change without the username being rejected by a moderator and that moderator
# agreeing that the username should be allowed to change.
SET
# APPROVED is used when the username was changed, and subsequently approved by
# said moderator.
APPROVED
# REJECTED is used when the username was changed, and subsequently rejected by
# said moderator.
REJECTED
# CHANGED is used after a user has changed their username after it was
# rejected.
CHANGED
}
# UserStatusInput describes the queryable components of the UserStatus.
input UserStatusInput {
# username will restrict the returned users to only those with the given
# username status's. If not provided, no filtering will be performed.
username: [USER_STATUS_USERNAME!]
# banned will restrict the returned users to only those that are, or are not
# banned. If not provided, no filtering will be performed.
banned: Boolean
# suspended will restrict the returned users to only those that are, or are not
# suspended. If not provided, no filtering will be performed.
suspended: Boolean
}
type UsernameStatusHistory {
status: USER_STATUS_USERNAME!
assigned_by: User
created_at: Date!
}
type UsernameStatus {
status: USER_STATUS_USERNAME!
history: [UsernameStatusHistory!]
}
type BannedStatusHistory {
status: Boolean!
assigned_by: User
created_at: Date!
}
type BannedStatus {
status: Boolean!
history: [BannedStatusHistory!]
}
type SuspensionStatusHistory {
until: Date
assigned_by: User
created_at: Date!
}
type SuspensionStatus {
until: Date
history: [SuspensionStatusHistory!]
}
type UserStatus {
# username is the status of the username.
username: UsernameStatus!
# banned is the bool that determines if the user is banned or not.
banned: BannedStatus!
# suspension is the date that the user is suspended until.
suspension: SuspensionStatus!
}
input UserStateInput {
status: UserStatusInput
}
# UserState describes the different permission based details for a user.
type UserState {
# status describes the statuses of different aspects of the user's details.
status: UserStatus
}
# Any person who can author comments, create actions, and view comments on a
@@ -114,11 +205,7 @@ type User {
reliable: Reliability
# returns user status
status: USER_STATUS
# returns suspension info. Only available to Admins and Moderators
# or on own logged in User.
suspension: SuspensionInfo
state: UserState
}
# UserConnection represents a paginable subset of a user list.
@@ -143,8 +230,7 @@ input UsersQuery {
# Users returned will only be ones which have at least one action of this.
action_type: ACTION_TYPE
# Current status of a user..
statuses: [USER_STATUS!]
state: UserStateInput
# Limit the number of results to be returned.
limit: Int = 10
@@ -261,7 +347,7 @@ enum ACTION_TYPE {
# CommentsQuery allows the ability to query comments by a specific methods.
input CommentsQuery {
# Author of the commente
# Author of the comments.
author_id: ID
# Current status of a comment.
@@ -351,8 +437,8 @@ input UserCountQuery {
# type.
action_type: ACTION_TYPE
# Current status of a user.
statuses: [USER_STATUS]
# state queries for a specific subset of users with the given state query.
state: UserStateInput
}
type EditInfo {
@@ -810,14 +896,6 @@ enum SORT_COMMENTS_BY {
REPLIES
}
# All queries that can be executed.
enum USER_STATUS {
ACTIVE
BANNED
PENDING
APPROVED
}
# Metrics for the assets.
enum ASSET_METRICS_SORT {
@@ -995,26 +1073,13 @@ input CreateDontAgreeInput {
}
# Input for suspendUser mutation.
input SuspendUserInput {
input SetUserSuspensionStatusInput {
# id of target user.
id: ID!
# message to be sent to the user.
message: String!
# target user will be suspended until this date.
until: Date!
}
# Input for rejectUsername mutation.
input RejectUsernameInput {
# id of target user.
id: ID!
# message to be sent to the user.
message: String!
until: Date
}
# Configurable settings that can be overridden for the Asset. You must specify
@@ -1027,7 +1092,7 @@ input AssetSettingsInput {
# moderation is the moderation mode for the asset.
moderation: MODERATION_MODE
# questionBoxEnable will enable the Question Boxs' content to be visable above
# questionBoxEnable will enable the Question Boxs' content to be visible above
# the comment box.
questionBoxEnable: Boolean
@@ -1075,17 +1140,9 @@ type DeleteActionResponse implements Response {
errors: [UserError!]
}
# SetUserStatusResponse is the response returned with possibly some errors
# relating to the delete action attempt.
type SetUserStatusResponse implements Response {
# An array of errors relating to the mutation that occurred.
errors: [UserError!]
}
# SuspendUserResponse is the response returned with possibly some errors
# relating to the suspend action attempt.
type SuspendUserResponse implements Response {
# SetUserSuspensionStatusResponse is the response returned with possibly some
# errors relating to the suspend action attempt.
type SetUserSuspensionStatusResponse implements Response {
# An array of errors relating to the mutation that occurred.
errors: [UserError!]
@@ -1218,7 +1275,7 @@ input UpdateSettingsInput {
# comment is posted that it can still be edited by the author.
editCommentWindowLength: Int
# wordlist allows chaninging the available wordlists.
# wordlist allows changing the available wordlists.
wordlist: UpdateWordlistInput
# domains allows changing the available lists of domains.
@@ -1281,6 +1338,29 @@ type RevokeTokenResponse implements Response {
errors: [UserError!]
}
# SetUserBanStatusInput contains the input to change the ban status of a given
# user.
input SetUserBanStatusInput {
# id is the user to set the ban status on.
id: ID!
# status is the ban status to set on the target user.
status: Boolean!
}
type SetUserBanStatusResponse implements Response {
# An array of errors relating to the mutation that occurred.
errors: [UserError!]
}
type SetUsernameStatusResponse implements Response {
# An array of errors relating to the mutation that occurred.
errors: [UserError!]
}
# All mutations for the application are defined on this object.
type RootMutation {
@@ -1299,17 +1379,21 @@ type RootMutation {
# Edit a comment
editComment(id: ID!, asset_id: ID!, edit: EditCommentInput): EditCommentResponse!
# Sets User status. Requires the `ADMIN` role.
# Sets the suspension status on a given user. Requires the `MODERATOR` role.
# Mutation is restricted.
setUserStatus(id: ID!, status: USER_STATUS!): SetUserStatusResponse
setUserSuspensionStatus(input: SetUserSuspensionStatusInput!): SetUserSuspensionStatusResponse
# Suspends a user. Requires the `ADMIN` role.
# Sets the ban status on a given user. Requires the `MODERATOR` role.
# Mutation is restricted.
suspendUser(input: SuspendUserInput!): SuspendUserResponse
setUserBanStatus(input: SetUserBanStatusInput!): SetUserBanStatusResponse
# Reject a username. Requires the `ADMIN` role.
# Mutation is restricted.
rejectUsername(input: RejectUsernameInput!): RejectUsernameResponse
# Sets the username status on a given user to `APPROVED`. Requires the
# `MODERATOR` role. Mutation is restricted.
approveUsername(id: ID!): SetUsernameStatusResponse
# Sets the username status on a given user to `REJECTED`. Requires the
# `MODERATOR` role. Mutation is restricted.
rejectUsername(id: ID!): SetUsernameStatusResponse
# Sets Comment status. Requires the `ADMIN` role.
# Mutation is restricted.
@@ -1327,7 +1411,7 @@ type RootMutation {
# Updates the status of an asset allowing you to close/reopen an asset for
# commenting.
# Mutation is restricted.
# Mutation is restricted.
updateAssetStatus(id: ID!, input: UpdateAssetStatusInput!): UpdateAssetStatusResponse
# updateSettings will update the global settings.