This commit is contained in:
Aaron William Po
2023-10-07 13:27:01 -04:00
parent 5b287ed2ac
commit 2ee12d351f
21 changed files with 346 additions and 210 deletions

View File

@@ -45,12 +45,7 @@ const editBeerPost = async (
req: EditBeerPostRequest, req: EditBeerPostRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>, res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => { ) => {
const { await editBeerPostById({ id: req.query.id, data: req.body });
body,
query: { id },
} = req;
await editBeerPostById(id, body);
res.status(200).json({ res.status(200).json({
message: 'Beer post updated successfully', message: 'Beer post updated successfully',
@@ -62,8 +57,7 @@ const editBeerPost = async (
const deleteBeerPost = async (req: BeerPostRequest, res: NextApiResponse) => { const deleteBeerPost = async (req: BeerPostRequest, res: NextApiResponse) => {
const { id } = req.query; const { id } = req.query;
const deleted = deleteBeerPostById(id); const deleted = deleteBeerPostById({ beerPostId: id });
if (!deleted) { if (!deleted) {
throw new ServerError('Beer post not found', 404); throw new ServerError('Beer post not found', 404);
} }
@@ -74,12 +68,14 @@ const deleteBeerPost = async (req: BeerPostRequest, res: NextApiResponse) => {
statusCode: 200, statusCode: 200,
}); });
}; };
const router = createRouter< const router = createRouter<
EditBeerPostRequest, EditBeerPostRequest,
NextApiResponse<z.infer<typeof APIResponseValidationSchema>> NextApiResponse<z.infer<typeof APIResponseValidationSchema>>
>(); >();
router.put( router
.put(
validateRequest({ validateRequest({
bodySchema: EditBeerPostValidationSchema, bodySchema: EditBeerPostValidationSchema,
querySchema: z.object({ id: z.string() }), querySchema: z.object({ id: z.string() }),
@@ -87,13 +83,13 @@ router.put(
getCurrentUser, getCurrentUser,
checkIfBeerPostOwner, checkIfBeerPostOwner,
editBeerPost, editBeerPost,
); )
router.delete( .delete(
validateRequest({ querySchema: z.object({ id: z.string() }) }), validateRequest({ querySchema: z.object({ id: z.string() }) }),
getCurrentUser, getCurrentUser,
checkIfBeerPostOwner, checkIfBeerPostOwner,
deleteBeerPost, deleteBeerPost,
); );
const handler = router.handler(NextConnectOptions); const handler = router.handler(NextConnectOptions);

View File

@@ -19,7 +19,7 @@ const getBeerPosts = async (
const pageNum = parseInt(req.query.page_num, 10); const pageNum = parseInt(req.query.page_num, 10);
const pageSize = parseInt(req.query.page_size, 10); const pageSize = parseInt(req.query.page_size, 10);
const beerPosts = await getAllBeerPosts(pageNum, pageSize); const beerPosts = await getAllBeerPosts({ pageNum, pageSize });
const beerPostCount = await DBClient.instance.beerPost.count(); const beerPostCount = await DBClient.instance.beerPost.count();
res.setHeader('X-Total-Count', beerPostCount); res.setHeader('X-Total-Count', beerPostCount);

View File

@@ -26,6 +26,7 @@ const search = async (req: SearchAPIRequest, res: NextApiResponse) => {
ibu: true, ibu: true,
abv: true, abv: true,
createdAt: true, createdAt: true,
updatedAt: true,
description: true, description: true,
postedBy: { select: { username: true, id: true } }, postedBy: { select: { username: true, id: true } },
brewery: { select: { name: true, id: true } }, brewery: { select: { name: true, id: true } },

View File

@@ -29,6 +29,7 @@ const getAllBeersByBrewery = async (
ibu: true, ibu: true,
abv: true, abv: true,
createdAt: true, createdAt: true,
updatedAt: true,
description: true, description: true,
postedBy: { select: { username: true, id: true } }, postedBy: { select: { username: true, id: true } },
brewery: { select: { name: true, id: true } }, brewery: { select: { name: true, id: true } },

View File

@@ -19,7 +19,7 @@ const getBreweryPosts = async (
const pageNum = parseInt(req.query.page_num, 10); const pageNum = parseInt(req.query.page_num, 10);
const pageSize = parseInt(req.query.page_size, 10); const pageSize = parseInt(req.query.page_size, 10);
const breweryPosts = await getAllBreweryPosts(pageNum, pageSize); const breweryPosts = await getAllBreweryPosts({ pageNum, pageSize });
const breweryPostCount = await DBClient.instance.breweryPost.count(); const breweryPostCount = await DBClient.instance.breweryPost.count();
res.setHeader('X-Total-Count', breweryPostCount); res.setHeader('X-Total-Count', breweryPostCount);

View File

@@ -1,8 +0,0 @@
import { z } from 'zod';
const CreateCommentValidationSchema = z.object({
userId: z.string().uuid(),
beerPostId: z.string().uuid(),
});
export default CreateCommentValidationSchema;

View File

@@ -7,7 +7,7 @@ const CreateBeerPostWithUserSchema = CreateBeerPostValidationSchema.extend({
userId: z.string().cuid(), userId: z.string().cuid(),
}); });
const createNewBeerPost = async ({ const createNewBeerPost = ({
name, name,
description, description,
abv, abv,
@@ -15,9 +15,10 @@ const createNewBeerPost = async ({
styleId, styleId,
breweryId, breweryId,
userId, userId,
}: z.infer<typeof CreateBeerPostWithUserSchema>) => { }: z.infer<typeof CreateBeerPostWithUserSchema>): Promise<
const newBeerPost: z.infer<typeof BeerPostQueryResult> = z.infer<typeof BeerPostQueryResult>
await DBClient.instance.beerPost.create({ > => {
return DBClient.instance.beerPost.create({
data: { data: {
name, name,
description, description,
@@ -34,13 +35,13 @@ const createNewBeerPost = async ({
abv: true, abv: true,
ibu: true, ibu: true,
createdAt: true, createdAt: true,
updatedAt: true,
beerImages: { select: { id: true, path: true, caption: true, alt: true } }, beerImages: { select: { id: true, path: true, caption: true, alt: true } },
brewery: { select: { id: true, name: true } }, brewery: { select: { id: true, name: true } },
style: { select: { id: true, name: true, description: true } }, style: { select: { id: true, name: true, description: true } },
postedBy: { select: { id: true, username: true } }, postedBy: { select: { id: true, username: true } },
}, },
}); });
return newBeerPost;
}; };
export default createNewBeerPost; export default createNewBeerPost;

View File

@@ -2,27 +2,29 @@ import DBClient from '@/prisma/DBClient';
import { z } from 'zod'; import { z } from 'zod';
import BeerPostQueryResult from './schema/BeerPostQueryResult'; import BeerPostQueryResult from './schema/BeerPostQueryResult';
const deleteBeerPostById = async ( interface DeleteBeerPostByIdArgs {
id: string, beerPostId: string;
): Promise<z.infer<typeof BeerPostQueryResult> | null> => { }
const deleted = await DBClient.instance.beerPost.delete({
where: { id }, const deleteBeerPostById = ({
beerPostId,
}: DeleteBeerPostByIdArgs): Promise<z.infer<typeof BeerPostQueryResult> | null> => {
return DBClient.instance.beerPost.delete({
where: { id: beerPostId },
select: { select: {
abv: true,
createdAt: true,
description: true,
ibu: true,
id: true, id: true,
name: true, name: true,
brewery: { select: { id: true, name: true } }, updatedAt: true,
description: true,
beerImages: { select: { id: true, path: true, caption: true, alt: true } }, beerImages: { select: { id: true, path: true, caption: true, alt: true } },
ibu: true,
abv: true,
style: { select: { id: true, name: true, description: true } }, style: { select: { id: true, name: true, description: true } },
postedBy: { select: { id: true, username: true } }, postedBy: { select: { id: true, username: true } },
createdAt: true, brewery: { select: { id: true, name: true } },
updatedAt: true,
}, },
}); });
return deleted;
}; };
export default deleteBeerPostById; export default deleteBeerPostById;

View File

@@ -1,10 +1,36 @@
import DBClient from '@/prisma/DBClient'; import DBClient from '@/prisma/DBClient';
import { z } from 'zod'; import { z } from 'zod';
import EditBeerPostValidationSchema from './schema/EditBeerPostValidationSchema'; import EditBeerPostValidationSchema from './schema/EditBeerPostValidationSchema';
import BeerPostQueryResult from './schema/BeerPostQueryResult';
const schema = EditBeerPostValidationSchema.omit({ id: true }); const schema = EditBeerPostValidationSchema.omit({ id: true, styleId: true });
export default async function editBeerPostById(id: string, data: z.infer<typeof schema>) { interface EditBeerPostByIdArgs {
const beerPost = await DBClient.instance.beerPost.update({ where: { id }, data }); id: string;
return beerPost; data: z.infer<typeof schema>;
} }
const editBeerPostById = ({
id,
data: { abv, ibu, name, description },
}: EditBeerPostByIdArgs): Promise<z.infer<typeof BeerPostQueryResult>> => {
return DBClient.instance.beerPost.update({
where: { id },
data: { abv, ibu, name, description },
select: {
id: true,
name: true,
description: true,
abv: true,
ibu: true,
createdAt: true,
updatedAt: true,
beerImages: { select: { id: true, path: true, caption: true, alt: true } },
brewery: { select: { id: true, name: true } },
style: { select: { id: true, name: true, description: true } },
postedBy: { select: { id: true, username: true } },
},
});
};
export default editBeerPostById;

View File

@@ -4,11 +4,16 @@ import { z } from 'zod';
const prisma = DBClient.instance; const prisma = DBClient.instance;
const getAllBeerPosts = async (pageNum: number, pageSize: number) => { interface GetAllBeerPostsArgs {
const skip = (pageNum - 1) * pageSize; pageNum: number;
pageSize: number;
}
const beerPosts: z.infer<typeof BeerPostQueryResult>[] = await prisma.beerPost.findMany( const getAllBeerPosts = ({
{ pageNum,
pageSize,
}: GetAllBeerPostsArgs): Promise<z.infer<typeof BeerPostQueryResult>[]> => {
return prisma.beerPost.findMany({
select: { select: {
id: true, id: true,
name: true, name: true,
@@ -16,18 +21,16 @@ const getAllBeerPosts = async (pageNum: number, pageSize: number) => {
abv: true, abv: true,
description: true, description: true,
createdAt: true, createdAt: true,
updatedAt: true,
style: { select: { name: true, id: true, description: true } }, style: { select: { name: true, id: true, description: true } },
brewery: { select: { name: true, id: true } }, brewery: { select: { name: true, id: true } },
postedBy: { select: { id: true, username: true } }, postedBy: { select: { id: true, username: true } },
beerImages: { select: { path: true, caption: true, id: true, alt: true } }, beerImages: { select: { path: true, caption: true, id: true, alt: true } },
}, },
take: pageSize, take: pageSize,
skip, skip: (pageNum - 1) * pageSize,
orderBy: { createdAt: 'desc' }, orderBy: { createdAt: 'desc' },
}, });
);
return beerPosts;
}; };
export default getAllBeerPosts; export default getAllBeerPosts;

View File

@@ -4,15 +4,17 @@ import { z } from 'zod';
const prisma = DBClient.instance; const prisma = DBClient.instance;
const getBeerPostById = async (id: string) => { const getBeerPostById = async (
const beerPost: z.infer<typeof BeerPostQueryResult> | null = id: string,
await prisma.beerPost.findFirst({ ): Promise<z.infer<typeof BeerPostQueryResult> | null> => {
return prisma.beerPost.findFirst({
select: { select: {
id: true, id: true,
name: true, name: true,
ibu: true, ibu: true,
abv: true, abv: true,
createdAt: true, createdAt: true,
updatedAt: true,
description: true, description: true,
postedBy: { select: { username: true, id: true } }, postedBy: { select: { username: true, id: true } },
brewery: { select: { name: true, id: true } }, brewery: { select: { name: true, id: true } },
@@ -21,8 +23,6 @@ const getBeerPostById = async (id: string) => {
}, },
where: { id }, where: { id },
}); });
return beerPost;
}; };
export default getBeerPostById; export default getBeerPostById;

View File

@@ -13,7 +13,10 @@ const getBeerRecommendations = async ({
beerPost, beerPost,
pageNum, pageNum,
pageSize, pageSize,
}: GetBeerRecommendationsArgs) => { }: GetBeerRecommendationsArgs): Promise<{
beerRecommendations: z.infer<typeof BeerPostQueryResult>[];
count: number;
}> => {
const skip = (pageNum - 1) * pageSize; const skip = (pageNum - 1) * pageSize;
const take = pageSize; const take = pageSize;
@@ -30,6 +33,7 @@ const getBeerRecommendations = async ({
abv: true, abv: true,
description: true, description: true,
createdAt: true, createdAt: true,
updatedAt: true,
style: { select: { name: true, id: true, description: true } }, style: { select: { name: true, id: true, description: true } },
brewery: { select: { name: true, id: true } }, brewery: { select: { name: true, id: true } },
postedBy: { select: { id: true, username: true } }, postedBy: { select: { id: true, username: true } },

View File

@@ -13,7 +13,7 @@ const BeerPostQueryResult = z.object({
style: z.object({ id: z.string(), name: z.string(), description: z.string() }), style: z.object({ id: z.string(), name: z.string(), description: z.string() }),
postedBy: z.object({ id: z.string(), username: z.string() }), postedBy: z.object({ id: z.string(), username: z.string() }),
createdAt: z.coerce.date(), createdAt: z.coerce.date(),
updatedAt: z.coerce.date().optional(), updatedAt: z.coerce.date().nullable(),
}); });
export default BeerPostQueryResult; export default BeerPostQueryResult;

View File

@@ -2,9 +2,15 @@ import { z } from 'zod';
import DBClient from '@/prisma/DBClient'; import DBClient from '@/prisma/DBClient';
import BeerStyleQueryResult from './schema/BeerStyleQueryResult'; import BeerStyleQueryResult from './schema/BeerStyleQueryResult';
const deleteBeerStyleById = async (id: string) => { interface DeleteBeerStyleByIdArgs {
beerStyleId: string;
}
const deleteBeerStyleById = async ({
beerStyleId,
}: DeleteBeerStyleByIdArgs): Promise<z.infer<typeof BeerStyleQueryResult> | null> => {
const deleted = await DBClient.instance.beerStyle.delete({ const deleted = await DBClient.instance.beerStyle.delete({
where: { id }, where: { id: beerStyleId },
select: { select: {
id: true, id: true,
name: true, name: true,
@@ -18,7 +24,11 @@ const deleteBeerStyleById = async (id: string) => {
}, },
}); });
return deleted as z.infer<typeof BeerStyleQueryResult> | null; /**
* Prisma does not support tuples, so we have to typecast the ibuRange and abvRange
* fields to [number, number] in order to satisfy the zod schema.
*/
return deleted as Awaited<ReturnType<typeof deleteBeerStyleById>>;
}; };
export default deleteBeerStyleById; export default deleteBeerStyleById;

View File

@@ -0,0 +1,30 @@
import DBClient from '@/prisma/DBClient';
import { z } from 'zod';
import BeerStyleQueryResult from './schema/BeerStyleQueryResult';
const editBeerStyleById = async (
id: string,
): Promise<z.infer<typeof BeerStyleQueryResult> | null> => {
const beerStyle = await DBClient.instance.beerStyle.findUnique({
where: { id },
select: {
id: true,
name: true,
postedBy: { select: { id: true, username: true } },
createdAt: true,
updatedAt: true,
abvRange: true,
ibuRange: true,
description: true,
glassware: { select: { id: true, name: true } },
},
});
/**
* Prisma does not support tuples, so we have to typecast the ibuRange and abvRange
* fields to [number, number] in order to satisfy the zod schema.
*/
return beerStyle as Awaited<ReturnType<typeof editBeerStyleById>>;
};
export default editBeerStyleById;

View File

@@ -2,14 +2,16 @@ import DBClient from '@/prisma/DBClient';
import { z } from 'zod'; import { z } from 'zod';
import BeerStyleQueryResult from './schema/BeerStyleQueryResult'; import BeerStyleQueryResult from './schema/BeerStyleQueryResult';
interface GetAllBeerStylesArgs {
pageNum: number;
pageSize: number;
}
const getAllBeerStyles = async ({ const getAllBeerStyles = async ({
pageNum, pageNum,
pageSize, pageSize,
}: { }: GetAllBeerStylesArgs): Promise<z.infer<typeof BeerStyleQueryResult>[]> => {
pageNum: number; const beerStyles = await DBClient.instance.beerStyle.findMany({
pageSize: number;
}): Promise<z.infer<typeof BeerStyleQueryResult>[]> =>
DBClient.instance.beerStyle.findMany({
take: pageSize, take: pageSize,
skip: (pageNum - 1) * pageSize, skip: (pageNum - 1) * pageSize,
select: { select: {
@@ -23,6 +25,13 @@ const getAllBeerStyles = async ({
description: true, description: true,
glassware: { select: { id: true, name: true } }, glassware: { select: { id: true, name: true } },
}, },
}) as ReturnType<typeof getAllBeerStyles>; });
/**
* Prisma does not support tuples, so we have to typecast the ibuRange and abvRange
* fields to [number, number] in order to satisfy the zod schema.
*/
return beerStyles as Awaited<ReturnType<typeof getAllBeerStyles>>;
};
export default getAllBeerStyles; export default getAllBeerStyles;

View File

@@ -2,8 +2,10 @@ import DBClient from '@/prisma/DBClient';
import { z } from 'zod'; import { z } from 'zod';
import BeerStyleQueryResult from './schema/BeerStyleQueryResult'; import BeerStyleQueryResult from './schema/BeerStyleQueryResult';
const getBeerStyleById = async (id: string) => { const getBeerStyleById = async (
const beerStyle = (await DBClient.instance.beerStyle.findUnique({ id: string,
): Promise<z.infer<typeof BeerStyleQueryResult> | null> => {
const beerStyle = await DBClient.instance.beerStyle.findUnique({
where: { id }, where: { id },
select: { select: {
id: true, id: true,
@@ -16,9 +18,13 @@ const getBeerStyleById = async (id: string) => {
description: true, description: true,
glassware: { select: { id: true, name: true } }, glassware: { select: { id: true, name: true } },
}, },
})) as z.infer<typeof BeerStyleQueryResult> | null; });
return beerStyle; /**
* Prisma does not support tuples, so we have to typecast the ibuRange and abvRange
* fields to [number, number] in order to satisfy the zod schema.
*/
return beerStyle as Awaited<ReturnType<typeof getBeerStyleById>>;
}; };
export default getBeerStyleById; export default getBeerStyleById;

View File

@@ -0,0 +1,46 @@
import { z } from 'zod';
const CreateBeerStyleValidationSchema = z.object({
glasswareId: z
.string()
.cuid({
message: 'Glassware ID must be a valid CUID.',
})
.min(1, { message: 'Glassware ID is required.' }),
description: z
.string()
.min(1, { message: 'Description is required.' })
.max(500, { message: 'Description must be less than or equal to 500 characters.' }),
ibuRange: z
.tuple([
z
.number()
.min(0, { message: 'IBU range minimum must be greater than or equal to 0.' })
.max(100, { message: 'IBU range minimum must be less than or equal to 100.' }),
z
.number()
.min(0, { message: 'IBU range maximum must be greater than or equal to 0.' })
.max(100, { message: 'IBU range maximum must be less than or equal to 100.' }),
])
.refine((ibuRange) => ibuRange[0] <= ibuRange[1], {
message: 'IBU range minimum must be less than or equal to maximum.',
}),
abvRange: z
.tuple([
z
.number()
.min(0, { message: 'ABV range minimum must be greater than or equal to 0.' }),
z
.number()
.min(0, { message: 'ABV range maximum must be greater than or equal to 0.' }),
])
.refine((abvRange) => abvRange[0] <= abvRange[1], {
message: 'ABV range minimum must be less than or equal to maximum.',
}),
name: z
.string()
.min(1, { message: 'Name is required.' })
.max(100, { message: 'Name must be less than or equal to 100 characters.' }),
});
export default CreateBeerStyleValidationSchema;

View File

@@ -19,9 +19,10 @@ const createNewBreweryPost = async ({
locationId, locationId,
name, name,
userId, userId,
}: z.infer<typeof CreateNewBreweryPostWithUserAndLocationSchema>) => { }: z.infer<typeof CreateNewBreweryPostWithUserAndLocationSchema>): Promise<
const breweryPost: z.infer<typeof BreweryPostQueryResult> = z.infer<typeof BreweryPostQueryResult>
(await DBClient.instance.breweryPost.create({ > => {
const post = (await DBClient.instance.breweryPost.create({
data: { data: {
name, name,
description, description,
@@ -47,9 +48,8 @@ const createNewBreweryPost = async ({
}, },
}, },
}, },
})) as z.infer<typeof BreweryPostQueryResult>; })) as Awaited<ReturnType<typeof createNewBreweryPost>>;
return breweryPost; return post;
}; };
export default createNewBreweryPost; export default createNewBreweryPost;

View File

@@ -5,14 +5,16 @@ import { z } from 'zod';
const prisma = DBClient.instance; const prisma = DBClient.instance;
const getAllBreweryPosts = async (pageNum?: number, pageSize?: number) => { const getAllBreweryPosts = async ({
const skip = pageNum && pageSize ? (pageNum - 1) * pageSize : undefined; pageNum,
const take = pageNum && pageSize ? pageSize : undefined; pageSize,
}: {
const breweryPosts: z.infer<typeof BreweryPostQueryResult>[] = pageNum: number;
(await prisma.breweryPost.findMany({ pageSize: number;
skip, }): Promise<z.infer<typeof BreweryPostQueryResult>[]> => {
take, const breweryPosts = await prisma.breweryPost.findMany({
take: pageSize,
skip: (pageNum - 1) * pageSize,
select: { select: {
id: true, id: true,
location: { location: {
@@ -32,9 +34,13 @@ const getAllBreweryPosts = async (pageNum?: number, pageSize?: number) => {
dateEstablished: true, dateEstablished: true,
}, },
orderBy: { createdAt: 'desc' }, orderBy: { createdAt: 'desc' },
})) as z.infer<typeof BreweryPostQueryResult>[]; });
return breweryPosts; /**
* Prisma does not support tuples, so we have to typecast the coordinates field to
* [number, number] in order to satisfy the zod schema.
*/
return breweryPosts as Awaited<ReturnType<typeof getAllBreweryPosts>>;
}; };
export default getAllBreweryPosts; export default getAllBreweryPosts;

View File

@@ -5,8 +5,7 @@ import { z } from 'zod';
const prisma = DBClient.instance; const prisma = DBClient.instance;
const getBreweryPostById = async (id: string) => { const getBreweryPostById = async (id: string) => {
const breweryPost: z.infer<typeof BreweryPostQueryResult> | null = const breweryPost = await prisma.breweryPost.findFirst({
(await prisma.breweryPost.findFirst({
select: { select: {
id: true, id: true,
location: { location: {
@@ -26,9 +25,13 @@ const getBreweryPostById = async (id: string) => {
dateEstablished: true, dateEstablished: true,
}, },
where: { id }, where: { id },
})) as z.infer<typeof BreweryPostQueryResult> | null; });
return breweryPost; /**
* Prisma does not support tuples, so we have to typecast the coordinates field to
* [number, number] in order to satisfy the zod schema.
*/
return breweryPost as z.infer<typeof BreweryPostQueryResult> | null;
}; };
export default getBreweryPostById; export default getBreweryPostById;