mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-02-16 20:13:49 +00:00
Restructure codebase to use src directory
This commit is contained in:
102
src/pages/api/beers/[id]/comments/index.ts
Normal file
102
src/pages/api/beers/[id]/comments/index.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import DBClient from '@/prisma/DBClient';
|
||||
import getAllBeerComments from '@/services/BeerComment/getAllBeerComments';
|
||||
import validateRequest from '@/config/nextConnect/middleware/validateRequest';
|
||||
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
|
||||
import { UserExtendedNextApiRequest } from '@/config/auth/types';
|
||||
import NextConnectOptions from '@/config/nextConnect/NextConnectOptions';
|
||||
import createNewBeerComment from '@/services/BeerComment/createNewBeerComment';
|
||||
|
||||
import BeerCommentValidationSchema from '@/services/BeerComment/schema/CreateBeerCommentValidationSchema';
|
||||
|
||||
import { createRouter } from 'next-connect';
|
||||
import { z } from 'zod';
|
||||
import getCurrentUser from '@/config/nextConnect/middleware/getCurrentUser';
|
||||
import { NextApiResponse } from 'next';
|
||||
import BeerCommentQueryResult from '@/services/BeerComment/schema/BeerCommentQueryResult';
|
||||
|
||||
interface CreateCommentRequest extends UserExtendedNextApiRequest {
|
||||
body: z.infer<typeof BeerCommentValidationSchema>;
|
||||
query: { id: string };
|
||||
}
|
||||
|
||||
interface GetAllCommentsRequest extends UserExtendedNextApiRequest {
|
||||
query: { id: string; page_size: string; page_num: string };
|
||||
}
|
||||
|
||||
const createComment = async (
|
||||
req: CreateCommentRequest,
|
||||
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
|
||||
) => {
|
||||
const { content, rating } = req.body;
|
||||
|
||||
const beerPostId = req.query.id;
|
||||
|
||||
const newBeerComment: z.infer<typeof BeerCommentQueryResult> =
|
||||
await createNewBeerComment({
|
||||
content,
|
||||
rating,
|
||||
beerPostId,
|
||||
userId: req.user!.id,
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
message: 'Beer comment created successfully',
|
||||
statusCode: 201,
|
||||
payload: newBeerComment,
|
||||
success: true,
|
||||
});
|
||||
};
|
||||
|
||||
const getAll = async (
|
||||
req: GetAllCommentsRequest,
|
||||
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
|
||||
) => {
|
||||
const beerPostId = req.query.id;
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
const { page_size, page_num } = req.query;
|
||||
|
||||
const comments = await getAllBeerComments(
|
||||
{ id: beerPostId },
|
||||
{ pageSize: parseInt(page_size, 10), pageNum: parseInt(page_num, 10) },
|
||||
);
|
||||
|
||||
const pageCount = await DBClient.instance.beerComment.count({ where: { beerPostId } });
|
||||
|
||||
res.setHeader('X-Total-Count', pageCount);
|
||||
|
||||
res.status(200).json({
|
||||
message: 'Beer comments fetched successfully',
|
||||
statusCode: 200,
|
||||
payload: comments,
|
||||
success: true,
|
||||
});
|
||||
};
|
||||
|
||||
const router = createRouter<
|
||||
// I don't want to use any, but I can't figure out how to get the types to work
|
||||
any,
|
||||
NextApiResponse<z.infer<typeof APIResponseValidationSchema>>
|
||||
>();
|
||||
|
||||
router.post(
|
||||
validateRequest({
|
||||
bodySchema: BeerCommentValidationSchema,
|
||||
querySchema: z.object({ id: z.string().uuid() }),
|
||||
}),
|
||||
getCurrentUser,
|
||||
createComment,
|
||||
);
|
||||
|
||||
router.get(
|
||||
validateRequest({
|
||||
querySchema: z.object({
|
||||
id: z.string().uuid(),
|
||||
page_size: z.coerce.number().int().positive(),
|
||||
page_num: z.coerce.number().int().positive(),
|
||||
}),
|
||||
}),
|
||||
getAll,
|
||||
);
|
||||
|
||||
const handler = router.handler(NextConnectOptions);
|
||||
export default handler;
|
||||
98
src/pages/api/beers/[id]/images/index.ts
Normal file
98
src/pages/api/beers/[id]/images/index.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
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';
|
||||
import { createRouter, expressWrapper } from 'next-connect';
|
||||
|
||||
import getCurrentUser from '@/config/nextConnect/middleware/getCurrentUser';
|
||||
|
||||
import multer from 'multer';
|
||||
|
||||
import cloudinaryConfig from '@/config/cloudinary';
|
||||
import { NextApiResponse } from 'next';
|
||||
import { z } from 'zod';
|
||||
import ServerError from '@/config/util/ServerError';
|
||||
import validateRequest from '@/config/nextConnect/middleware/validateRequest';
|
||||
|
||||
const { storage } = cloudinaryConfig;
|
||||
|
||||
const fileFilter: multer.Options['fileFilter'] = (req, file, cb) => {
|
||||
const { mimetype } = file;
|
||||
|
||||
const isImage = mimetype.startsWith('image/');
|
||||
|
||||
if (!isImage) {
|
||||
cb(null, false);
|
||||
}
|
||||
cb(null, true);
|
||||
};
|
||||
|
||||
const uploadMiddleware = expressWrapper(
|
||||
multer({ storage, fileFilter, limits: { files: 3 } }).array('images'),
|
||||
);
|
||||
|
||||
const BeerPostImageValidationSchema = z.object({
|
||||
caption: z.string(),
|
||||
alt: z.string(),
|
||||
});
|
||||
|
||||
interface UploadBeerPostImagesRequest extends UserExtendedNextApiRequest {
|
||||
files?: Express.Multer.File[];
|
||||
query: { id: string };
|
||||
body: z.infer<typeof BeerPostImageValidationSchema>;
|
||||
}
|
||||
|
||||
const processImageData = async (
|
||||
req: UploadBeerPostImagesRequest,
|
||||
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
|
||||
) => {
|
||||
const { files, user, body } = req;
|
||||
|
||||
if (!files || !files.length) {
|
||||
throw new ServerError('No images uploaded', 400);
|
||||
}
|
||||
const beerImagePromises: Promise<BeerImage>[] = [];
|
||||
|
||||
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 Promise.all(beerImagePromises);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: `Successfully uploaded ${beerImages.length} image${
|
||||
beerImages.length > 1 ? 's' : ''
|
||||
}`,
|
||||
statusCode: 200,
|
||||
});
|
||||
};
|
||||
|
||||
const router = createRouter<
|
||||
UploadBeerPostImagesRequest,
|
||||
NextApiResponse<z.infer<typeof APIResponseValidationSchema>>
|
||||
>();
|
||||
|
||||
router.post(
|
||||
getCurrentUser,
|
||||
// @ts-expect-error
|
||||
uploadMiddleware,
|
||||
validateRequest({ bodySchema: BeerPostImageValidationSchema }),
|
||||
processImageData,
|
||||
);
|
||||
|
||||
const handler = router.handler(NextConnectOptions);
|
||||
export default handler;
|
||||
|
||||
export const config = { api: { bodyParser: false } };
|
||||
90
src/pages/api/beers/[id]/index.ts
Normal file
90
src/pages/api/beers/[id]/index.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import getCurrentUser from '@/config/nextConnect/middleware/getCurrentUser';
|
||||
import getBeerPostById from '@/services/BeerPost/getBeerPostById';
|
||||
import { UserExtendedNextApiRequest } from '@/config/auth/types';
|
||||
import NextConnectOptions from '@/config/nextConnect/NextConnectOptions';
|
||||
import editBeerPostById from '@/services/BeerPost/editBeerPostById';
|
||||
import EditBeerPostValidationSchema from '@/services/BeerPost/schema/EditBeerPostValidationSchema';
|
||||
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
|
||||
import { NextApiResponse } from 'next';
|
||||
import { createRouter, NextHandler } from 'next-connect';
|
||||
import { z } from 'zod';
|
||||
import ServerError from '@/config/util/ServerError';
|
||||
import DBClient from '@/prisma/DBClient';
|
||||
|
||||
interface BeerPostRequest extends UserExtendedNextApiRequest {
|
||||
query: { id: string };
|
||||
}
|
||||
|
||||
interface EditBeerPostRequest extends BeerPostRequest {
|
||||
body: z.infer<typeof EditBeerPostValidationSchema>;
|
||||
}
|
||||
|
||||
const checkIfBeerPostOwner = async (
|
||||
req: BeerPostRequest,
|
||||
res: NextApiResponse,
|
||||
next: NextHandler,
|
||||
) => {
|
||||
const { user, query } = req;
|
||||
const { id } = query;
|
||||
|
||||
const beerPost = await getBeerPostById(id);
|
||||
|
||||
if (!beerPost) {
|
||||
throw new ServerError('Beer post not found', 404);
|
||||
}
|
||||
|
||||
if (beerPost.postedBy.id !== user!.id) {
|
||||
throw new ServerError('You cannot edit that beer post.', 403);
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
const editBeerPost = async (
|
||||
req: EditBeerPostRequest,
|
||||
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
|
||||
) => {
|
||||
const {
|
||||
body,
|
||||
query: { id },
|
||||
} = req;
|
||||
|
||||
await editBeerPostById(id, body);
|
||||
|
||||
res.status(200).json({
|
||||
message: 'Beer post updated successfully',
|
||||
success: true,
|
||||
statusCode: 200,
|
||||
});
|
||||
};
|
||||
|
||||
const deleteBeerPost = async (req: BeerPostRequest, res: NextApiResponse) => {
|
||||
const {
|
||||
query: { id },
|
||||
} = req;
|
||||
|
||||
const deleted = await DBClient.instance.beerPost.delete({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
if (!deleted) {
|
||||
throw new ServerError('Beer post not found', 404);
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
message: 'Beer post deleted successfully',
|
||||
success: true,
|
||||
statusCode: 200,
|
||||
});
|
||||
};
|
||||
const router = createRouter<
|
||||
EditBeerPostRequest,
|
||||
NextApiResponse<z.infer<typeof APIResponseValidationSchema>>
|
||||
>();
|
||||
|
||||
router.put(getCurrentUser, checkIfBeerPostOwner, editBeerPost);
|
||||
router.delete(getCurrentUser, checkIfBeerPostOwner, deleteBeerPost);
|
||||
|
||||
const handler = router.handler(NextConnectOptions);
|
||||
|
||||
export default handler;
|
||||
83
src/pages/api/beers/[id]/like/index.ts
Normal file
83
src/pages/api/beers/[id]/like/index.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import validateRequest from '@/config/nextConnect/middleware/validateRequest';
|
||||
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
|
||||
import getBeerPostById from '@/services/BeerPost/getBeerPostById';
|
||||
import { UserExtendedNextApiRequest } from '@/config/auth/types';
|
||||
import { createRouter } from 'next-connect';
|
||||
import { z } from 'zod';
|
||||
import { NextApiRequest, NextApiResponse } from 'next';
|
||||
import ServerError from '@/config/util/ServerError';
|
||||
import createBeerPostLike from '@/services/BeerPostLike/createBeerPostLike';
|
||||
import removeBeerPostLikeById from '@/services/BeerPostLike/removeBeerPostLikeById';
|
||||
import findBeerPostLikeById from '@/services/BeerPostLike/findBeerPostLikeById';
|
||||
import getCurrentUser from '@/config/nextConnect/middleware/getCurrentUser';
|
||||
import NextConnectOptions from '@/config/nextConnect/NextConnectOptions';
|
||||
import DBClient from '@/prisma/DBClient';
|
||||
|
||||
const sendLikeRequest = async (
|
||||
req: UserExtendedNextApiRequest,
|
||||
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
|
||||
) => {
|
||||
const user = req.user!;
|
||||
const id = req.query.id as string;
|
||||
|
||||
const beer = await getBeerPostById(id);
|
||||
if (!beer) {
|
||||
throw new ServerError('Could not find a beer post with that id', 404);
|
||||
}
|
||||
|
||||
const alreadyLiked = await findBeerPostLikeById(beer.id, user.id);
|
||||
|
||||
const jsonResponse = {
|
||||
success: true as const,
|
||||
message: '',
|
||||
statusCode: 200 as const,
|
||||
};
|
||||
|
||||
if (alreadyLiked) {
|
||||
await removeBeerPostLikeById(alreadyLiked.id);
|
||||
jsonResponse.message = 'Successfully unliked beer post';
|
||||
} else {
|
||||
await createBeerPostLike({ id, user });
|
||||
jsonResponse.message = 'Successfully liked beer post';
|
||||
}
|
||||
|
||||
res.status(200).json(jsonResponse);
|
||||
};
|
||||
|
||||
const getLikeCount = async (
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
|
||||
) => {
|
||||
const id = req.query.id as string;
|
||||
|
||||
const likes = await DBClient.instance.beerPostLike.count({
|
||||
where: { beerPostId: id },
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: 'Successfully retrieved like count.',
|
||||
statusCode: 200,
|
||||
payload: { likeCount: likes },
|
||||
});
|
||||
};
|
||||
|
||||
const router = createRouter<
|
||||
UserExtendedNextApiRequest,
|
||||
NextApiResponse<z.infer<typeof APIResponseValidationSchema>>
|
||||
>();
|
||||
|
||||
router.post(
|
||||
getCurrentUser,
|
||||
validateRequest({ querySchema: z.object({ id: z.string().uuid() }) }),
|
||||
sendLikeRequest,
|
||||
);
|
||||
|
||||
router.get(
|
||||
validateRequest({ querySchema: z.object({ id: z.string().uuid() }) }),
|
||||
getLikeCount,
|
||||
);
|
||||
|
||||
const handler = router.handler(NextConnectOptions);
|
||||
|
||||
export default handler;
|
||||
49
src/pages/api/beers/[id]/like/is-liked.ts
Normal file
49
src/pages/api/beers/[id]/like/is-liked.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
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';
|
||||
|
||||
const checkIfLiked = async (
|
||||
req: UserExtendedNextApiRequest,
|
||||
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
|
||||
) => {
|
||||
const user = req.user!;
|
||||
const id = req.query.id as string;
|
||||
|
||||
const alreadyLiked = await DBClient.instance.beerPostLike.findFirst({
|
||||
where: {
|
||||
beerPostId: id,
|
||||
likedById: user.id,
|
||||
},
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: alreadyLiked ? 'Beer post is liked.' : 'Beer post is not liked.',
|
||||
statusCode: 200,
|
||||
payload: { isLiked: !!alreadyLiked },
|
||||
});
|
||||
};
|
||||
|
||||
const router = createRouter<
|
||||
UserExtendedNextApiRequest,
|
||||
NextApiResponse<z.infer<typeof APIResponseValidationSchema>>
|
||||
>();
|
||||
|
||||
router.get(
|
||||
getCurrentUser,
|
||||
validateRequest({
|
||||
querySchema: z.object({
|
||||
id: z.string().uuid(),
|
||||
}),
|
||||
}),
|
||||
checkIfLiked,
|
||||
);
|
||||
|
||||
const handler = router.handler(NextConnectOptions);
|
||||
export default handler;
|
||||
52
src/pages/api/beers/create.ts
Normal file
52
src/pages/api/beers/create.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { UserExtendedNextApiRequest } from '@/config/auth/types';
|
||||
import validateRequest from '@/config/nextConnect/middleware/validateRequest';
|
||||
import { createRouter } from 'next-connect';
|
||||
import createNewBeerPost from '@/services/BeerPost/createNewBeerPost';
|
||||
import CreateBeerPostValidationSchema from '@/services/BeerPost/schema/CreateBeerPostValidationSchema';
|
||||
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
|
||||
import { NextApiResponse } from 'next';
|
||||
import { z } from 'zod';
|
||||
import NextConnectOptions from '@/config/nextConnect/NextConnectOptions';
|
||||
import getCurrentUser from '@/config/nextConnect/middleware/getCurrentUser';
|
||||
|
||||
interface CreateBeerPostRequest extends UserExtendedNextApiRequest {
|
||||
body: z.infer<typeof CreateBeerPostValidationSchema>;
|
||||
}
|
||||
|
||||
const createBeerPost = async (
|
||||
req: CreateBeerPostRequest,
|
||||
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
|
||||
) => {
|
||||
const { name, description, typeId, abv, ibu, breweryId } = req.body;
|
||||
|
||||
const newBeerPost = await createNewBeerPost({
|
||||
name,
|
||||
description,
|
||||
abv,
|
||||
ibu,
|
||||
typeId,
|
||||
breweryId,
|
||||
userId: req.user!.id,
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
message: 'Beer post created successfully',
|
||||
statusCode: 201,
|
||||
payload: newBeerPost,
|
||||
success: true,
|
||||
});
|
||||
};
|
||||
|
||||
const router = createRouter<
|
||||
CreateBeerPostRequest,
|
||||
NextApiResponse<z.infer<typeof APIResponseValidationSchema>>
|
||||
>();
|
||||
|
||||
router.post(
|
||||
validateRequest({ bodySchema: CreateBeerPostValidationSchema }),
|
||||
getCurrentUser,
|
||||
createBeerPost,
|
||||
);
|
||||
|
||||
const handler = router.handler(NextConnectOptions);
|
||||
export default handler;
|
||||
58
src/pages/api/beers/search.ts
Normal file
58
src/pages/api/beers/search.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import validateRequest from '@/config/nextConnect/middleware/validateRequest';
|
||||
import NextConnectOptions from '@/config/nextConnect/NextConnectOptions';
|
||||
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
|
||||
import { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { createRouter } from 'next-connect';
|
||||
import { z } from 'zod';
|
||||
import DBClient from '@/prisma/DBClient';
|
||||
import beerPostQueryResult from '@/services/BeerPost/schema/BeerPostQueryResult';
|
||||
|
||||
const SearchSchema = z.object({
|
||||
search: z.string().min(1),
|
||||
});
|
||||
|
||||
interface SearchAPIRequest extends NextApiRequest {
|
||||
query: z.infer<typeof SearchSchema>;
|
||||
}
|
||||
|
||||
const search = async (req: SearchAPIRequest, res: NextApiResponse) => {
|
||||
const { search: query } = req.query;
|
||||
|
||||
const beers: z.infer<typeof beerPostQueryResult>[] =
|
||||
await DBClient.instance.beerPost.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
ibu: true,
|
||||
abv: true,
|
||||
createdAt: true,
|
||||
description: true,
|
||||
postedBy: { select: { username: true, id: true } },
|
||||
brewery: { select: { name: true, id: true } },
|
||||
type: { select: { name: true, id: true } },
|
||||
beerImages: { select: { alt: true, path: true, caption: true, id: true } },
|
||||
},
|
||||
where: {
|
||||
OR: [
|
||||
{ name: { contains: query, mode: 'insensitive' } },
|
||||
{ description: { contains: query, mode: 'insensitive' } },
|
||||
|
||||
{ brewery: { name: { contains: query, mode: 'insensitive' } } },
|
||||
{ type: { name: { contains: query, mode: 'insensitive' } } },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
res.status(200).json(beers);
|
||||
};
|
||||
|
||||
const router = createRouter<
|
||||
SearchAPIRequest,
|
||||
NextApiResponse<z.infer<typeof APIResponseValidationSchema>>
|
||||
>();
|
||||
|
||||
router.get(validateRequest({}), search);
|
||||
|
||||
const handler = router.handler(NextConnectOptions);
|
||||
|
||||
export default handler;
|
||||
Reference in New Issue
Block a user