Skip to content

Commit

Permalink
feat: improve sign up error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
gabrielmeloc22 committed Aug 14, 2024
1 parent 2fd95d4 commit 648d296
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 24 deletions.
6 changes: 6 additions & 0 deletions apps/server/schema/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -305,9 +305,15 @@ type mutation {
type RegisterUserPayload {
token: String
user: User
errors: [RegisterMutationErrors!]
clientMutationId: String
}

enum RegisterMutationErrors {
USERNAME_TAKEN
EMAIL_TAKEN
}

input RegisterUserInput {
username: String!
email: EmailAddress
Expand Down
60 changes: 45 additions & 15 deletions apps/server/src/modules/user/mutations/RegisterMutation.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { generateToken } from "@/auth";
import type { Context } from "@/context";
import { genSaltSync, hashSync } from "bcrypt";
import { GraphQLNonNull, GraphQLString } from "graphql";
import { genSalt, hash } from "bcrypt";
import {
GraphQLEnumType,
GraphQLList,
GraphQLNonNull,
GraphQLString,
} from "graphql";
import { mutationWithClientMutationId } from "graphql-relay";
import { EmailAddressResolver } from "graphql-scalars";
import { type User, UserModel } from "../UserModel";
Expand All @@ -13,9 +18,12 @@ type RegisterInput = {
password: string;
};

type RegisterError = "USERNAME_TAKEN" | "EMAIL_TAKEN";

type RegisterOutput = {
token: string | null;
user: User | null;
errors: RegisterError[] | null;
};

export const Register = mutationWithClientMutationId<
Expand All @@ -31,31 +39,53 @@ export const Register = mutationWithClientMutationId<
user: {
type: UserType,
},
errors: {
type: new GraphQLList(
new GraphQLNonNull(
new GraphQLEnumType({
name: "RegisterMutationErrors",
values: {
USERNAME_TAKEN: { value: "USERNAME_TAKEN" },
EMAIL_TAKEN: { value: "EMAIL_TAKEN" },
},
}),
),
),
},
},
inputFields: {
username: { type: new GraphQLNonNull(GraphQLString) },
email: { type: EmailAddressResolver },
password: { type: new GraphQLNonNull(GraphQLString) },
},
mutateAndGetPayload: async ({ email, password, username }) => {
const userAlreadyExists = !!(await UserModel.findOne({
$or: [
{ email: email.trim().toLowerCase() },
{ username: username.trim() },
],
}));
const cleanUsername = username.trim();
const cleanEmail = email.trim().toLowerCase();

const duplicateUser = await UserModel.findOne({
$or: [{ email: cleanEmail }, { username: cleanUsername }],
});

if (duplicateUser) {
const errors: RegisterError[] = [];

if (duplicateUser.email === cleanEmail) {
errors.push("EMAIL_TAKEN");
}
if (duplicateUser.username === cleanUsername) {
errors.push("USERNAME_TAKEN");
}

if (userAlreadyExists) {
throw new Error("User already exists");
return { errors, token: null, user: null };
}

const salt = genSaltSync();
const hashedPassword = hashSync(password, salt);
const salt = await genSalt();
const hashedPassword = await hash(password, salt);

const user = await UserModel.create({
email: email.trim(),
email: cleanEmail,
password: hashedPassword,
username: username.trim(),
username: cleanUsername,
});
const token = generateToken(user);

Expand All @@ -65,6 +95,6 @@ export const Register = mutationWithClientMutationId<
throw new Error(JSON.stringify(err));
}

return { token, user };
return { token, user, errors: null };
},
});
17 changes: 13 additions & 4 deletions apps/web/__generated__/signUpMutation.graphql.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions apps/web/data/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -305,9 +305,15 @@ type mutation {
type RegisterUserPayload {
token: String
user: User
errors: [RegisterMutationErrors!]
clientMutationId: String
}

enum RegisterMutationErrors {
USERNAME_TAKEN
EMAIL_TAKEN
}

input RegisterUserInput {
username: String!
email: EmailAddress
Expand Down
20 changes: 15 additions & 5 deletions apps/web/src/components/sign-up.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const UserRegisterFormSchema = z.object({
const RegisterUserMutation = graphql`
mutation signUpMutation($input: RegisterUserInput!) {
register(input: $input) {
errors
token
}
}
Expand All @@ -39,16 +40,25 @@ export function SignUp() {
const onSubmit = form.handleSubmit((data) => {
register({
variables: { input: data },
updater: (store, data) => {
if (data?.register?.token) {
store.invalidateStore();
}
},
onCompleted: (data) => {
if (data.register?.token) {
login(data.register.token);
location.reload();
}
if (data.register?.errors) {
for (const error of data.register.errors) {
switch (error) {
case "EMAIL_TAKEN":
form.setError("email", { message: "Email already taken" });
break;
case "USERNAME_TAKEN":
form.setError("username", {
message: "Username already taken",
});
break;
}
}
}
},
});
});
Expand Down

0 comments on commit 648d296

Please sign in to comment.