[CORL-183] Invite Users (#2349)

* feat: initial UI impl

* feat: attach react devtools hook in development

* feat: working mutations

* feat: polished the invite modal with mutation

Co-authored-by: Vinh <vinh@wikiwi.io>

* feat: added check

* feat: improve the invite server impl

* feat: admin invite interface improvements

* fix: update tests

* feat: moved invite UI to admin

* fix: include email enabled as condition for invite

* feat: added admin tests

* feat: added tests for invite complete flow

* fix: review
This commit is contained in:
Wyatt Johnson
2019-06-28 22:51:42 +00:00
committed by GitHub
co-authored by Vinh
parent 76033118e5
commit 51b142035e
96 changed files with 3647 additions and 847 deletions
@@ -20,12 +20,14 @@ import {
updateRole,
updateUsername,
} from "coral-server/services/users";
import { invite } from "coral-server/services/users/auth/invite";
import {
GQLBanUserInput,
GQLCreateTokenInput,
GQLDeactivateTokenInput,
GQLIgnoreUserInput,
GQLInviteUsersInput,
GQLRemoveUserBanInput,
GQLRemoveUserIgnoreInput,
GQLRemoveUserSuspensionInput,
@@ -41,6 +43,25 @@ import {
} from "../schema/__generated__/types";
export const Users = (ctx: TenantContext) => ({
invite: async ({ role, emails }: GQLInviteUsersInput) =>
mapFieldsetToErrorCodes(
invite(
ctx.mongo,
ctx.tenant,
ctx.config,
ctx.mailerQueue,
ctx.signingConfig!,
{ role, emails },
ctx.user!,
ctx.now
),
{
"input.emails": [
ERROR_CODES.EMAIL_INVALID_FORMAT,
ERROR_CODES.EMAIL_EXCEEDS_MAX_LENGTH,
],
}
),
setUsername: async (
input: GQLSetUsernameInput
): Promise<Readonly<User> | null> =>
@@ -0,0 +1,7 @@
import { GQLInviteTypeResolver } from "coral-server/graph/tenant/schema/__generated__/types";
import * as invite from "coral-server/models/invite";
export const Invite: GQLInviteTypeResolver<invite.Invite> = {
createdBy: ({ createdBy }, args, ctx) =>
ctx.loaders.Users.user.load(createdBy),
};
@@ -107,6 +107,10 @@ export const Mutation: Required<GQLMutationTypeResolver<void>> = {
comment: await ctx.mutators.Actions.rejectComment(input),
clientMutationId: input.clientMutationId,
}),
inviteUsers: async (source, { input }, ctx) => ({
invites: await ctx.mutators.Users.invite(input),
clientMutationId: input.clientMutationId,
}),
setUsername: async (source, { input }, ctx) => ({
user: await ctx.mutators.Users.setUsername(input),
clientMutationId: input.clientMutationId,
@@ -16,6 +16,7 @@ import { FacebookAuthIntegration } from "./FacebookAuthIntegration";
import { FeatureCommentPayload } from "./FeatureCommentPayload";
import { Flag } from "./Flag";
import { GoogleAuthIntegration } from "./GoogleAuthIntegration";
import { Invite } from "./Invite";
import { ModerationQueue } from "./ModerationQueue";
import { ModerationQueues } from "./ModerationQueues";
import { Mutation } from "./Mutation";
@@ -48,6 +49,7 @@ const Resolvers: GQLResolver = {
FeatureCommentPayload,
Flag,
GoogleAuthIntegration,
Invite,
ModerationQueue,
ModerationQueues,
Mutation,
@@ -881,7 +881,7 @@ type Email {
"""
smtpURI is the SMTP connection url to send emails on.
"""
smtpURI: String
smtpURI: String @auth(roles: [ADMIN])
"""
fromAddress is the email address that will be used to send emails from.
@@ -1137,7 +1137,7 @@ type Settings {
"""
email is the set of credentials and settings associated with the organization.
"""
email: Email! @auth(roles: [ADMIN])
email: Email! @auth(roles: [ADMIN, MODERATOR])
"""
wordList will return a given list of words.
@@ -1228,6 +1228,39 @@ type Token {
createdAt: Time!
}
"""
Invite represents a given User that is pending registration that has been
invited by an Administrator.
"""
type Invite {
"""
id is the identifier for the Invite.
"""
id: ID!
"""
email is the email address that will be assigned and used for the
invited User.
"""
email: String!
"""
role is the USER_ROLE that the User will be assigned upon
account creation.
"""
role: USER_ROLE!
"""
createdBy is the User that created the Invite.
"""
createdBy: User!
"""
createdAt is the time that the Invite was created on.
"""
createdAt: Time!
}
"""
BanStatusHistory is the list of all ban events against a specific User.
"""
@@ -3748,6 +3781,38 @@ type SetUsernamePayload {
"""
clientMutationId: String!
}
##################
# inviteUser
##################
input InviteUsersInput {
"""
emails is the email addresses of the Users to be invited.
"""
emails: [String!]!
"""
role is the designated role of the User being invited.
"""
role: USER_ROLE!
"""
clientMutationId is required for Relay support.
"""
clientMutationId: String!
}
type InviteUsersPayload {
"""
invites is the references to the invited Users.
"""
invites: [Invite]!
"""
clientMutationId is required for Relay support.
"""
clientMutationId: String!
}
##################
# setEmail
@@ -4362,6 +4427,13 @@ type Mutation {
rejectComment(input: RejectCommentInput!): RejectCommentPayload!
@auth(roles: [MODERATOR, ADMIN])
"""
inviteUsers will send emails to the users with a new account at the designated
role.
"""
inviteUsers(input: InviteUsersInput!): InviteUsersPayload!
@auth(roles: [ADMIN])
"""
setUsername will set the username on the current User if they have not set one
before. This mutation will fail if the username is already set.