Refactor: move service logic out of api routes and into separate files

This commit is contained in:
Aaron William Po
2023-05-14 17:12:14 -04:00
parent 60e76089f3
commit 5c91c6ab08
11 changed files with 118 additions and 54 deletions

View File

@@ -49,6 +49,7 @@ const useBeerPosts = ({ pageSize }: { pageSize: number }) => {
const { data, error, isLoading, setSize, size } = useSWRInfinite( const { data, error, isLoading, setSize, size } = useSWRInfinite(
(index) => `/api/beers?page_num=${index + 1}&page_size=${pageSize}`, (index) => `/api/beers?page_num=${index + 1}&page_size=${pageSize}`,
fetcher, fetcher,
{ parallel: true },
); );
const beerPosts = data?.flatMap((d) => d.beerPosts) ?? []; const beerPosts = data?.flatMap((d) => d.beerPosts) ?? [];

View File

@@ -46,6 +46,7 @@ const useBreweryPosts = ({ pageSize }: { pageSize: number }) => {
const { data, error, isLoading, setSize, size } = useSWRInfinite( const { data, error, isLoading, setSize, size } = useSWRInfinite(
(index) => `/api/breweries?page_num=${index + 1}&page_size=${pageSize}`, (index) => `/api/breweries?page_num=${index + 1}&page_size=${pageSize}`,
fetcher, fetcher,
{ parallel: true },
); );
const breweryPosts = data?.flatMap((d) => d.breweryPosts) ?? []; const breweryPosts = data?.flatMap((d) => d.breweryPosts) ?? [];

View File

@@ -4,8 +4,9 @@ import validateRequest from '@/config/nextConnect/middleware/validateRequest';
import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; import NextConnectOptions from '@/config/nextConnect/NextConnectOptions';
import ServerError from '@/config/util/ServerError'; import ServerError from '@/config/util/ServerError';
import DBClient from '@/prisma/DBClient'; import DBClient from '@/prisma/DBClient';
import findBeerCommentById from '@/services/BeerComment/findBeerCommentById';
import CreateCommentValidationSchema from '@/services/types/CommentSchema/CreateCommentValidationSchema'; import CreateCommentValidationSchema from '@/services/types/CommentSchema/CreateCommentValidationSchema';
import editBeerCommentById from '@/services/BeerComment/editBeerCommentById';
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
import { NextApiResponse } from 'next'; import { NextApiResponse } from 'next';
import { createRouter, NextHandler } from 'next-connect'; import { createRouter, NextHandler } from 'next-connect';
@@ -27,9 +28,7 @@ const checkIfCommentOwner = async (
) => { ) => {
const { id } = req.query; const { id } = req.query;
const user = req.user!; const user = req.user!;
const comment = await DBClient.instance.beerComment.findUnique({ const comment = await findBeerCommentById(id);
where: { id },
});
if (!comment) { if (!comment) {
throw new ServerError('Comment not found', 404); throw new ServerError('Comment not found', 404);
@@ -48,13 +47,10 @@ const editComment = async (
) => { ) => {
const { id } = req.query; const { id } = req.query;
const updated = await DBClient.instance.beerComment.update({ const updated = await editBeerCommentById({
where: { id }, content: req.body.content,
data: { rating: req.body.rating,
content: req.body.content, id,
rating: req.body.rating,
updatedAt: new Date(),
},
}); });
return res.status(200).json({ return res.status(200).json({

View File

@@ -1,5 +1,3 @@
import DBClient from '@/prisma/DBClient';
import { BeerImage } from '@prisma/client';
import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; import NextConnectOptions from '@/config/nextConnect/NextConnectOptions';
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
import { UserExtendedNextApiRequest } from '@/config/auth/types'; import { UserExtendedNextApiRequest } from '@/config/auth/types';
@@ -14,6 +12,8 @@ import { NextApiResponse } from 'next';
import { z } from 'zod'; import { z } from 'zod';
import ServerError from '@/config/util/ServerError'; import ServerError from '@/config/util/ServerError';
import validateRequest from '@/config/nextConnect/middleware/validateRequest'; import validateRequest from '@/config/nextConnect/middleware/validateRequest';
import processImageDataIntoDB from '@/services/BeerImage/processImageDataIntoDB';
import ImageMetadataValidationSchema from '@/services/types/ImageSchema/ImageMetadataValidationSchema';
const { storage } = cloudinaryConfig; const { storage } = cloudinaryConfig;
@@ -34,15 +34,10 @@ const uploadMiddleware = expressWrapper(
), ),
); );
const BeerPostImageValidationSchema = z.object({
caption: z.string(),
alt: z.string(),
});
interface UploadBeerPostImagesRequest extends UserExtendedNextApiRequest { interface UploadBeerPostImagesRequest extends UserExtendedNextApiRequest {
files?: Express.Multer.File[]; files?: Express.Multer.File[];
query: { id: string }; query: { id: string };
body: z.infer<typeof BeerPostImageValidationSchema>; body: z.infer<typeof ImageMetadataValidationSchema>;
} }
const processImageData = async ( const processImageData = async (
@@ -54,24 +49,15 @@ const processImageData = async (
if (!files || !files.length) { if (!files || !files.length) {
throw new ServerError('No images uploaded', 400); throw new ServerError('No images uploaded', 400);
} }
const beerImagePromises: Promise<BeerImage>[] = [];
files.forEach((file) => { const beerImages = await processImageDataIntoDB({
beerImagePromises.push( alt: body.alt,
DBClient.instance.beerImage.create({ caption: body.caption,
data: { beerPostId: req.query.id,
alt: body.alt, userId: user!.id,
postedBy: { connect: { id: user!.id } }, files,
beerPost: { connect: { id: req.query.id } },
path: file.path,
caption: body.caption,
},
}),
);
}); });
const beerImages = await Promise.all(beerImagePromises);
res.status(200).json({ res.status(200).json({
success: true, success: true,
message: `Successfully uploaded ${beerImages.length} image${ message: `Successfully uploaded ${beerImages.length} image${
@@ -90,7 +76,7 @@ router.post(
getCurrentUser, getCurrentUser,
// @ts-expect-error // @ts-expect-error
uploadMiddleware, uploadMiddleware,
validateRequest({ bodySchema: BeerPostImageValidationSchema }), validateRequest({ bodySchema: ImageMetadataValidationSchema }),
processImageData, processImageData,
); );

View File

@@ -11,7 +11,7 @@ import removeBeerPostLikeById from '@/services/BeerPostLike/removeBeerPostLikeBy
import findBeerPostLikeById from '@/services/BeerPostLike/findBeerPostLikeById'; import findBeerPostLikeById from '@/services/BeerPostLike/findBeerPostLikeById';
import getCurrentUser from '@/config/nextConnect/middleware/getCurrentUser'; import getCurrentUser from '@/config/nextConnect/middleware/getCurrentUser';
import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; import NextConnectOptions from '@/config/nextConnect/NextConnectOptions';
import DBClient from '@/prisma/DBClient'; import getBeerPostLikeCount from '@/services/BeerPostLike/getBeerPostLikeCount';
const sendLikeRequest = async ( const sendLikeRequest = async (
req: UserExtendedNextApiRequest, req: UserExtendedNextApiRequest,
@@ -25,7 +25,10 @@ const sendLikeRequest = async (
throw new ServerError('Could not find a beer post with that id', 404); 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 = { const jsonResponse = {
success: true as const, success: true as const,
@@ -50,9 +53,7 @@ const getLikeCount = async (
) => { ) => {
const id = req.query.id as string; const id = req.query.id as string;
const likeCount = await DBClient.instance.beerPostLike.count({ const likeCount = await getBeerPostLikeCount(id);
where: { beerPostId: id },
});
res.status(200).json({ res.status(200).json({
success: true, success: true,

View File

@@ -2,25 +2,20 @@ import getCurrentUser from '@/config/nextConnect/middleware/getCurrentUser';
import { UserExtendedNextApiRequest } from '@/config/auth/types'; import { UserExtendedNextApiRequest } from '@/config/auth/types';
import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; import NextConnectOptions from '@/config/nextConnect/NextConnectOptions';
import validateRequest from '@/config/nextConnect/middleware/validateRequest'; import validateRequest from '@/config/nextConnect/middleware/validateRequest';
import DBClient from '@/prisma/DBClient';
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
import { NextApiResponse } from 'next'; import { NextApiResponse } from 'next';
import { createRouter } from 'next-connect'; import { createRouter } from 'next-connect';
import { z } from 'zod'; import { z } from 'zod';
import findBeerPostLikeById from '@/services/BeerPostLike/findBeerPostLikeById';
const checkIfLiked = async ( const checkIfLiked = async (
req: UserExtendedNextApiRequest, req: UserExtendedNextApiRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>, res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => { ) => {
const user = req.user!; 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({ const alreadyLiked = await findBeerPostLikeById({ beerPostId, likedById: user.id });
where: {
beerPostId: id,
likedById: user.id,
},
});
res.status(200).json({ res.status(200).json({
success: true, success: true,
@@ -37,11 +32,7 @@ const router = createRouter<
router.get( router.get(
getCurrentUser, getCurrentUser,
validateRequest({ validateRequest({ querySchema: z.object({ id: z.string().uuid() }) }),
querySchema: z.object({
id: z.string().uuid(),
}),
}),
checkIfLiked, checkIfLiked,
); );

View File

@@ -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;

View File

@@ -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;

View File

@@ -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<typeof ImageMetadataValidationSchema>['alt'];
caption: z.infer<typeof ImageMetadataValidationSchema>['caption'];
beerPostId: string;
userId: string;
}
const processImageDataIntoDB = ({
alt,
caption,
files,
beerPostId,
userId,
}: ProcessImageDataArgs) => {
const beerImagePromises: Promise<BeerImage>[] = [];
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;

View File

@@ -1,6 +1,14 @@
import DBClient from '@/prisma/DBClient'; 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 } }); DBClient.instance.beerPostLike.findFirst({ where: { beerPostId, likedById } });
export default findBeerPostLikeById; export default findBeerPostLikeById;

View File

@@ -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;