continue extracting user controllers out of routes

This commit is contained in:
Aaron William Po
2023-12-06 20:30:11 -05:00
parent 2ff39613cd
commit c7d5c65ffb
31 changed files with 584 additions and 543 deletions

View File

@@ -10,6 +10,7 @@ import getBeerRecommendations from '@/services/BeerPost/getBeerRecommendations';
import getAllBeerPosts from '@/services/BeerPost/getAllBeerPosts';
import DBClient from '@/prisma/DBClient';
import createNewBeerPost from '@/services/BeerPost/createNewBeerPost';
import getBeerPostsByPostedById from '@/services/BeerPost/getBeerPostsByPostedById';
import {
BeerPostRequest,
CreateBeerPostRequest,
@@ -17,6 +18,7 @@ import {
GetAllBeerPostsRequest,
GetBeerRecommendationsRequest,
} from './types';
import { GetPostsByUserIdRequest } from '../types';
export const checkIfBeerPostOwner = async <BeerPostRequestType extends BeerPostRequest>(
req: BeerPostRequestType,
@@ -141,3 +143,32 @@ export const createBeerPost = async (
success: true,
});
};
export const getBeerPostsByUserId = async (
req: GetPostsByUserIdRequest,
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 = await getBeerPostsByPostedById({
pageNum,
pageSize,
postedById: id,
});
const beerPostCount = await DBClient.instance.beerPost.count({
where: { postedBy: { id } },
});
res.setHeader('X-Total-Count', beerPostCount);
res.status(200).json({
message: `Beer posts by user ${id} fetched successfully`,
statusCode: 200,
payload: beerPosts,
success: true,
});
};

View File

@@ -0,0 +1,36 @@
import DBClient from '@/prisma/DBClient';
import getAllBreweryPostsByPostedById from '@/services/BreweryPost/getAllBreweryPostsByPostedById';
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
import { NextApiResponse } from 'next';
import { z } from 'zod';
import { GetPostsByUserIdRequest } from '../types';
// eslint-disable-next-line import/prefer-default-export
export const getBreweryPostsByUserId = async (
req: GetPostsByUserIdRequest,
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 = await getAllBreweryPostsByPostedById({
pageNum,
pageSize,
postedById: id,
});
const breweryPostCount = await DBClient.instance.breweryPost.count({
where: { postedBy: { id } },
});
res.setHeader('X-Total-Count', breweryPostCount);
res.status(200).json({
message: `Brewery posts by user ${id} fetched successfully`,
statusCode: 200,
payload: breweryPosts,
success: true,
});
};

View File

@@ -3,3 +3,7 @@ import { NextApiRequest } from 'next';
export interface GetAllPostsRequest extends NextApiRequest {
query: { page_size: string; page_num: string };
}
export interface GetPostsByUserIdRequest extends NextApiRequest {
query: { id: string; page_size: string; page_num: string };
}

View File

@@ -19,11 +19,17 @@ import { verifyConfirmationToken } from '@/config/jwt';
import updateUserToBeConfirmedById from '@/services/User/updateUserToBeConfirmedById';
import DBClient from '@/prisma/DBClient';
import sendResetPasswordEmail from '@/services/User/sendResetPasswordEmail';
import { hashPassword } from '@/config/auth/passwordFns';
import deleteUserById from '@/services/User/deleteUserById';
import {
CheckEmailRequest,
CheckUsernameRequest,
RegisterUserRequest,
ResetPasswordRequest,
TokenValidationRequest,
UpdatePasswordRequest,
} from './types';
import { EditUserRequest, UserRouteRequest } from '../profile/types';
export const authenticateUser = expressWrapper(
async (
@@ -165,3 +171,135 @@ export const resetPassword = async (
'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 findUserByEmail(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 findUserByUsername(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 { password } = req.body;
const hash = await hashPassword(password);
const user = req.user!;
await DBClient.instance.user.update({
data: { hash },
where: { id: user.id },
});
res.json({
message: 'Updated user password.',
statusCode: 200,
success: true,
});
};
export const resendConfirmation = async (
req: UserExtendedNextApiRequest,
res: NextApiResponse,
) => {
const user = req.user!;
await sendConfirmationEmail(user);
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 [usernameIsTaken, emailIsTaken] = await Promise.all([
findUserByUsername(username),
findUserByEmail(email),
]);
const emailChanged = req.user!.email !== email;
const usernameChanged = req.user!.username !== username;
if (emailIsTaken && emailChanged) {
throw new ServerError('Email is already taken', 400);
}
if (usernameIsTaken && usernameChanged) {
throw new ServerError('Username is already taken', 400);
}
const updatedUser = await DBClient.instance.user.update({
where: { id: req.user!.id },
data: {
email,
firstName,
lastName,
username,
accountIsVerified: emailChanged ? false : undefined,
},
});
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 deleteUserById(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

@@ -1,5 +1,8 @@
import { UserExtendedNextApiRequest } from '@/config/auth/types';
import { CreateUserValidationSchema } from '@/services/User/schema/CreateUserValidationSchemas';
import {
CreateUserValidationSchema,
UpdatePasswordSchema,
} from '@/services/User/schema/CreateUserValidationSchemas';
import TokenValidationSchema from '@/services/User/schema/TokenValidationSchema';
import { NextApiRequest } from 'next';
import { z } from 'zod';
@@ -15,3 +18,14 @@ export interface TokenValidationRequest extends UserExtendedNextApiRequest {
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,237 @@
import ServerError from '@/config/util/ServerError';
import DBClient from '@/prisma/DBClient';
import findUserById from '@/services/User/findUserById';
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
import { NextApiResponse } from 'next';
import { z } from 'zod';
import getUsersFollowingUser from '@/services/UserFollows/getUsersFollowingUser';
import getUsersFollowedByUser from '@/services/UserFollows/getUsersFollowedByUser';
import { NextHandler } from 'next-connect';
import updateUserAvatarById, {
UpdateUserAvatarByIdParams,
} from '@/services/UserAccount/UpdateUserAvatarByIdParams';
import { UserExtendedNextApiRequest } from '@/config/auth/types';
import updateUserProfileById from '@/services/User/updateUserProfileById';
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 findUserById(id);
if (!user) {
throw new ServerError('User not found', 404);
}
const currentUser = req.user!;
const userIsFollowedBySessionUser = await DBClient.instance.userFollow.findFirst({
where: {
followerId: currentUser.id,
followingId: id,
},
});
if (!userIsFollowedBySessionUser) {
await DBClient.instance.userFollow.create({
data: { followerId: currentUser.id, followingId: id },
});
res.status(200).json({
message: 'Now following user.',
success: true,
statusCode: 200,
});
return;
}
await DBClient.instance.userFollow.delete({
where: {
followerId_followingId: {
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 findUserById(id);
if (!user) {
throw new ServerError('User not found', 404);
}
const pageNum = parseInt(page_num, 10);
const pageSize = parseInt(page_size, 10);
const following = await getUsersFollowingUser({
userId: id,
pageNum,
pageSize,
});
const followingCount = await DBClient.instance.userFollow.count({
where: { following: { id } },
});
res.setHeader('X-Total-Count', followingCount);
res.json({
message: 'Retrieved users that are followed by queried user',
payload: following,
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 findUserById(id);
if (!user) {
throw new ServerError('User not found', 404);
}
const pageNum = parseInt(page_num, 10);
const pageSize = parseInt(page_size, 10);
const following = await getUsersFollowedByUser({
userId: id,
pageNum,
pageSize,
});
const followingCount = await DBClient.instance.userFollow.count({
where: { follower: { id } },
});
res.setHeader('X-Total-Count', followingCount);
res.json({
message: 'Retrieved users that are followed by queried user',
payload: following,
success: true,
statusCode: 200,
});
};
export const checkIfUserIsFollowedBySessionUser = async (
req: GetUserFollowInfoRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
const { id } = req.query;
const user = await findUserById(id);
if (!user) {
throw new ServerError('User not found', 404);
}
const currentUser = req.user!;
const userIsFollowedBySessionUser = await DBClient.instance.userFollow.findFirst({
where: { followerId: currentUser.id, followingId: id },
});
if (!userIsFollowedBySessionUser) {
res.status(200).json({
message: 'User is not followed by the current user.',
success: true,
statusCode: 200,
payload: { isFollowed: false },
});
return;
}
res.status(200).json({
message: 'User is followed by the current user.',
success: true,
statusCode: 200,
payload: { isFollowed: true },
});
};
export const checkIfUserCanEditUser = async (
req: EditUserRequest,
res: NextApiResponse,
next: NextHandler,
) => {
const authenticatedUser = req.user!;
const userToUpdate = await findUserById(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;
const avatar: UpdateUserAvatarByIdParams['data']['avatar'] = {
alt: file.originalname,
path: file.path,
caption: '',
};
await updateUserAvatarById({ id: user!.id, data: { avatar } });
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({ id: 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/User/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 };
}