mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-02-16 10:42:08 +00:00
Update api routes to use authenticated user
This commit is contained in:
77
components/Login/LoginForm.tsx
Normal file
77
components/Login/LoginForm.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
import sendLoginUserRequest from '@/requests/sendLoginUserRequest';
|
||||
import LoginValidationSchema from '@/services/user/schema/LoginValidationSchema';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useForm, SubmitHandler } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
import FormError from '../ui/forms/FormError';
|
||||
import FormInfo from '../ui/forms/FormInfo';
|
||||
import FormLabel from '../ui/forms/FormLabel';
|
||||
import FormSegment from '../ui/forms/FormSegment';
|
||||
import FormTextInput from '../ui/forms/FormTextInput';
|
||||
|
||||
type LoginT = z.infer<typeof LoginValidationSchema>;
|
||||
const LoginForm = () => {
|
||||
const router = useRouter();
|
||||
const { register, handleSubmit, formState } = useForm<LoginT>({
|
||||
resolver: zodResolver(LoginValidationSchema),
|
||||
defaultValues: {
|
||||
username: '',
|
||||
password: '',
|
||||
},
|
||||
});
|
||||
|
||||
const { errors } = formState;
|
||||
|
||||
const onSubmit: SubmitHandler<LoginT> = async (data) => {
|
||||
try {
|
||||
const response = await sendLoginUserRequest(data);
|
||||
|
||||
router.push(`/users/${response.id}`);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form className="form-control w-9/12 space-y-5" onSubmit={handleSubmit(onSubmit)}>
|
||||
<div>
|
||||
<FormInfo>
|
||||
<FormLabel htmlFor="username">username</FormLabel>
|
||||
<FormError>{errors.username?.message}</FormError>
|
||||
</FormInfo>
|
||||
<FormSegment>
|
||||
<FormTextInput
|
||||
id="username"
|
||||
type="text"
|
||||
formValidationSchema={register('username')}
|
||||
error={!!errors.username}
|
||||
placeholder="username"
|
||||
/>
|
||||
</FormSegment>
|
||||
|
||||
<FormInfo>
|
||||
<FormLabel htmlFor="password">password</FormLabel>
|
||||
<FormError>{errors.password?.message}</FormError>
|
||||
</FormInfo>
|
||||
<FormSegment>
|
||||
<FormTextInput
|
||||
id="password"
|
||||
type="password"
|
||||
formValidationSchema={register('password')}
|
||||
error={!!errors.password}
|
||||
placeholder="password"
|
||||
/>
|
||||
</FormSegment>
|
||||
</div>
|
||||
|
||||
<div className="w-full">
|
||||
<button type="submit" className="btn-primary btn w-full">
|
||||
Login
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoginForm;
|
||||
@@ -3,11 +3,11 @@ import { NextHandler } from 'next-connect';
|
||||
import findUserById from '@/services/user/findUserById';
|
||||
import ServerError from '@/config/util/ServerError';
|
||||
import { getLoginSession } from '../session';
|
||||
import { ExtendedNextApiRequest } from '../types';
|
||||
import { UserExtendedNextApiRequest } from '../types';
|
||||
|
||||
/** Get the current user from the session. Adds the user to the request object. */
|
||||
const getCurrentUser = async (
|
||||
req: ExtendedNextApiRequest,
|
||||
req: UserExtendedNextApiRequest,
|
||||
res: NextApiResponse,
|
||||
next: NextHandler,
|
||||
) => {
|
||||
|
||||
@@ -15,7 +15,7 @@ export const UserSessionSchema = BasicUserInfoSchema.merge(
|
||||
}),
|
||||
);
|
||||
|
||||
export interface ExtendedNextApiRequest extends NextApiRequest {
|
||||
export interface UserExtendedNextApiRequest extends NextApiRequest {
|
||||
user?: z.infer<typeof GetUserSchema>;
|
||||
}
|
||||
|
||||
|
||||
48
config/zod/middleware/validateRequest.ts
Normal file
48
config/zod/middleware/validateRequest.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import ServerError from '@/config/util/ServerError';
|
||||
import { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { NextHandler } from 'next-connect';
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* Middleware to validate the request body and/or query against a zod schema.
|
||||
*
|
||||
* @example
|
||||
* const handler = nextConnect(NextConnectConfig).post(
|
||||
* validateRequest({ bodySchema: BeerPostValidationSchema }),
|
||||
* getCurrentUser,
|
||||
* createBeerPost,
|
||||
* );
|
||||
*
|
||||
* @param args
|
||||
* @param args.bodySchema The body schema to validate against.
|
||||
* @param args.querySchema The query schema to validate against.
|
||||
* @throws ServerError with status code 400 if the request body or query is invalid.
|
||||
*/
|
||||
const validateRequest =
|
||||
({
|
||||
bodySchema,
|
||||
querySchema,
|
||||
}: {
|
||||
bodySchema?: z.ZodSchema<any>;
|
||||
querySchema?: z.ZodSchema<any>;
|
||||
}) =>
|
||||
async (req: NextApiRequest, res: NextApiResponse, next: NextHandler) => {
|
||||
if (bodySchema) {
|
||||
const parsed = bodySchema.safeParse(req.body);
|
||||
if (!parsed.success) {
|
||||
throw new ServerError('Invalid request body.', 400);
|
||||
}
|
||||
}
|
||||
|
||||
if (querySchema) {
|
||||
const parsed = querySchema.safeParse(req.query);
|
||||
if (!parsed.success) {
|
||||
throw new ServerError(parsed.error.message, 400);
|
||||
}
|
||||
req.query = parsed.data;
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
export default validateRequest;
|
||||
@@ -1,27 +1,31 @@
|
||||
import validateRequest from '@/config/zod/middleware/validateRequest';
|
||||
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
|
||||
import { UserExtendedNextApiRequest } from '@/config/auth/types';
|
||||
import NextConnectConfig from '@/config/nextConnect/NextConnectConfig';
|
||||
import ServerError from '@/config/util/ServerError';
|
||||
import createNewBeerComment from '@/services/BeerComment/createNewBeerComment';
|
||||
import { BeerCommentQueryResultT } from '@/services/BeerComment/schema/BeerCommentQueryResult';
|
||||
import BeerCommentValidationSchema from '@/services/BeerComment/schema/CreateBeerCommentValidationSchema';
|
||||
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
|
||||
import { NextApiHandler } from 'next';
|
||||
|
||||
import nextConnect from 'next-connect';
|
||||
import { z } from 'zod';
|
||||
import getCurrentUser from '@/config/auth/middleware/getCurrentUser';
|
||||
import { NextApiResponse } from 'next';
|
||||
|
||||
const createComment: NextApiHandler<z.infer<typeof APIResponseValidationSchema>> = async (
|
||||
req,
|
||||
res,
|
||||
interface CreateCommentRequest extends UserExtendedNextApiRequest {
|
||||
body: z.infer<typeof BeerCommentValidationSchema>;
|
||||
}
|
||||
|
||||
const createComment = async (
|
||||
req: CreateCommentRequest,
|
||||
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
|
||||
) => {
|
||||
const cleanedReqBody = BeerCommentValidationSchema.safeParse(req.body);
|
||||
if (!cleanedReqBody.success) {
|
||||
throw new ServerError('Invalid request body', 400);
|
||||
}
|
||||
const { content, rating, beerPostId } = cleanedReqBody.data;
|
||||
const { content, rating, beerPostId } = req.body;
|
||||
|
||||
const newBeerComment: BeerCommentQueryResultT = await createNewBeerComment({
|
||||
content,
|
||||
rating,
|
||||
beerPostId,
|
||||
userId: req.user!.id,
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
@@ -32,6 +36,10 @@ const createComment: NextApiHandler<z.infer<typeof APIResponseValidationSchema>>
|
||||
});
|
||||
};
|
||||
|
||||
const handler = nextConnect(NextConnectConfig).post(createComment);
|
||||
const handler = nextConnect(NextConnectConfig).post(
|
||||
validateRequest({ bodySchema: BeerCommentValidationSchema }),
|
||||
getCurrentUser,
|
||||
createComment,
|
||||
);
|
||||
|
||||
export default handler;
|
||||
|
||||
@@ -1,21 +1,24 @@
|
||||
import { UserExtendedNextApiRequest } from '@/config/auth/types';
|
||||
import validateRequest from '@/config/zod/middleware/validateRequest';
|
||||
import nextConnect from 'next-connect';
|
||||
import ServerError from '@/config/util/ServerError';
|
||||
import createNewBeerPost from '@/services/BeerPost/createNewBeerPost';
|
||||
import BeerPostValidationSchema from '@/services/BeerPost/schema/CreateBeerPostValidationSchema';
|
||||
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
|
||||
import { NextApiHandler } from 'next';
|
||||
import { NextApiResponse } from 'next';
|
||||
import { z } from 'zod';
|
||||
import NextConnectConfig from '@/config/nextConnect/NextConnectConfig';
|
||||
import getCurrentUser from '@/config/auth/middleware/getCurrentUser';
|
||||
|
||||
const createBeerPost: NextApiHandler<
|
||||
z.infer<typeof APIResponseValidationSchema>
|
||||
> = async (req, res) => {
|
||||
const cleanedReqBody = BeerPostValidationSchema.safeParse(req.body);
|
||||
if (!cleanedReqBody.success) {
|
||||
throw new ServerError('Invalid request body', 400);
|
||||
}
|
||||
interface CreateBeerPostRequest extends UserExtendedNextApiRequest {
|
||||
body: z.infer<typeof BeerPostValidationSchema>;
|
||||
}
|
||||
|
||||
const createBeerPost = async (
|
||||
req: CreateBeerPostRequest,
|
||||
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
|
||||
) => {
|
||||
const { name, description, typeId, abv, ibu, breweryId } = req.body;
|
||||
|
||||
const { name, description, typeId, abv, ibu, breweryId } = cleanedReqBody.data;
|
||||
const newBeerPost = await createNewBeerPost({
|
||||
name,
|
||||
description,
|
||||
@@ -23,6 +26,7 @@ const createBeerPost: NextApiHandler<
|
||||
ibu,
|
||||
typeId,
|
||||
breweryId,
|
||||
userId: req.user!.id,
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
@@ -33,6 +37,10 @@ const createBeerPost: NextApiHandler<
|
||||
});
|
||||
};
|
||||
|
||||
const handler = nextConnect(NextConnectConfig).post(createBeerPost);
|
||||
const handler = nextConnect(NextConnectConfig).post(
|
||||
validateRequest({ bodySchema: BeerPostValidationSchema }),
|
||||
getCurrentUser,
|
||||
createBeerPost,
|
||||
);
|
||||
|
||||
export default handler;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import NextConnectConfig from '@/config/nextConnect/NextConnectConfig';
|
||||
import { ExtendedNextApiRequest } from '@/config/auth/types';
|
||||
import { UserExtendedNextApiRequest } from '@/config/auth/types';
|
||||
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
|
||||
import { NextApiResponse } from 'next';
|
||||
import getCurrentUser from '@/config/auth/middleware/getCurrentUser';
|
||||
import nextConnect from 'next-connect';
|
||||
import { z } from 'zod';
|
||||
|
||||
const sendCurrentUser = async (req: ExtendedNextApiRequest, res: NextApiResponse) => {
|
||||
const sendCurrentUser = async (req: UserExtendedNextApiRequest, res: NextApiResponse) => {
|
||||
const { user } = req;
|
||||
res.status(200).json({
|
||||
message: `Currently logged in as ${user!.username}`,
|
||||
@@ -17,7 +17,7 @@ const sendCurrentUser = async (req: ExtendedNextApiRequest, res: NextApiResponse
|
||||
};
|
||||
|
||||
const handler = nextConnect<
|
||||
ExtendedNextApiRequest,
|
||||
UserExtendedNextApiRequest,
|
||||
NextApiResponse<z.infer<typeof APIResponseValidationSchema>>
|
||||
>(NextConnectConfig).get(getCurrentUser, sendCurrentUser);
|
||||
|
||||
|
||||
@@ -8,20 +8,19 @@ import { NextApiResponse } from 'next';
|
||||
import { z } from 'zod';
|
||||
import ServerError from '@/config/util/ServerError';
|
||||
import LoginValidationSchema from '@/services/user/schema/LoginValidationSchema';
|
||||
import { ExtendedNextApiRequest } from '../../../config/auth/types';
|
||||
import { UserExtendedNextApiRequest } from '../../../config/auth/types';
|
||||
|
||||
export default nextConnect<
|
||||
ExtendedNextApiRequest,
|
||||
UserExtendedNextApiRequest,
|
||||
NextApiResponse<z.infer<typeof APIResponseValidationSchema>>
|
||||
>(NextConnectConfig)
|
||||
.use(passport.initialize())
|
||||
.use(async (req, res, next) => {
|
||||
passport.use(localStrat);
|
||||
const parsed = LoginValidationSchema.safeParse(req.body);
|
||||
if (!parsed.success) {
|
||||
throw new ServerError('Username and password are required.', 400);
|
||||
}
|
||||
|
||||
passport.use(localStrat);
|
||||
passport.authenticate('local', { session: false }, (error, token) => {
|
||||
if (error) {
|
||||
next(error);
|
||||
|
||||
@@ -1,44 +1,18 @@
|
||||
import { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { z } from 'zod';
|
||||
import ServerError from '@/config/util/ServerError';
|
||||
import nc, { NextHandler } from 'next-connect';
|
||||
import nc from 'next-connect';
|
||||
import createNewUser from '@/services/user/createNewUser';
|
||||
import CreateUserValidationSchema from '@/services/user/schema/CreateUserValidationSchema';
|
||||
import NextConnectConfig from '@/config/nextConnect/NextConnectConfig';
|
||||
import findUserByUsername from '@/services/user/findUserByUsername';
|
||||
import findUserByEmail from '@/services/user/findUserByEmail';
|
||||
import validateRequest from '@/config/zod/middleware/validateRequest';
|
||||
|
||||
interface RegisterUserRequest extends NextApiRequest {
|
||||
body: z.infer<typeof CreateUserValidationSchema>;
|
||||
}
|
||||
|
||||
const validateRequest =
|
||||
({
|
||||
bodySchema,
|
||||
querySchema,
|
||||
}: {
|
||||
bodySchema?: z.ZodSchema<any>;
|
||||
querySchema?: z.ZodSchema<any>;
|
||||
}) =>
|
||||
async (req: NextApiRequest, res: NextApiResponse, next: NextHandler) => {
|
||||
if (bodySchema) {
|
||||
const parsed = bodySchema.safeParse(req.body);
|
||||
if (!parsed.success) {
|
||||
throw new ServerError('Invalid request body.', 400);
|
||||
}
|
||||
}
|
||||
|
||||
if (querySchema) {
|
||||
const parsed = querySchema.safeParse(req.query);
|
||||
if (!parsed.success) {
|
||||
throw new ServerError(parsed.error.message, 400);
|
||||
}
|
||||
req.query = parsed.data;
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
const registerUser = async (req: RegisterUserRequest, res: NextApiResponse) => {
|
||||
const [usernameTaken, emailTaken] = await Promise.all([
|
||||
findUserByUsername(req.body.username),
|
||||
|
||||
@@ -1,82 +1,9 @@
|
||||
import { NextPage } from 'next';
|
||||
import { SubmitHandler, useForm } from 'react-hook-form';
|
||||
import { useEffect } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { z } from 'zod';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import FormError from '@/components/ui/forms/FormError';
|
||||
import FormInfo from '@/components/ui/forms/FormInfo';
|
||||
import FormLabel from '@/components/ui/forms/FormLabel';
|
||||
import FormSegment from '@/components/ui/forms/FormSegment';
|
||||
import FormTextInput from '@/components/ui/forms/FormTextInput';
|
||||
import Layout from '@/components/ui/Layout';
|
||||
import LoginValidationSchema from '@/services/user/schema/LoginValidationSchema';
|
||||
import sendLoginUserRequest from '@/requests/sendLoginUserRequest';
|
||||
import useUser from '@/hooks/useUser';
|
||||
|
||||
type LoginT = z.infer<typeof LoginValidationSchema>;
|
||||
const LoginForm = () => {
|
||||
const router = useRouter();
|
||||
const { register, handleSubmit, formState } = useForm<LoginT>({
|
||||
resolver: zodResolver(LoginValidationSchema),
|
||||
defaultValues: {
|
||||
username: '',
|
||||
password: '',
|
||||
},
|
||||
});
|
||||
|
||||
const { errors } = formState;
|
||||
|
||||
const onSubmit: SubmitHandler<LoginT> = async (data) => {
|
||||
try {
|
||||
const response = await sendLoginUserRequest(data);
|
||||
|
||||
router.push(`/users/${response.id}`);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form className="form-control w-9/12 space-y-5" onSubmit={handleSubmit(onSubmit)}>
|
||||
<div>
|
||||
<FormInfo>
|
||||
<FormLabel htmlFor="username">username</FormLabel>
|
||||
<FormError>{errors.username?.message}</FormError>
|
||||
</FormInfo>
|
||||
<FormSegment>
|
||||
<FormTextInput
|
||||
id="username"
|
||||
type="text"
|
||||
formValidationSchema={register('username')}
|
||||
error={!!errors.username}
|
||||
placeholder="username"
|
||||
/>
|
||||
</FormSegment>
|
||||
|
||||
<FormInfo>
|
||||
<FormLabel htmlFor="password">password</FormLabel>
|
||||
<FormError>{errors.password?.message}</FormError>
|
||||
</FormInfo>
|
||||
<FormSegment>
|
||||
<FormTextInput
|
||||
id="password"
|
||||
type="password"
|
||||
formValidationSchema={register('password')}
|
||||
error={!!errors.password}
|
||||
placeholder="password"
|
||||
/>
|
||||
</FormSegment>
|
||||
</div>
|
||||
|
||||
<div className="w-full">
|
||||
<button type="submit" className="btn-primary btn w-full">
|
||||
Login
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
import LoginForm from '@/components/Login/LoginForm';
|
||||
|
||||
const LoginPage: NextPage = () => {
|
||||
const { user } = useUser();
|
||||
@@ -88,7 +15,7 @@ const LoginPage: NextPage = () => {
|
||||
}
|
||||
|
||||
router.push(`/user/current`);
|
||||
}, [user]);
|
||||
}, [user, router]);
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
|
||||
@@ -2,18 +2,21 @@ import DBClient from '@/prisma/DBClient';
|
||||
import { z } from 'zod';
|
||||
import BeerCommentValidationSchema from './schema/CreateBeerCommentValidationSchema';
|
||||
|
||||
const CreateBeerCommentWithUserSchema = BeerCommentValidationSchema.extend({
|
||||
userId: z.string().uuid(),
|
||||
});
|
||||
const createNewBeerComment = async ({
|
||||
content,
|
||||
rating,
|
||||
beerPostId,
|
||||
}: z.infer<typeof BeerCommentValidationSchema>) => {
|
||||
const user = await DBClient.instance.user.findFirstOrThrow();
|
||||
userId,
|
||||
}: z.infer<typeof CreateBeerCommentWithUserSchema>) => {
|
||||
return DBClient.instance.beerComment.create({
|
||||
data: {
|
||||
content,
|
||||
rating,
|
||||
beerPost: { connect: { id: beerPostId } },
|
||||
postedBy: { connect: { id: user.id } },
|
||||
postedBy: { connect: { id: userId } },
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
|
||||
@@ -2,6 +2,10 @@ import DBClient from '@/prisma/DBClient';
|
||||
import { z } from 'zod';
|
||||
import BeerPostValidationSchema from './schema/CreateBeerPostValidationSchema';
|
||||
|
||||
const CreateBeerPostWithUserSchema = BeerPostValidationSchema.extend({
|
||||
userId: z.string().uuid(),
|
||||
});
|
||||
|
||||
const createNewBeerPost = async ({
|
||||
name,
|
||||
description,
|
||||
@@ -9,9 +13,8 @@ const createNewBeerPost = async ({
|
||||
ibu,
|
||||
typeId,
|
||||
breweryId,
|
||||
}: z.infer<typeof BeerPostValidationSchema>) => {
|
||||
const user = await DBClient.instance.user.findFirstOrThrow();
|
||||
|
||||
userId,
|
||||
}: z.infer<typeof CreateBeerPostWithUserSchema>) => {
|
||||
const newBeerPost = await DBClient.instance.beerPost.create({
|
||||
data: {
|
||||
name,
|
||||
@@ -19,7 +22,7 @@ const createNewBeerPost = async ({
|
||||
abv,
|
||||
ibu,
|
||||
type: { connect: { id: typeId } },
|
||||
postedBy: { connect: { id: user.id } },
|
||||
postedBy: { connect: { id: userId } },
|
||||
brewery: { connect: { id: breweryId } },
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user