mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-02-16 10:42:08 +00:00
continue extracting user controllers out of routes
This commit is contained in:
@@ -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,
|
||||
});
|
||||
};
|
||||
|
||||
36
src/controllers/posts/breweries/index.ts
Normal file
36
src/controllers/posts/breweries/index.ts
Normal 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,
|
||||
});
|
||||
};
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
};
|
||||
@@ -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 };
|
||||
}
|
||||
237
src/controllers/users/profile/index.ts
Normal file
237
src/controllers/users/profile/index.ts
Normal 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,
|
||||
});
|
||||
};
|
||||
23
src/controllers/users/profile/types/index.ts
Normal file
23
src/controllers/users/profile/types/index.ts
Normal 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 };
|
||||
}
|
||||
Reference in New Issue
Block a user