From 5c91c6ab0864d68d83a40c46e1143b209178ff8b Mon Sep 17 00:00:00 2001 From: Aaron William Po Date: Sun, 14 May 2023 17:12:14 -0400 Subject: [PATCH] Refactor: move service logic out of api routes and into separate files --- .../data-fetching/beer-posts/useBeerPosts.ts | 1 + .../brewery-posts/useBreweryPosts.ts | 1 + src/pages/api/beer-comments/[id].ts | 18 ++++----- src/pages/api/beers/[id]/images/index.ts | 34 +++++----------- src/pages/api/beers/[id]/like/index.ts | 11 +++--- src/pages/api/beers/[id]/like/is-liked.ts | 17 ++------ .../BeerComment/editBeerCommentById.ts | 22 +++++++++++ .../BeerComment/findBeerCommentById.ts | 11 ++++++ .../BeerImage/processImageDataIntoDB.ts | 39 +++++++++++++++++++ .../BeerPostLike/findBeerPostLikeById.ts | 10 ++++- .../ImageMetadataValidationSchema.ts | 8 ++++ 11 files changed, 118 insertions(+), 54 deletions(-) create mode 100644 src/services/BeerComment/editBeerCommentById.ts create mode 100644 src/services/BeerComment/findBeerCommentById.ts create mode 100644 src/services/BeerImage/processImageDataIntoDB.ts create mode 100644 src/services/types/ImageSchema/ImageMetadataValidationSchema.ts diff --git a/src/hooks/data-fetching/beer-posts/useBeerPosts.ts b/src/hooks/data-fetching/beer-posts/useBeerPosts.ts index 043a35e..67a23c9 100644 --- a/src/hooks/data-fetching/beer-posts/useBeerPosts.ts +++ b/src/hooks/data-fetching/beer-posts/useBeerPosts.ts @@ -49,6 +49,7 @@ const useBeerPosts = ({ pageSize }: { pageSize: number }) => { const { data, error, isLoading, setSize, size } = useSWRInfinite( (index) => `/api/beers?page_num=${index + 1}&page_size=${pageSize}`, fetcher, + { parallel: true }, ); const beerPosts = data?.flatMap((d) => d.beerPosts) ?? []; diff --git a/src/hooks/data-fetching/brewery-posts/useBreweryPosts.ts b/src/hooks/data-fetching/brewery-posts/useBreweryPosts.ts index 18127ee..afc74dc 100644 --- a/src/hooks/data-fetching/brewery-posts/useBreweryPosts.ts +++ b/src/hooks/data-fetching/brewery-posts/useBreweryPosts.ts @@ -46,6 +46,7 @@ const useBreweryPosts = ({ pageSize }: { pageSize: number }) => { const { data, error, isLoading, setSize, size } = useSWRInfinite( (index) => `/api/breweries?page_num=${index + 1}&page_size=${pageSize}`, fetcher, + { parallel: true }, ); const breweryPosts = data?.flatMap((d) => d.breweryPosts) ?? []; diff --git a/src/pages/api/beer-comments/[id].ts b/src/pages/api/beer-comments/[id].ts index bbd9178..bf9129d 100644 --- a/src/pages/api/beer-comments/[id].ts +++ b/src/pages/api/beer-comments/[id].ts @@ -4,8 +4,9 @@ import validateRequest from '@/config/nextConnect/middleware/validateRequest'; import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; import ServerError from '@/config/util/ServerError'; import DBClient from '@/prisma/DBClient'; +import findBeerCommentById from '@/services/BeerComment/findBeerCommentById'; import CreateCommentValidationSchema from '@/services/types/CommentSchema/CreateCommentValidationSchema'; - +import editBeerCommentById from '@/services/BeerComment/editBeerCommentById'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import { NextApiResponse } from 'next'; import { createRouter, NextHandler } from 'next-connect'; @@ -27,9 +28,7 @@ const checkIfCommentOwner = async ( ) => { const { id } = req.query; const user = req.user!; - const comment = await DBClient.instance.beerComment.findUnique({ - where: { id }, - }); + const comment = await findBeerCommentById(id); if (!comment) { throw new ServerError('Comment not found', 404); @@ -48,13 +47,10 @@ const editComment = async ( ) => { const { id } = req.query; - const updated = await DBClient.instance.beerComment.update({ - where: { id }, - data: { - content: req.body.content, - rating: req.body.rating, - updatedAt: new Date(), - }, + const updated = await editBeerCommentById({ + content: req.body.content, + rating: req.body.rating, + id, }); return res.status(200).json({ diff --git a/src/pages/api/beers/[id]/images/index.ts b/src/pages/api/beers/[id]/images/index.ts index 25f31d4..f3dc0e8 100644 --- a/src/pages/api/beers/[id]/images/index.ts +++ b/src/pages/api/beers/[id]/images/index.ts @@ -1,5 +1,3 @@ -import DBClient from '@/prisma/DBClient'; -import { BeerImage } from '@prisma/client'; import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import { UserExtendedNextApiRequest } from '@/config/auth/types'; @@ -14,6 +12,8 @@ import { NextApiResponse } from 'next'; import { z } from 'zod'; import ServerError from '@/config/util/ServerError'; import validateRequest from '@/config/nextConnect/middleware/validateRequest'; +import processImageDataIntoDB from '@/services/BeerImage/processImageDataIntoDB'; +import ImageMetadataValidationSchema from '@/services/types/ImageSchema/ImageMetadataValidationSchema'; const { storage } = cloudinaryConfig; @@ -34,15 +34,10 @@ const uploadMiddleware = expressWrapper( ), ); -const BeerPostImageValidationSchema = z.object({ - caption: z.string(), - alt: z.string(), -}); - interface UploadBeerPostImagesRequest extends UserExtendedNextApiRequest { files?: Express.Multer.File[]; query: { id: string }; - body: z.infer; + body: z.infer; } const processImageData = async ( @@ -54,24 +49,15 @@ const processImageData = async ( if (!files || !files.length) { throw new ServerError('No images uploaded', 400); } - const beerImagePromises: Promise[] = []; - files.forEach((file) => { - beerImagePromises.push( - DBClient.instance.beerImage.create({ - data: { - alt: body.alt, - postedBy: { connect: { id: user!.id } }, - beerPost: { connect: { id: req.query.id } }, - path: file.path, - caption: body.caption, - }, - }), - ); + const beerImages = await processImageDataIntoDB({ + alt: body.alt, + caption: body.caption, + beerPostId: req.query.id, + userId: user!.id, + files, }); - const beerImages = await Promise.all(beerImagePromises); - res.status(200).json({ success: true, message: `Successfully uploaded ${beerImages.length} image${ @@ -90,7 +76,7 @@ router.post( getCurrentUser, // @ts-expect-error uploadMiddleware, - validateRequest({ bodySchema: BeerPostImageValidationSchema }), + validateRequest({ bodySchema: ImageMetadataValidationSchema }), processImageData, ); diff --git a/src/pages/api/beers/[id]/like/index.ts b/src/pages/api/beers/[id]/like/index.ts index dbf76ec..d422617 100644 --- a/src/pages/api/beers/[id]/like/index.ts +++ b/src/pages/api/beers/[id]/like/index.ts @@ -11,7 +11,7 @@ import removeBeerPostLikeById from '@/services/BeerPostLike/removeBeerPostLikeBy import findBeerPostLikeById from '@/services/BeerPostLike/findBeerPostLikeById'; import getCurrentUser from '@/config/nextConnect/middleware/getCurrentUser'; import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; -import DBClient from '@/prisma/DBClient'; +import getBeerPostLikeCount from '@/services/BeerPostLike/getBeerPostLikeCount'; const sendLikeRequest = async ( req: UserExtendedNextApiRequest, @@ -25,7 +25,10 @@ const sendLikeRequest = async ( throw new ServerError('Could not find a beer post with that id', 404); } - const alreadyLiked = await findBeerPostLikeById(beer.id, user.id); + const alreadyLiked = await findBeerPostLikeById({ + beerPostId: beer.id, + likedById: user.id, + }); const jsonResponse = { success: true as const, @@ -50,9 +53,7 @@ const getLikeCount = async ( ) => { const id = req.query.id as string; - const likeCount = await DBClient.instance.beerPostLike.count({ - where: { beerPostId: id }, - }); + const likeCount = await getBeerPostLikeCount(id); res.status(200).json({ success: true, diff --git a/src/pages/api/beers/[id]/like/is-liked.ts b/src/pages/api/beers/[id]/like/is-liked.ts index 09f1b71..2b325b2 100644 --- a/src/pages/api/beers/[id]/like/is-liked.ts +++ b/src/pages/api/beers/[id]/like/is-liked.ts @@ -2,25 +2,20 @@ import getCurrentUser from '@/config/nextConnect/middleware/getCurrentUser'; import { UserExtendedNextApiRequest } from '@/config/auth/types'; import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; import validateRequest from '@/config/nextConnect/middleware/validateRequest'; -import DBClient from '@/prisma/DBClient'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import { NextApiResponse } from 'next'; import { createRouter } from 'next-connect'; import { z } from 'zod'; +import findBeerPostLikeById from '@/services/BeerPostLike/findBeerPostLikeById'; const checkIfLiked = async ( req: UserExtendedNextApiRequest, res: NextApiResponse>, ) => { const user = req.user!; - const id = req.query.id as string; + const beerPostId = req.query.id as string; - const alreadyLiked = await DBClient.instance.beerPostLike.findFirst({ - where: { - beerPostId: id, - likedById: user.id, - }, - }); + const alreadyLiked = await findBeerPostLikeById({ beerPostId, likedById: user.id }); res.status(200).json({ success: true, @@ -37,11 +32,7 @@ const router = createRouter< router.get( getCurrentUser, - validateRequest({ - querySchema: z.object({ - id: z.string().uuid(), - }), - }), + validateRequest({ querySchema: z.object({ id: z.string().uuid() }) }), checkIfLiked, ); diff --git a/src/services/BeerComment/editBeerCommentById.ts b/src/services/BeerComment/editBeerCommentById.ts new file mode 100644 index 0000000..61e4570 --- /dev/null +++ b/src/services/BeerComment/editBeerCommentById.ts @@ -0,0 +1,22 @@ +import DBClient from '@/prisma/DBClient'; + +interface EditBeerCommentByIdArgs { + id: string; + content: string; + rating: number; +} + +const editBeerCommentById = async ({ id, content, rating }: EditBeerCommentByIdArgs) => { + const updated = await DBClient.instance.beerComment.update({ + where: { id }, + data: { + content, + rating, + updatedAt: new Date(), + }, + }); + + return updated; +}; + +export default editBeerCommentById; diff --git a/src/services/BeerComment/findBeerCommentById.ts b/src/services/BeerComment/findBeerCommentById.ts new file mode 100644 index 0000000..1cc1f0a --- /dev/null +++ b/src/services/BeerComment/findBeerCommentById.ts @@ -0,0 +1,11 @@ +import DBClient from '@/prisma/DBClient'; + +const findBeerCommentById = async (id: string) => { + const comment = await DBClient.instance.beerComment.findUnique({ + where: { id }, + }); + + return comment; +}; + +export default findBeerCommentById; diff --git a/src/services/BeerImage/processImageDataIntoDB.ts b/src/services/BeerImage/processImageDataIntoDB.ts new file mode 100644 index 0000000..a59cf2c --- /dev/null +++ b/src/services/BeerImage/processImageDataIntoDB.ts @@ -0,0 +1,39 @@ +import DBClient from '@/prisma/DBClient'; +import { BeerImage } from '@prisma/client'; +import { z } from 'zod'; +import ImageMetadataValidationSchema from '../types/ImageSchema/ImageMetadataValidationSchema'; + +interface ProcessImageDataArgs { + files: Express.Multer.File[]; + alt: z.infer['alt']; + caption: z.infer['caption']; + beerPostId: string; + userId: string; +} + +const processImageDataIntoDB = ({ + alt, + caption, + files, + beerPostId, + userId, +}: ProcessImageDataArgs) => { + const beerImagePromises: Promise[] = []; + files.forEach((file) => { + beerImagePromises.push( + DBClient.instance.beerImage.create({ + data: { + alt, + caption, + postedBy: { connect: { id: userId } }, + beerPost: { connect: { id: beerPostId } }, + path: file.path, + }, + }), + ); + }); + + return Promise.all(beerImagePromises); +}; + +export default processImageDataIntoDB; diff --git a/src/services/BeerPostLike/findBeerPostLikeById.ts b/src/services/BeerPostLike/findBeerPostLikeById.ts index ad5dac9..11e2789 100644 --- a/src/services/BeerPostLike/findBeerPostLikeById.ts +++ b/src/services/BeerPostLike/findBeerPostLikeById.ts @@ -1,6 +1,14 @@ import DBClient from '@/prisma/DBClient'; -const findBeerPostLikeById = async (beerPostId: string, likedById: string) => +interface FindBeerPostLikeByIdArgs { + beerPostId: string; + likedById: string; +} + +const findBeerPostLikeById = async ({ + beerPostId, + likedById, +}: FindBeerPostLikeByIdArgs) => DBClient.instance.beerPostLike.findFirst({ where: { beerPostId, likedById } }); export default findBeerPostLikeById; diff --git a/src/services/types/ImageSchema/ImageMetadataValidationSchema.ts b/src/services/types/ImageSchema/ImageMetadataValidationSchema.ts new file mode 100644 index 0000000..4ae1614 --- /dev/null +++ b/src/services/types/ImageSchema/ImageMetadataValidationSchema.ts @@ -0,0 +1,8 @@ +import { z } from 'zod'; + +const ImageMetadataValidationSchema = z.object({ + caption: z.string().min(1, { message: 'Caption is required.' }), + alt: z.string().min(1, { message: 'Alt text is required.' }), +}); + +export default ImageMetadataValidationSchema;