Move next js project to archive (#207)

This commit is contained in:
Aaron Po
2026-04-20 02:30:25 -04:00
committed by GitHub
parent 92ec16ce93
commit d47e3ed7f0
347 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,114 @@
import { NextApiResponse } from 'next';
import { NextHandler } from 'next-connect';
import { z } from 'zod';
import ServerError from '@/config/util/ServerError';
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
import {
getBeerPostCommentByIdService,
editBeerPostCommentByIdService,
createBeerPostCommentService,
getAllBeerCommentsService,
deleteBeerCommentByIdService,
} from '@/services/comments/beer-comment';
import {
CommentRequest,
EditAndCreateCommentRequest,
GetAllCommentsRequest,
} from '../types';
export const checkIfBeerCommentOwner = async <T extends CommentRequest>(
req: T,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
next: NextHandler,
) => {
const { commentId } = req.query;
const user = req.user!;
const comment = await getBeerPostCommentByIdService({ beerPostCommentId: commentId });
if (!comment) {
throw new ServerError('Comment not found', 404);
}
if (comment.postedBy.id !== user.id) {
throw new ServerError('You are not authorized to modify this comment', 403);
}
return next();
};
export const editBeerPostComment = async (
req: EditAndCreateCommentRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
const { commentId } = req.query;
await editBeerPostCommentByIdService({ body: req.body, beerPostCommentId: commentId });
res.status(200).json({
success: true,
message: 'Comment updated successfully',
statusCode: 200,
});
};
export const deleteBeerPostComment = async (
req: CommentRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
const { commentId } = req.query;
await deleteBeerCommentByIdService({ beerPostCommentId: commentId });
res.status(200).json({
success: true,
message: 'Comment deleted successfully',
statusCode: 200,
});
};
export const createBeerPostComment = async (
req: EditAndCreateCommentRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
const beerPostId = req.query.postId;
const newBeerComment = await createBeerPostCommentService({
body: req.body,
userId: req.user!.id,
beerPostId,
});
res.status(201).json({
message: 'Beer comment created successfully',
statusCode: 201,
payload: newBeerComment,
success: true,
});
};
export const getAllBeerPostComments = async (
req: GetAllCommentsRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
const beerPostId = req.query.postId;
// eslint-disable-next-line @typescript-eslint/naming-convention
const { page_size, page_num } = req.query;
const { comments, count } = await getAllBeerCommentsService({
beerPostId,
pageNum: parseInt(page_num, 10),
pageSize: parseInt(page_size, 10),
});
res.setHeader('X-Total-Count', count);
res.status(200).json({
message: 'Beer comments fetched successfully',
statusCode: 200,
payload: comments,
success: true,
});
};

View File

@@ -0,0 +1,120 @@
import ServerError from '@/config/util/ServerError';
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
import { NextApiResponse } from 'next';
import { NextHandler } from 'next-connect';
import { z } from 'zod';
import {
updateBeerStyleCommentById,
createNewBeerStyleComment,
getAllBeerStyleComments,
findBeerStyleCommentById,
deleteBeerStyleCommentById,
} from '@/services/comments/beer-style-comment';
import {
CommentRequest,
EditAndCreateCommentRequest,
GetAllCommentsRequest,
} from '../types';
export const checkIfBeerStyleCommentOwner = async <
CommentRequestType extends CommentRequest,
>(
req: CommentRequestType,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
next: NextHandler,
) => {
const { commentId } = req.query;
const user = req.user!;
const beerStyleComment = await findBeerStyleCommentById({
beerStyleCommentId: commentId,
});
if (!beerStyleComment) {
throw new ServerError('Beer style comment not found.', 404);
}
if (beerStyleComment.postedBy.id !== user.id) {
throw new ServerError(
'You are not authorized to modify this beer style comment.',
403,
);
}
return next();
};
export const editBeerStyleComment = async (
req: EditAndCreateCommentRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
await updateBeerStyleCommentById({
beerStyleCommentId: req.query.commentId,
body: req.body,
});
return res.status(200).json({
success: true,
message: 'Comment updated successfully',
statusCode: 200,
});
};
export const deleteBeerStyleComment = async (
req: CommentRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
const { commentId } = req.query;
await deleteBeerStyleCommentById({ beerStyleCommentId: commentId });
res.status(200).json({
success: true,
message: 'Comment deleted successfully',
statusCode: 200,
});
};
export const createComment = async (
req: EditAndCreateCommentRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
const newBeerStyleComment = await createNewBeerStyleComment({
body: req.body,
beerStyleId: req.query.postId,
userId: req.user!.id,
});
res.status(201).json({
message: 'Beer comment created successfully',
statusCode: 201,
payload: newBeerStyleComment,
success: true,
});
};
export const getAll = async (
req: GetAllCommentsRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
const beerStyleId = req.query.postId;
// eslint-disable-next-line @typescript-eslint/naming-convention
const { page_size, page_num } = req.query;
const { comments, count } = await getAllBeerStyleComments({
beerStyleId,
pageNum: parseInt(page_num, 10),
pageSize: parseInt(page_size, 10),
});
res.setHeader('X-Total-Count', count);
res.status(200).json({
message: 'Beer comments fetched successfully',
statusCode: 200,
payload: comments,
success: true,
});
};

View File

@@ -0,0 +1,121 @@
import ServerError from '@/config/util/ServerError';
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
import { NextApiResponse } from 'next';
import { NextHandler } from 'next-connect';
import { z } from 'zod';
import {
getBreweryCommentById,
createNewBreweryComment,
getAllBreweryComments,
deleteBreweryCommentByIdService,
updateBreweryCommentById,
} from '@/services/comments/brewery-comment';
import {
CommentRequest,
EditAndCreateCommentRequest,
GetAllCommentsRequest,
} from '../types';
export const checkIfBreweryCommentOwner = async <
CommentRequestType extends CommentRequest,
>(
req: CommentRequestType,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
next: NextHandler,
) => {
const { commentId } = req.query;
const user = req.user!;
const comment = await getBreweryCommentById({ breweryCommentId: commentId });
if (!comment) {
throw new ServerError('Comment not found', 404);
}
if (comment.postedBy.id !== user.id) {
throw new ServerError('You are not authorized to modify this comment', 403);
}
return next();
};
export const editBreweryPostComment = async (
req: EditAndCreateCommentRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
const { commentId } = req.query;
const updated = await updateBreweryCommentById({
breweryCommentId: commentId,
body: req.body,
});
return res.status(200).json({
success: true,
message: 'Comment updated successfully',
statusCode: 200,
payload: updated,
});
};
export const deleteBreweryPostComment = async (
req: CommentRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
const { commentId } = req.query;
await deleteBreweryCommentByIdService({ breweryCommentId: commentId });
res.status(200).json({
success: true,
message: 'Brewery comment deleted successfully',
statusCode: 200,
});
};
export const createComment = async (
req: EditAndCreateCommentRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
const breweryPostId = req.query.postId;
const user = req.user!;
const newBreweryComment = await createNewBreweryComment({
body: req.body,
breweryPostId,
userId: user.id,
});
res.status(201).json({
message: 'Brewery comment created successfully',
statusCode: 201,
payload: newBreweryComment,
success: true,
});
};
export const getAll = async (
req: GetAllCommentsRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
const breweryPostId = req.query.postId;
// eslint-disable-next-line @typescript-eslint/naming-convention
const { page_size, page_num } = req.query;
const { comments, count } = await getAllBreweryComments({
id: breweryPostId,
pageNum: parseInt(page_num, 10),
pageSize: parseInt(page_size, 10),
});
res.setHeader('X-Total-Count', count);
res.status(200).json({
message: 'Brewery comments fetched successfully',
statusCode: 200,
payload: comments,
success: true,
});
};

View File

@@ -0,0 +1,15 @@
import { UserExtendedNextApiRequest } from '@/config/auth/types';
import CreateCommentValidationSchema from '@/services/schema/CommentSchema/CreateCommentValidationSchema';
import { z } from 'zod';
export interface CommentRequest extends UserExtendedNextApiRequest {
query: { postId: string; commentId: string };
}
export interface EditAndCreateCommentRequest extends CommentRequest {
body: z.infer<typeof CreateCommentValidationSchema>;
}
export interface GetAllCommentsRequest extends UserExtendedNextApiRequest {
query: { postId: string; page_size: string; page_num: string };
}

View File

@@ -0,0 +1,51 @@
import ServerError from '@/config/util/ServerError';
import {
addBeerImagesService,
deleteBeerImageService,
} from '@/services/images/beer-image';
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
import { NextApiResponse } from 'next';
import { z } from 'zod';
import { DeleteImageRequest, UploadImagesRequest } from '../types';
// eslint-disable-next-line import/prefer-default-export
export const processBeerImageData = async (
req: UploadImagesRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
const { files, user, body } = req;
if (!files || !files.length) {
throw new ServerError('No images uploaded', 400);
}
const beerImages = await addBeerImagesService({
beerPostId: req.query.postId,
userId: user!.id,
body,
files,
});
res.status(200).json({
success: true,
message: `Successfully uploaded ${beerImages.length} image${
beerImages.length > 1 ? 's' : ''
}`,
statusCode: 200,
});
};
export const deleteBeerImageData = async (
req: DeleteImageRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
const { id } = req.query;
await deleteBeerImageService({ beerImageId: id });
res.status(200).json({
success: true,
message: `Successfully deleted image with id ${id}`,
statusCode: 200,
});
};

View File

@@ -0,0 +1,34 @@
import ServerError from '@/config/util/ServerError';
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
import { NextApiResponse } from 'next';
import { z } from 'zod';
import { addBreweryImagesService } from '@/services/images/brewery-image';
import { UploadImagesRequest } from '../types';
// eslint-disable-next-line import/prefer-default-export
export const processBreweryImageData = async (
req: UploadImagesRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
const { files, user, body } = req;
if (!files || !files.length) {
throw new ServerError('No images uploaded', 400);
}
const breweryImages = await addBreweryImagesService({
breweryPostId: req.query.postId,
userId: user!.id,
body,
files,
});
res.status(200).json({
success: true,
message: `Successfully uploaded ${breweryImages.length} image${
breweryImages.length > 1 ? 's' : ''
}`,
statusCode: 200,
});
};

View File

@@ -0,0 +1,13 @@
import { UserExtendedNextApiRequest } from '@/config/auth/types';
import ImageMetadataValidationSchema from '@/services/schema/ImageSchema/ImageMetadataValidationSchema';
import { z } from 'zod';
export interface UploadImagesRequest extends UserExtendedNextApiRequest {
files?: Express.Multer.File[];
query: { postId: string };
body: z.infer<typeof ImageMetadataValidationSchema>;
}
export interface DeleteImageRequest extends UserExtendedNextApiRequest {
query: { id: string };
}

View File

@@ -0,0 +1,90 @@
import { UserExtendedNextApiRequest } from '@/config/auth/types';
import ServerError from '@/config/util/ServerError';
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
import { NextApiResponse } from 'next';
import { z } from 'zod';
import { getBeerPostById } from '@/services/posts/beer-post';
import {
findBeerPostLikeByIdService,
createBeerPostLikeService,
removeBeerPostLikeService,
getBeerPostLikeCountService,
} from '@/services/likes/beer-post-like';
import { LikeRequest } from '../types';
export const sendBeerPostLikeRequest = async (
req: LikeRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
const user = req.user!;
const { postId } = req.query;
const beer = await getBeerPostById({ beerPostId: postId });
if (!beer) {
throw new ServerError('Could not find a beer post with that id.', 404);
}
const liked = await findBeerPostLikeByIdService({
beerPostId: beer.id,
likedById: user.id,
});
if (liked) {
await removeBeerPostLikeService({ beerPostLikeId: liked.id });
res.status(200).json({
success: true,
message: 'Successfully unliked beer post.',
statusCode: 200,
payload: { liked: false },
});
return;
}
await createBeerPostLikeService({ beerPostId: beer.id, likedById: user.id });
res.status(200).json({
success: true,
message: 'Successfully liked beer post.',
statusCode: 200,
payload: { liked: true },
});
};
export const getBeerPostLikeCount = async (
req: LikeRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
const { postId } = req.query;
const likeCount = await getBeerPostLikeCountService({ beerPostId: postId });
res.status(200).json({
success: true,
message: 'Successfully retrieved like count.',
statusCode: 200,
payload: { likeCount },
});
};
export const checkIfBeerPostIsLiked = async (
req: UserExtendedNextApiRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
const user = req.user!;
const beerPostId = req.query.postId as string;
const alreadyLiked = await findBeerPostLikeByIdService({
beerPostId,
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 },
});
};

View File

@@ -0,0 +1,82 @@
import ServerError from '@/config/util/ServerError';
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
import { NextApiResponse } from 'next';
import { z } from 'zod';
import { UserExtendedNextApiRequest } from '@/config/auth/types';
import {
createBeerStyleLikeService,
findBeerStyleLikeService,
getBeerStyleLikeCountService,
removeBeerStyleLikeService,
} from '@/services/likes/beer-style-like';
import { getBeerStyleByIdService } from '@/services/posts/beer-style-post';
import { LikeRequest } from '../types';
export const sendBeerStyleLikeRequest = async (
req: LikeRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
const user = req.user!;
const { postId } = req.query;
const beerStyle = await getBeerStyleByIdService({ beerStyleId: postId });
if (!beerStyle) {
throw new ServerError('Could not find a beer style with that id.', 404);
}
const beerStyleLike = await findBeerStyleLikeService({
beerStyleId: beerStyle.id,
likedById: user.id,
});
if (beerStyleLike) {
await removeBeerStyleLikeService({ beerStyleLikeId: beerStyleLike.id });
res.status(200).json({
message: 'Successfully unliked beer style.',
success: true,
statusCode: 200,
});
} else {
await createBeerStyleLikeService({ beerStyleId: beerStyle.id, likedById: user.id });
res.status(200).json({
message: 'Successfully liked beer style.',
success: true,
statusCode: 200,
});
}
};
export const getBeerStyleLikeCountRequest = async (
req: LikeRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
const { postId } = req.query;
const likeCount = await getBeerStyleLikeCountService({ beerStyleId: postId });
res.status(200).json({
success: true,
message: 'Successfully retrieved like count.',
statusCode: 200,
payload: { likeCount },
});
};
export const checkIfBeerStyleIsLiked = async (
req: UserExtendedNextApiRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
const user = req.user!;
const beerStyleId = req.query.id as string;
const alreadyLiked = await findBeerStyleLikeService({
beerStyleId,
likedById: user.id,
});
res.status(200).json({
success: true,
message: alreadyLiked ? 'Beer style is liked.' : 'Beer style is not liked.',
statusCode: 200,
payload: { isLiked: !!alreadyLiked },
});
};

View File

@@ -0,0 +1,96 @@
import ServerError from '@/config/util/ServerError';
import {
createBreweryPostLikeService,
findBreweryPostLikeService,
getBreweryPostLikeCountService,
removeBreweryPostLikeService,
} from '@/services/likes/brewery-post-like';
import { getBreweryPostByIdService } from '@/services/posts/brewery-post';
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
import { NextApiResponse } from 'next';
import { z } from 'zod';
import { LikeRequest } from '../types';
export const sendBreweryPostLikeRequest = async (
req: LikeRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
const { postId } = req.query;
const user = req.user!;
const breweryPost = await getBreweryPostByIdService({ breweryPostId: postId });
if (!breweryPost) {
throw new ServerError('Could not find a brewery post with that id', 404);
}
const like = await findBreweryPostLikeService({
breweryPostId: breweryPost.id,
likedById: user.id,
});
if (like) {
await removeBreweryPostLikeService({ breweryPostLikeId: like.id });
res.status(200).json({
success: true,
message: 'Successfully removed like from brewery post.',
statusCode: 200,
});
return;
}
await createBreweryPostLikeService({
breweryPostId: breweryPost.id,
likedById: user.id,
});
res.status(200).json({
success: true,
message: 'Successfully liked brewery post.',
statusCode: 200,
});
};
export const getBreweryPostLikeCount = async (
req: LikeRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
const { postId } = req.query;
const breweryPost = await getBreweryPostByIdService({ breweryPostId: postId });
if (!breweryPost) {
throw new ServerError('Could not find a brewery post with that id', 404);
}
const likeCount = await getBreweryPostLikeCountService({
breweryPostId: breweryPost.id,
});
res.status(200).json({
success: true,
message: 'Successfully retrieved like count',
statusCode: 200,
payload: { likeCount },
});
};
export const getBreweryPostLikeStatus = async (
req: LikeRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
const user = req.user!;
const { postId } = req.query;
const liked = await findBreweryPostLikeService({
breweryPostId: postId,
likedById: user.id,
});
res.status(200).json({
success: true,
message: liked ? 'Brewery post is liked.' : 'Brewery post is not liked.',
statusCode: 200,
payload: { isLiked: !!liked },
});
};

View File

@@ -0,0 +1,5 @@
import { UserExtendedNextApiRequest } from '@/config/auth/types';
export interface LikeRequest extends UserExtendedNextApiRequest {
query: { postId: string };
}

View File

@@ -0,0 +1,170 @@
import ServerError from '@/config/util/ServerError';
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
import { NextApiResponse } from 'next';
import { NextHandler } from 'next-connect';
import { z } from 'zod';
import { GetAllPostsByConnectedPostId } from '@/controllers/posts/types';
import {
BeerPostRequest,
CreateBeerPostRequest,
EditBeerPostRequest,
GetAllBeerPostsRequest,
GetBeerRecommendationsRequest,
} from '@/controllers/posts/beer-posts/types';
import {
getBeerPostById,
editBeerPostByIdService,
deleteBeerPostByIdService,
getBeerRecommendationsService,
getAllBeerPostsService,
createNewBeerPost,
getBeerPostsByPostedByIdService,
} from '@/services/posts/beer-post';
export const checkIfBeerPostOwner = async <BeerPostRequestType extends BeerPostRequest>(
req: BeerPostRequestType,
res: NextApiResponse,
next: NextHandler,
) => {
const { user, query } = req;
const { postId } = query;
const beerPost = await getBeerPostById({ beerPostId: postId });
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);
}
return next();
};
export const editBeerPost = async (
req: EditBeerPostRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
await editBeerPostByIdService({ beerPostId: req.query.postId, body: req.body });
res.status(200).json({
message: 'Beer post updated successfully',
success: true,
statusCode: 200,
});
};
export const deleteBeerPost = async (req: BeerPostRequest, res: NextApiResponse) => {
const { postId } = req.query;
const deleted = await deleteBeerPostByIdService({ beerPostId: postId });
if (!deleted) {
throw new ServerError('Beer post not found', 404);
}
res.status(200).json({
message: 'Beer post deleted successfully',
success: true,
statusCode: 200,
});
};
export const getBeerPostRecommendations = async (
req: GetBeerRecommendationsRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
const { postId } = req.query;
const beerPost = await getBeerPostById({ beerPostId: postId });
if (!beerPost) {
throw new ServerError('Beer post not found', 404);
}
const pageNum = parseInt(req.query.page_num as string, 10);
const pageSize = parseInt(req.query.page_size as string, 10);
const { beerRecommendations, count } = await getBeerRecommendationsService({
beerPost,
pageNum,
pageSize,
});
res.setHeader('X-Total-Count', count);
res.status(200).json({
success: true,
message: 'Recommendations fetched successfully',
statusCode: 200,
payload: beerRecommendations,
});
};
export const getBeerPosts = async (
req: GetAllBeerPostsRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
const pageNum = parseInt(req.query.page_num, 10);
const pageSize = parseInt(req.query.page_size, 10);
const { beerPosts, count } = await getAllBeerPostsService({ pageNum, pageSize });
res.setHeader('X-Total-Count', count);
res.status(200).json({
message: 'Beer posts retrieved successfully',
statusCode: 200,
payload: beerPosts,
success: true,
});
};
export const createBeerPost = async (
req: CreateBeerPostRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
const { name, description, styleId, abv, ibu, breweryId } = req.body;
const newBeerPost = await createNewBeerPost({
name,
description,
abv,
ibu,
styleId,
breweryId,
userId: req.user!.id,
});
res.status(201).json({
message: 'Beer post created successfully',
statusCode: 201,
payload: newBeerPost,
success: true,
});
};
export const getBeerPostsByUserId = async (
req: GetAllPostsByConnectedPostId,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
const pageNum = parseInt(req.query.page_num, 10);
const pageSize = parseInt(req.query.page_size, 10);
const { id } = req.query;
const { beerPosts, count } = await getBeerPostsByPostedByIdService({
pageNum,
pageSize,
postedById: id,
});
res.setHeader('X-Total-Count', count);
res.status(200).json({
message: `Beer posts by user ${id} fetched successfully`,
statusCode: 200,
payload: beerPosts,
success: true,
});
};

View File

@@ -0,0 +1,25 @@
import { UserExtendedNextApiRequest } from '@/config/auth/types';
import CreateBeerPostValidationSchema from '@/services/posts/beer-post/schema/CreateBeerPostValidationSchema';
import EditBeerPostValidationSchema from '@/services/posts/beer-post/schema/EditBeerPostValidationSchema';
import { NextApiRequest } from 'next';
import { z } from 'zod';
export interface BeerPostRequest extends UserExtendedNextApiRequest {
query: { postId: string };
}
export interface EditBeerPostRequest extends BeerPostRequest {
body: z.infer<typeof EditBeerPostValidationSchema>;
}
export interface GetAllBeerPostsRequest extends NextApiRequest {
query: { page_num: string; page_size: string };
}
export interface GetBeerRecommendationsRequest extends BeerPostRequest {
query: { postId: string; page_num: string; page_size: string };
}
export interface CreateBeerPostRequest extends UserExtendedNextApiRequest {
body: z.infer<typeof CreateBeerPostValidationSchema>;
}

View File

@@ -0,0 +1,105 @@
import { NextApiResponse } from 'next';
import { z } from 'zod';
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
import { getBeerPostsByBeerStyleIdService } from '@/services/posts/beer-post';
import {
createBeerStyleService,
getAllBeerStylesService,
getBeerStyleByIdService,
} from '@/services/posts/beer-style-post';
import { CreateBeerStyleRequest, GetBeerStyleByIdRequest } from './types';
import { GetAllPostsByConnectedPostId, GetAllPostsRequest } from '../types';
export const getBeerStyle = async (
req: GetBeerStyleByIdRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
const { id } = req.query;
const beerStyle = await getBeerStyleByIdService({
beerStyleId: id,
});
res.status(200).json({
message: 'Beer style retrieved successfully.',
statusCode: 200,
payload: beerStyle,
success: true,
});
};
export const getAllBeersByBeerStyle = async (
req: GetAllPostsByConnectedPostId,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
// eslint-disable-next-line @typescript-eslint/naming-convention
const { page_size, page_num, id } = req.query;
const { beerPosts, count } = await getBeerPostsByBeerStyleIdService({
pageNum: parseInt(page_num, 10),
pageSize: parseInt(page_size, 10),
styleId: id,
});
res.setHeader('X-Total-Count', count);
res.status(200).json({
message: `Beers with style id ${id} retrieved successfully.`,
statusCode: 200,
payload: beerPosts,
success: true,
});
};
export const getBeerStyles = async (
req: GetAllPostsRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
const pageNum = parseInt(req.query.page_num, 10);
const pageSize = parseInt(req.query.page_size, 10);
const { beerStyles, beerStyleCount } = await getAllBeerStylesService({
pageNum,
pageSize,
});
res.setHeader('X-Total-Count', beerStyleCount);
res.status(200).json({
message: 'Beer styles retrieved successfully.',
statusCode: 200,
payload: beerStyles,
success: true,
});
};
export const createBeerStyle = async (
req: CreateBeerStyleRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
const { abvRange, description, glasswareId, ibuRange, name } = req.body;
const user = req.user!;
const beerStyle = await createBeerStyleService({
glasswareId,
postedById: user.id,
body: {
abvRange,
description,
ibuRange,
name,
},
});
res.json({
message: 'Beer style created successfully.',
statusCode: 200,
payload: beerStyle,
success: true,
});
};

View File

@@ -0,0 +1,13 @@
import { NextApiRequest } from 'next';
import { UserExtendedNextApiRequest } from '@/config/auth/types';
import { z } from 'zod';
import CreateBeerStyleValidationSchema from '@/services/posts/beer-style-post/schema/CreateBeerStyleValidationSchema';
export interface GetBeerStyleByIdRequest extends NextApiRequest {
query: { id: string };
}
export interface CreateBeerStyleRequest extends UserExtendedNextApiRequest {
body: z.infer<typeof CreateBeerStyleValidationSchema>;
}

View File

@@ -0,0 +1,218 @@
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
import { NextApiResponse } from 'next';
import { z } from 'zod';
import geocode from '@/config/mapbox/geocoder';
import ServerError from '@/config/util/ServerError';
import {
getAllBreweryPostsByPostedByIdService,
getAllBreweryPostsService,
createNewBreweryPostService,
createBreweryPostLocationService,
getMapBreweryPostsService,
getBreweryPostByIdService,
updateBreweryPostService,
deleteBreweryPostService,
} from '@/services/posts/brewery-post';
import { getBeerPostsByBreweryIdService } from '@/services/posts/beer-post';
import { NextHandler } from 'next-connect';
import {
BreweryPostRequest,
CreateBreweryPostRequest,
EditBreweryPostRequest,
GetBreweryPostsRequest,
} from './types';
import { GetAllPostsByConnectedPostId } from '../types';
export const getBreweryPostsByUserId = async (
req: GetAllPostsByConnectedPostId,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
const pageNum = parseInt(req.query.page_num, 10);
const pageSize = parseInt(req.query.page_size, 10);
const { id } = req.query;
const { breweryPosts, count } = await getAllBreweryPostsByPostedByIdService({
pageNum,
pageSize,
postedById: id,
});
res.setHeader('X-Total-Count', count);
res.status(200).json({
message: `Brewery posts by user ${id} fetched successfully`,
statusCode: 200,
payload: breweryPosts,
success: true,
});
};
export const getBreweryPosts = async (
req: GetBreweryPostsRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
const pageNum = parseInt(req.query.page_num, 10);
const pageSize = parseInt(req.query.page_size, 10);
const { breweryPosts, count } = await getAllBreweryPostsService({ pageNum, pageSize });
res.setHeader('X-Total-Count', count);
res.status(200).json({
message: 'Brewery posts retrieved successfully',
statusCode: 200,
payload: breweryPosts,
success: true,
});
};
export const createBreweryPost = async (
req: CreateBreweryPostRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
const { name, description, dateEstablished, address, city, country, region } = req.body;
const userId = req.user!.id;
const fullAddress = `${address}, ${city}, ${region}, ${country}`;
const geocoded = await geocode(fullAddress);
if (!geocoded) {
throw new ServerError('Address is not valid', 400);
}
const [latitude, longitude] = geocoded.center;
const location = await createBreweryPostLocationService({
body: {
address,
city,
country,
stateOrProvince: region,
coordinates: [latitude, longitude],
},
postedById: userId,
});
const newBreweryPost = await createNewBreweryPostService({
name,
description,
locationId: location.id,
dateEstablished,
userId,
});
res.status(201).json({
message: 'Brewery post created successfully',
statusCode: 201,
payload: newBreweryPost,
success: true,
});
};
export const getMapBreweryPosts = async (
req: GetBreweryPostsRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
const pageNum = parseInt(req.query.page_num, 10);
const pageSize = parseInt(req.query.page_size, 10);
const { breweryPosts, count } = await getMapBreweryPostsService({
pageNum,
pageSize,
});
res.setHeader('X-Total-Count', count);
res.status(200).json({
message: 'Brewery posts retrieved successfully',
statusCode: 200,
payload: breweryPosts,
success: true,
});
};
export const getAllBeersByBrewery = async (
req: GetAllPostsByConnectedPostId,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
// eslint-disable-next-line @typescript-eslint/naming-convention
const { page_size, page_num, id } = req.query;
const pageNum = parseInt(page_num, 10);
const pageSize = parseInt(page_size, 10);
const { beerPosts, count } = await getBeerPostsByBreweryIdService({
pageNum,
pageSize,
breweryId: id,
});
res.setHeader('X-Total-Count', count);
res.status(200).json({
message: 'Beers fetched successfully',
statusCode: 200,
payload: beerPosts,
success: true,
});
};
export const checkIfBreweryPostOwner = async (
req: BreweryPostRequest,
res: NextApiResponse,
next: NextHandler,
) => {
const user = req.user!;
const { postId } = req.query;
const breweryPost = await getBreweryPostByIdService({ breweryPostId: postId });
if (!breweryPost) {
throw new ServerError('Brewery post not found', 404);
}
if (breweryPost.postedBy.id !== user.id) {
throw new ServerError('You are not the owner of this brewery post', 403);
}
return next();
};
export const editBreweryPost = async (
req: EditBreweryPostRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
const {
body,
query: { postId },
} = req;
await updateBreweryPostService({ breweryPostId: postId, body });
res.status(200).json({
message: 'Brewery post updated successfully',
success: true,
statusCode: 200,
});
};
export const deleteBreweryPost = async (
req: BreweryPostRequest,
res: NextApiResponse,
) => {
const { postId } = req.query;
const deleted = await deleteBreweryPostService({ breweryPostId: postId });
if (!deleted) {
throw new ServerError('Brewery post not found', 404);
}
res.status(200).json({
message: 'Brewery post deleted successfully',
success: true,
statusCode: 200,
});
};

View File

@@ -0,0 +1,22 @@
import { UserExtendedNextApiRequest } from '@/config/auth/types';
import CreateBreweryPostSchema from '@/services/posts/brewery-post/schema/CreateBreweryPostSchema';
import EditBreweryPostValidationSchema from '@/services/posts/brewery-post/schema/EditBreweryPostValidationSchema';
import PaginatedQueryResponseSchema from '@/services/schema/PaginatedQueryResponseSchema';
import { NextApiRequest } from 'next';
import { z } from 'zod';
export interface GetBreweryPostsRequest extends NextApiRequest {
query: z.infer<typeof PaginatedQueryResponseSchema>;
}
export interface CreateBreweryPostRequest extends UserExtendedNextApiRequest {
body: z.infer<typeof CreateBreweryPostSchema>;
}
export interface BreweryPostRequest extends UserExtendedNextApiRequest {
query: { postId: string };
}
export interface EditBreweryPostRequest extends BreweryPostRequest {
body: z.infer<typeof EditBreweryPostValidationSchema>;
}

View File

@@ -0,0 +1,38 @@
import { NextApiRequest } from 'next';
/** Represents the request object for getting all posts. */
export interface GetAllPostsRequest extends NextApiRequest {
query: { page_size: string; page_num: string };
}
/**
* Represents the request object for getting all posts by a connected post ID.
*
* This may include:
*
* - All beers by a brewery ID
* - All beers by a beer style ID
* - All beer styles by a user ID
* - And more...
*
* @example
* const getAllBeersByBeerStyle = async (
* req: GetAllPostsByConnectedPostId,
* res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
* ) => {
* const { page_size, page_num, id } = req.query;
* // ...
* };
*
* @example
* const getAllBeersByUserId = async (
* req: GetAllPostsByConnectedPostId,
* res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
* ) => {
* const { page_size, page_num, id } = req.query;
* // ...
* };
*/
export interface GetAllPostsByConnectedPostId extends NextApiRequest {
query: { id: string; page_size: string; page_num: string };
}

View File

@@ -0,0 +1,302 @@
import { removeTokenCookie } from '@/config/auth/cookie';
import localStrat from '@/config/auth/localStrat';
import { getLoginSession, setLoginSession } from '@/config/auth/session';
import { UserExtendedNextApiRequest } from '@/config/auth/types';
import ServerError from '@/config/util/ServerError';
import { NextApiRequest, NextApiResponse } from 'next';
import { expressWrapper } from 'next-connect';
import passport from 'passport';
import { z } from 'zod';
import GetUserSchema from '@/services/users/auth/schema/GetUserSchema';
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
import type { NextFunction } from 'express';
import { verifyConfirmationToken } from '@/config/jwt';
import { hashPassword } from '@/config/auth/passwordFns';
import {
createNewUserService,
deleteUserService,
findUserByEmailService,
findUserByUsernameService,
sendConfirmationEmailService,
sendResetPasswordEmailService,
updateUserService,
updateUserPasswordService,
confirmUserService,
} from '@/services/users/auth';
import { EditUserRequest, UserRouteRequest } from '@/controllers/users/profile/types';
import {
CheckEmailRequest,
CheckUsernameRequest,
RegisterUserRequest,
ResetPasswordRequest,
TokenValidationRequest,
UpdatePasswordRequest,
} from './types';
export const authenticateUser = expressWrapper(
async (
req: UserExtendedNextApiRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
next: NextFunction,
) => {
passport.initialize();
passport.use(localStrat);
passport.authenticate(
'local',
{ session: false },
(error: unknown, token: z.infer<typeof GetUserSchema>) => {
if (error) {
next(error);
return;
}
req.user = token;
next();
},
)(req, res, next);
},
);
export const loginUser = async (
req: UserExtendedNextApiRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
const user = req.user!;
await setLoginSession(res, user);
res.status(200).json({
message: 'Login successful.',
payload: user,
statusCode: 200,
success: true,
});
};
export const logoutUser = async (
req: NextApiRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
const session = await getLoginSession(req);
if (!session) {
throw new ServerError('You are not logged in.', 400);
}
removeTokenCookie(res);
res.redirect('/');
};
export const registerUser = async (
req: RegisterUserRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
const [usernameTaken, emailTaken] = (
await Promise.all([
findUserByUsernameService({ username: req.body.username }),
findUserByEmailService({ email: req.body.email }),
])
).map((user) => !!user);
if (usernameTaken) {
throw new ServerError(
'Could not register a user with that username as it is already taken.',
409,
);
}
if (emailTaken) {
throw new ServerError(
'Could not register a user with that email as it is already taken.',
409,
);
}
const user = await createNewUserService(req.body);
await setLoginSession(res, {
id: user.id,
username: user.username,
});
await sendConfirmationEmailService({
email: user.email,
username: user.username,
userId: user.id,
});
res.status(201).json({
success: true,
statusCode: 201,
message: 'User registered successfully.',
payload: user,
});
};
export const confirmUser = async (
req: TokenValidationRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
const { token } = req.query;
const user = req.user!;
const { id } = await verifyConfirmationToken(token);
if (user.accountIsVerified) {
throw new ServerError('Your account is already verified.', 400);
}
if (user.id !== id) {
throw new ServerError('Could not confirm user.', 401);
}
await confirmUserService({ userId: id });
res.status(200).json({
message: 'User confirmed successfully.',
statusCode: 200,
success: true,
});
};
export const resetPassword = async (
req: ResetPasswordRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
const { email } = req.body;
const user = await findUserByEmailService({ email });
if (user) {
await sendResetPasswordEmailService({
email: user.email,
username: user.username,
userId: user.id,
});
}
res.status(200).json({
statusCode: 200,
success: true,
message:
'If an account with that email exists, we have sent you an email to reset your password.',
});
};
export const sendCurrentUser = async (
req: UserExtendedNextApiRequest,
res: NextApiResponse,
) => {
const { user } = req;
res.status(200).json({
message: `Currently logged in as ${user!.username}`,
statusCode: 200,
success: true,
payload: user,
});
};
export const checkEmail = async (req: CheckEmailRequest, res: NextApiResponse) => {
const { email: emailToCheck } = req.query;
const email = await findUserByEmailService({ email: emailToCheck });
res.json({
success: true,
payload: { emailIsTaken: !!email },
statusCode: 200,
message: 'Getting email availability.',
});
};
export const checkUsername = async (req: CheckUsernameRequest, res: NextApiResponse) => {
const { username: usernameToCheck } = req.query;
const username = await findUserByUsernameService({ username: usernameToCheck });
res.json({
success: true,
payload: { usernameIsTaken: !!username },
statusCode: 200,
message: username ? 'Username is taken.' : 'Username is available.',
});
};
export const updatePassword = async (
req: UpdatePasswordRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
const user = req.user!;
const { password } = req.body;
await updateUserPasswordService({
userId: user.id,
password: await hashPassword(password),
});
res.json({
message: 'Updated user password.',
statusCode: 200,
success: true,
});
};
export const resendConfirmation = async (
req: UserExtendedNextApiRequest,
res: NextApiResponse,
) => {
const user = req.user!;
await sendConfirmationEmailService({
userId: user.id,
username: user.username,
email: user.email,
});
res.status(200).json({
message: `Resent the confirmation email for ${user.username}.`,
statusCode: 200,
success: true,
});
};
export const editUserInfo = async (
req: EditUserRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
const { email, firstName, lastName, username } = req.body;
const updatedUser = await updateUserService({
userId: req.user!.id,
data: { email, firstName, lastName, username },
});
res.json({
message: 'User edited successfully',
payload: updatedUser,
success: true,
statusCode: 200,
});
};
export const deleteAccount = async (
req: UserRouteRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
const { id } = req.query;
const deletedUser = await deleteUserService({ userId: id });
if (!deletedUser) {
throw new ServerError('Could not find a user with that id.', 400);
}
res.send({
message: 'Successfully deleted user.',
statusCode: 200,
success: true,
});
};

View File

@@ -0,0 +1,31 @@
import { UserExtendedNextApiRequest } from '@/config/auth/types';
import {
CreateUserValidationSchema,
UpdatePasswordSchema,
} from '@/services/users/auth/schema/CreateUserValidationSchemas';
import TokenValidationSchema from '@/services/users/auth/schema/TokenValidationSchema';
import { NextApiRequest } from 'next';
import { z } from 'zod';
export interface RegisterUserRequest extends NextApiRequest {
body: z.infer<typeof CreateUserValidationSchema>;
}
export interface TokenValidationRequest extends UserExtendedNextApiRequest {
query: z.infer<typeof TokenValidationSchema>;
}
export interface ResetPasswordRequest extends NextApiRequest {
body: { email: string };
}
export interface UpdatePasswordRequest extends UserExtendedNextApiRequest {
body: z.infer<typeof UpdatePasswordSchema>;
}
export interface CheckEmailRequest extends NextApiRequest {
query: { email: string };
}
export interface CheckUsernameRequest extends NextApiRequest {
query: { username: string };
}

View File

@@ -0,0 +1,216 @@
import ServerError from '@/config/util/ServerError';
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
import { NextApiResponse } from 'next';
import { z } from 'zod';
import { NextHandler } from 'next-connect';
import { UserExtendedNextApiRequest } from '@/config/auth/types';
import { findUserByIdService } from '@/services/users/auth';
import {
createUserFollow,
deleteUserFollow,
findUserFollow,
getUsersFollowedByUser,
getUsersFollowingUser,
updateUserAvatar,
updateUserProfileById,
} from '@/services/users/profile';
import {
UserRouteRequest,
GetUserFollowInfoRequest,
EditUserRequest,
UpdateAvatarRequest,
UpdateProfileRequest,
} from './types';
export const followUser = async (
req: UserRouteRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
const { id } = req.query;
const user = await findUserByIdService({ userId: id });
if (!user) {
throw new ServerError('User not found', 404);
}
const currentUser = req.user!;
const userIsFollowedBySessionUser = await findUserFollow({
followerId: currentUser.id,
followingId: id,
});
if (!userIsFollowedBySessionUser) {
await createUserFollow({ followerId: currentUser.id, followingId: id });
res.status(200).json({
message: 'Now following user.',
success: true,
statusCode: 200,
});
return;
}
await deleteUserFollow({ followerId: currentUser.id, followingId: id });
res.status(200).json({
message: 'No longer following user.',
success: true,
statusCode: 200,
});
};
export const getUserFollowers = async (
req: GetUserFollowInfoRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
// eslint-disable-next-line @typescript-eslint/naming-convention
const { id, page_num, page_size } = req.query;
const user = await findUserByIdService({ userId: id });
if (!user) {
throw new ServerError('User not found', 404);
}
const pageNum = parseInt(page_num, 10);
const pageSize = parseInt(page_size, 10);
const { follows, count } = await getUsersFollowingUser({
userId: id,
pageNum,
pageSize,
});
res.setHeader('X-Total-Count', count);
res.json({
message: 'Retrieved users that are followed by queried user',
payload: follows,
success: true,
statusCode: 200,
});
};
export const getUsersFollowed = async (
req: GetUserFollowInfoRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
// eslint-disable-next-line @typescript-eslint/naming-convention
const { id, page_num, page_size } = req.query;
const user = await findUserByIdService({ userId: id });
if (!user) {
throw new ServerError('User not found', 404);
}
const pageNum = parseInt(page_num, 10);
const pageSize = parseInt(page_size, 10);
const { follows, count } = await getUsersFollowedByUser({
userId: id,
pageNum,
pageSize,
});
res.setHeader('X-Total-Count', count);
res.json({
message: 'Retrieved users that are followed by queried user',
payload: follows,
success: true,
statusCode: 200,
});
};
export const checkIfUserIsFollowedBySessionUser = async (
req: GetUserFollowInfoRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
const { id } = req.query;
const user = await findUserByIdService({ userId: id });
if (!user) {
throw new ServerError('User not found', 404);
}
const currentUser = req.user!;
const userFollow = await findUserFollow({
followerId: currentUser.id,
followingId: id,
});
const isFollowed = !!userFollow;
res.status(200).json({
message: isFollowed
? 'User is followed by the session user.'
: 'User is not followed by the session user.',
success: true,
statusCode: 200,
payload: { isFollowed },
});
};
export const checkIfUserCanEditUser = async (
req: EditUserRequest,
res: NextApiResponse,
next: NextHandler,
) => {
const authenticatedUser = req.user!;
const userToUpdate = await findUserByIdService({ userId: req.query.id });
if (!userToUpdate) {
throw new ServerError('User not found', 404);
}
if (authenticatedUser.id !== userToUpdate.id) {
throw new ServerError('You are not permitted to modify this user', 403);
}
return next();
};
export const checkIfUserCanUpdateProfile = async <T extends UserExtendedNextApiRequest>(
req: T,
res: NextApiResponse,
next: NextHandler,
) => {
const user = req.user!;
if (user.id !== req.query.id) {
throw new ServerError('You can only update your own profile.', 403);
}
await next();
};
export const updateAvatar = async (req: UpdateAvatarRequest, res: NextApiResponse) => {
const { file, user } = req;
await updateUserAvatar({
userId: user!.id,
data: { alt: file.originalname, path: file.path, caption: '' },
});
res.status(200).json({
message: 'User avatar updated successfully.',
statusCode: 200,
success: true,
});
};
export const updateProfile = async (req: UpdateProfileRequest, res: NextApiResponse) => {
const user = req.user!;
const { body } = req;
await updateUserProfileById({ userId: user!.id, data: { bio: body.bio } });
res.status(200).json({
message: 'Profile updated successfully.',
statusCode: 200,
success: true,
});
};

View File

@@ -0,0 +1,23 @@
import { UserExtendedNextApiRequest } from '@/config/auth/types';
import EditUserSchema from '@/services/users/auth/schema/EditUserSchema';
import { z } from 'zod';
export interface UserRouteRequest extends UserExtendedNextApiRequest {
query: { id: string };
}
export interface GetUserFollowInfoRequest extends UserExtendedNextApiRequest {
query: { id: string; page_size: string; page_num: string };
}
export interface EditUserRequest extends UserRouteRequest {
body: z.infer<typeof EditUserSchema>;
}
export interface UpdateAvatarRequest extends UserExtendedNextApiRequest {
file: Express.Multer.File;
}
export interface UpdateProfileRequest extends UserExtendedNextApiRequest {
body: { bio: string };
}