Update auth requests

This commit is contained in:
Aaron William Po
2024-02-16 02:40:35 -05:00
parent 9f1d09a61d
commit 03088080e9
16 changed files with 225 additions and 202 deletions

View File

@@ -1,4 +1,3 @@
import validateEmailRequest from '@/requests/users/auth/validateEmailRequest';
import validateUsernameRequest from '@/requests/users/profile/validateUsernameRequest'; import validateUsernameRequest from '@/requests/users/profile/validateUsernameRequest';
import { BaseCreateUserSchema } from '@/services/users/auth/schema/CreateUserValidationSchemas'; import { BaseCreateUserSchema } from '@/services/users/auth/schema/CreateUserValidationSchemas';
import { Switch } from '@headlessui/react'; import { Switch } from '@headlessui/react';
@@ -7,7 +6,7 @@ import { Dispatch, FC, useContext } from 'react';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { z } from 'zod'; import { z } from 'zod';
import UserContext from '@/contexts/UserContext'; import UserContext from '@/contexts/UserContext';
import sendEditUserRequest from '@/requests/users/auth/sendEditUserRequest';
import createErrorToast from '@/util/createErrorToast'; import createErrorToast from '@/util/createErrorToast';
import { toast } from 'react-hot-toast'; import { toast } from 'react-hot-toast';
import { AccountPageAction, AccountPageState } from '@/reducers/accountPageReducer'; import { AccountPageAction, AccountPageState } from '@/reducers/accountPageReducer';
@@ -15,6 +14,7 @@ import FormError from '../ui/forms/FormError';
import FormInfo from '../ui/forms/FormInfo'; import FormInfo from '../ui/forms/FormInfo';
import FormLabel from '../ui/forms/FormLabel'; import FormLabel from '../ui/forms/FormLabel';
import FormTextInput from '../ui/forms/FormTextInput'; import FormTextInput from '../ui/forms/FormTextInput';
import { sendEditUserRequest, validateEmailRequest } from '@/requests/users/auth';
interface AccountInfoProps { interface AccountInfoProps {
pageState: AccountPageState; pageState: AccountPageState;
@@ -36,7 +36,7 @@ const AccountInfo: FC<AccountInfoProps> = ({ pageState, dispatch }) => {
.refine( .refine(
async (email) => { async (email) => {
if (user!.email === email) return true; if (user!.email === email) return true;
return validateEmailRequest(email); return validateEmailRequest({ email });
}, },
{ message: 'Email is already taken.' }, { message: 'Email is already taken.' },
), ),

View File

@@ -4,10 +4,11 @@ import { SubmitHandler, useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod'; import { z } from 'zod';
import { UpdatePasswordSchema } from '@/services/users/auth/schema/CreateUserValidationSchemas'; import { UpdatePasswordSchema } from '@/services/users/auth/schema/CreateUserValidationSchemas';
import sendUpdatePasswordRequest from '@/requests/users/auth/sendUpdatePasswordRequest';
import { AccountPageState, AccountPageAction } from '@/reducers/accountPageReducer'; import { AccountPageState, AccountPageAction } from '@/reducers/accountPageReducer';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import createErrorToast from '@/util/createErrorToast'; import createErrorToast from '@/util/createErrorToast';
import { sendUpdatePasswordRequest } from '@/requests/users/auth';
import FormError from '../ui/forms/FormError'; import FormError from '../ui/forms/FormError';
import FormInfo from '../ui/forms/FormInfo'; import FormInfo from '../ui/forms/FormInfo';
import FormLabel from '../ui/forms/FormLabel'; import FormLabel from '../ui/forms/FormLabel';

View File

@@ -1,4 +1,3 @@
import sendLoginUserRequest from '@/requests/users/auth/sendLoginUserRequest';
import LoginValidationSchema from '@/services/users/auth/schema/LoginValidationSchema'; import LoginValidationSchema from '@/services/users/auth/schema/LoginValidationSchema';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
@@ -15,6 +14,7 @@ import FormLabel from '../ui/forms/FormLabel';
import FormSegment from '../ui/forms/FormSegment'; import FormSegment from '../ui/forms/FormSegment';
import FormTextInput from '../ui/forms/FormTextInput'; import FormTextInput from '../ui/forms/FormTextInput';
import Button from '../ui/forms/Button'; import Button from '../ui/forms/Button';
import { sendLoginUserRequest } from '@/requests/users/auth';
type LoginT = z.infer<typeof LoginValidationSchema>; type LoginT = z.infer<typeof LoginValidationSchema>;
const LoginForm = () => { const LoginForm = () => {

View File

@@ -1,4 +1,3 @@
import sendRegisterUserRequest from '@/requests/users/auth/sendRegisterUserRequest';
import { CreateUserValidationSchemaWithUsernameAndEmailCheck } from '@/services/users/auth/schema/CreateUserValidationSchemas'; import { CreateUserValidationSchemaWithUsernameAndEmailCheck } from '@/services/users/auth/schema/CreateUserValidationSchemas';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
@@ -9,6 +8,7 @@ import { z } from 'zod';
import createErrorToast from '@/util/createErrorToast'; import createErrorToast from '@/util/createErrorToast';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import { sendRegisterUserRequest } from '@/requests/users/auth';
import Button from './ui/forms/Button'; import Button from './ui/forms/Button';
import FormError from './ui/forms/FormError'; import FormError from './ui/forms/FormError';
import FormInfo from './ui/forms/FormInfo'; import FormInfo from './ui/forms/FormInfo';

View File

@@ -1,7 +1,8 @@
import useFollowStatus from '@/hooks/data-fetching/user-follows/useFollowStatus'; import useFollowStatus from '@/hooks/data-fetching/user-follows/useFollowStatus';
import useGetUsersFollowedByUser from '@/hooks/data-fetching/user-follows/useGetUsersFollowedByUser'; import useGetUsersFollowedByUser from '@/hooks/data-fetching/user-follows/useGetUsersFollowedByUser';
import useGetUsersFollowingUser from '@/hooks/data-fetching/user-follows/useGetUsersFollowingUser'; import useGetUsersFollowingUser from '@/hooks/data-fetching/user-follows/useGetUsersFollowingUser';
import sendUserFollowRequest from '@/requests/users/auth/sendUserFollowRequest'; import { sendUserFollowRequest } from '@/requests/users/auth';
import GetUserSchema from '@/services/users/auth/schema/GetUserSchema'; import GetUserSchema from '@/services/users/auth/schema/GetUserSchema';
import { FC, useState } from 'react'; import { FC, useState } from 'react';
import { FaUserCheck, FaUserPlus } from 'react-icons/fa'; import { FaUserCheck, FaUserPlus } from 'react-icons/fa';
@@ -25,7 +26,7 @@ const UserFollowButton: FC<UserFollowButtonProps> = ({
const onClick = async () => { const onClick = async () => {
try { try {
setIsLoading(true); setIsLoading(true);
await sendUserFollowRequest(user.id); await sendUserFollowRequest({ userId: user.id });
await Promise.all([ await Promise.all([
mutateFollowStatus(), mutateFollowStatus(),
mutateFollowerCount(), mutateFollowerCount(),

View File

@@ -12,8 +12,9 @@ import { NextPage } from 'next';
import { SubmitHandler, useForm } from 'react-hook-form'; import { SubmitHandler, useForm } from 'react-hook-form';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import sendForgotPasswordRequest from '@/requests/users/auth/sendForgotPasswordRequest';
import { FaUserCircle } from 'react-icons/fa'; import { FaUserCircle } from 'react-icons/fa';
import { sendForgotPasswordRequest } from '@/requests/users/auth';
interface ForgotPasswordPageProps {} interface ForgotPasswordPageProps {}
@@ -29,7 +30,7 @@ const ForgotPasswordPage: NextPage<ForgotPasswordPageProps> = () => {
const onSubmit: SubmitHandler<{ email: string }> = async (data) => { const onSubmit: SubmitHandler<{ email: string }> = async (data) => {
try { try {
const loadingToast = toast.loading('Sending reset link...'); const loadingToast = toast.loading('Sending reset link...');
await sendForgotPasswordRequest(data.email); await sendForgotPasswordRequest({ email: data.email });
reset(); reset();
toast.dismiss(loadingToast); toast.dismiss(loadingToast);
toast.success('Password reset link sent!'); toast.success('Password reset link sent!');

View File

@@ -0,0 +1,169 @@
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
import { BasicUserInfoSchema } from '@/config/auth/types';
import GetUserSchema from '@/services/users/auth/schema/GetUserSchema';
import type {
SendEditUserRequest,
SendForgotPasswordRequest,
SendLoginUserRequest,
SendRegisterUserRequest,
SendUpdatePasswordRequest,
SendUserFollowRequest,
ValidateEmailRequest,
} from './types';
import { z } from 'zod';
export const sendEditUserRequest: SendEditUserRequest = async ({ user, data }) => {
const response = await fetch(`/api/users/${user!.id}`, {
body: JSON.stringify(data),
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
});
if (!response.ok) {
throw new Error(response.statusText);
}
const json = await response.json();
const parsed = APIResponseValidationSchema.safeParse(json);
if (!parsed.success) {
throw new Error('API response validation failed.');
}
return parsed.data;
};
export const sendForgotPasswordRequest: SendForgotPasswordRequest = async ({ email }) => {
const response = await fetch('/api/users/forgot-password', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email }),
});
if (!response.ok) {
throw new Error("Something went wrong and we couldn't send the reset link.");
}
const json = await response.json();
const parsed = APIResponseValidationSchema.safeParse(json);
if (!parsed.success) {
throw new Error(parsed.error.message);
}
return parsed.data;
};
export const sendLoginUserRequest: SendLoginUserRequest = async ({
username,
password,
}) => {
const response = await fetch('/api/users/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
username,
password,
}),
});
const json: unknown = await response.json();
const parsed = APIResponseValidationSchema.safeParse(json);
if (!parsed.success) {
throw new Error('API response validation failed');
}
if (!parsed.data.success) {
throw new Error(parsed.data.message);
}
const parsedPayload = BasicUserInfoSchema.safeParse(parsed.data.payload);
if (!parsedPayload.success) {
throw new Error('API response payload validation failed');
}
return parsedPayload.data;
};
export const sendRegisterUserRequest: SendRegisterUserRequest = async (data) => {
const response = await fetch('/api/users/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
const json = await response.json();
const parsed = APIResponseValidationSchema.safeParse(json);
if (!parsed.success) {
throw new Error('API response validation failed.');
}
if (!parsed.data.success) {
throw new Error(parsed.data.message);
}
const parsedPayload = GetUserSchema.safeParse(parsed.data.payload);
if (!parsedPayload.success) {
throw new Error('API response payload validation failed.');
}
return parsedPayload.data;
};
export const sendUpdatePasswordRequest: SendUpdatePasswordRequest = async (data) => {
const response = await fetch('/api/users/edit-password', {
body: JSON.stringify(data),
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
});
if (!response.ok) {
throw new Error(response.statusText);
}
const json = await response.json();
const parsed = APIResponseValidationSchema.safeParse(json);
if (!parsed.success) {
throw new Error('API response validation failed.');
}
return parsed.data;
};
export const sendUserFollowRequest: SendUserFollowRequest = async ({ userId }) => {
const response = await fetch(`/api/users/${userId}/follow-user`, { method: 'POST' });
const json = await response.json();
const parsed = APIResponseValidationSchema.safeParse(json);
if (!parsed.success) {
throw new Error('Invalid API response.');
}
return parsed.data;
};
export const validateEmailRequest: ValidateEmailRequest = async ({ email }) => {
const response = await fetch(`/api/users/check-email?email=${email}`);
const json = await response.json();
const parsed = APIResponseValidationSchema.safeParse(json);
if (!parsed.success) {
return false;
}
const parsedPayload = z
.object({ emailIsTaken: z.boolean() })
.safeParse(parsed.data.payload);
if (!parsedPayload.success) {
return false;
}
return !parsedPayload.data.emailIsTaken;
};

View File

@@ -1,35 +0,0 @@
import GetUserSchema from '@/services/users/auth/schema/GetUserSchema';
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
import { z } from 'zod';
interface SendEditUserRequestArgs {
user: z.infer<typeof GetUserSchema>;
data: {
username: string;
email: string;
firstName: string;
lastName: string;
};
}
const sendEditUserRequest = async ({ user, data }: SendEditUserRequestArgs) => {
const response = await fetch(`/api/users/${user!.id}`, {
body: JSON.stringify(data),
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
});
if (!response.ok) {
throw new Error(response.statusText);
}
const json = await response.json();
const parsed = APIResponseValidationSchema.safeParse(json);
if (!parsed.success) {
throw new Error('API response validation failed.');
}
return parsed;
};
export default sendEditUserRequest;

View File

@@ -1,24 +0,0 @@
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
const sendForgotPasswordRequest = async (email: string) => {
const response = await fetch('/api/users/forgot-password', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email }),
});
if (!response.ok) {
throw new Error("Something went wrong and we couldn't send the reset link.");
}
const json = await response.json();
const parsed = APIResponseValidationSchema.safeParse(json);
if (!parsed.success) {
throw new Error(parsed.error.message);
}
return parsed.data;
};
export default sendForgotPasswordRequest;

View File

@@ -1,31 +0,0 @@
import { BasicUserInfoSchema } from '@/config/auth/types';
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
const sendLoginUserRequest = async (data: { username: string; password: string }) => {
const response = await fetch('/api/users/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
const json: unknown = await response.json();
const parsed = APIResponseValidationSchema.safeParse(json);
if (!parsed.success) {
throw new Error('API response validation failed');
}
if (!parsed.data.success) {
throw new Error(parsed.data.message);
}
const parsedPayload = BasicUserInfoSchema.safeParse(parsed.data.payload);
if (!parsedPayload.success) {
throw new Error('API response payload validation failed');
}
return parsedPayload.data;
};
export default sendLoginUserRequest;

View File

@@ -1,33 +0,0 @@
import { CreateUserValidationSchema } from '@/services/users/auth/schema/CreateUserValidationSchemas';
import GetUserSchema from '@/services/users/auth/schema/GetUserSchema';
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
import { z } from 'zod';
async function sendRegisterUserRequest(data: z.infer<typeof CreateUserValidationSchema>) {
const response = await fetch('/api/users/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
const json = await response.json();
const parsed = APIResponseValidationSchema.safeParse(json);
if (!parsed.success) {
throw new Error('API response validation failed.');
}
if (!parsed.data.success) {
throw new Error(parsed.data.message);
}
const parsedPayload = GetUserSchema.safeParse(parsed.data.payload);
if (!parsedPayload.success) {
throw new Error('API response payload validation failed.');
}
return parsedPayload.data;
}
export default sendRegisterUserRequest;

View File

@@ -1,26 +0,0 @@
import { UpdatePasswordSchema } from '@/services/users/auth/schema/CreateUserValidationSchemas';
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
import { z } from 'zod';
const sendUpdatePasswordRequest = async (data: z.infer<typeof UpdatePasswordSchema>) => {
const response = await fetch('/api/users/edit-password', {
body: JSON.stringify(data),
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
});
if (!response.ok) {
throw new Error(response.statusText);
}
const json = await response.json();
const parsed = APIResponseValidationSchema.safeParse(json);
if (!parsed.success) {
throw new Error('API response validation failed.');
}
return parsed.data;
};
export default sendUpdatePasswordRequest;

View File

@@ -1,16 +0,0 @@
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
const sendUserFollowRequest = async (userId: string) => {
const response = await fetch(`/api/users/${userId}/follow-user`, { method: 'POST' });
const json = await response.json();
const parsed = APIResponseValidationSchema.safeParse(json);
if (!parsed.success) {
throw new Error('Invalid API response.');
}
return parsed;
};
export default sendUserFollowRequest;

View File

@@ -0,0 +1,41 @@
import { BasicUserInfoSchema } from '@/config/auth/types';
import {
CreateUserValidationSchema,
UpdatePasswordSchema,
} from '@/services/users/auth/schema/CreateUserValidationSchemas';
import GetUserSchema from '@/services/users/auth/schema/GetUserSchema';
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
import { z } from 'zod';
export type SendEditUserRequest = (args: {
user: z.infer<typeof GetUserSchema>;
data: {
username: string;
email: string;
firstName: string;
lastName: string;
};
}) => Promise<z.infer<typeof APIResponseValidationSchema>>;
export type SendForgotPasswordRequest = (args: {
email: string;
}) => Promise<z.infer<typeof APIResponseValidationSchema>>;
export type SendLoginUserRequest = (args: {
username: string;
password: string;
}) => Promise<z.infer<typeof BasicUserInfoSchema>>;
export type SendRegisterUserRequest = (
args: z.infer<typeof CreateUserValidationSchema>,
) => Promise<z.infer<typeof GetUserSchema>>;
export type SendUpdatePasswordRequest = (
args: z.infer<typeof UpdatePasswordSchema>,
) => Promise<z.infer<typeof APIResponseValidationSchema>>;
export type SendUserFollowRequest = (args: {
userId: string;
}) => Promise<z.infer<typeof APIResponseValidationSchema>>;
export type ValidateEmailRequest = (args: { email: string }) => Promise<boolean>;

View File

@@ -1,25 +0,0 @@
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
import { z } from 'zod';
const validateEmailRequest = async (email: string) => {
const response = await fetch(`/api/users/check-email?email=${email}`);
const json = await response.json();
const parsed = APIResponseValidationSchema.safeParse(json);
if (!parsed.success) {
return false;
}
const parsedPayload = z
.object({ emailIsTaken: z.boolean() })
.safeParse(parsed.data.payload);
if (!parsedPayload.success) {
return false;
}
return !parsedPayload.data.emailIsTaken;
};
export default validateEmailRequest;

View File

@@ -1,4 +1,4 @@
import validateEmailRequest from '@/requests/users/auth/validateEmailRequest'; import { validateEmailRequest } from '@/requests/users/auth';
import validateUsernameRequest from '@/requests/users/profile/validateUsernameRequest'; import validateUsernameRequest from '@/requests/users/profile/validateUsernameRequest';
import sub from 'date-fns/sub'; import sub from 'date-fns/sub';
import { z } from 'zod'; import { z } from 'zod';
@@ -60,7 +60,7 @@ export const CreateUserValidationSchemaWithUsernameAndEmailCheck =
email: z email: z
.string() .string()
.email({ message: 'Email must be a valid email address.' }) .email({ message: 'Email must be a valid email address.' })
.refine(async (email) => validateEmailRequest(email), { .refine(async (email) => validateEmailRequest({ email }), {
message: 'Email is already taken.', message: 'Email is already taken.',
}), }),
username: z username: z