mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-02-16 10:42:08 +00:00
Update user auth services
This commit is contained in:
@@ -3,4 +3,3 @@ Disallow: /api/
|
|||||||
Disallow: /login/
|
Disallow: /login/
|
||||||
Disallow: /register/
|
Disallow: /register/
|
||||||
Disallow: /users/
|
Disallow: /users/
|
||||||
Disallow: /account/
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import findUserByUsername from '@/services/users/auth/findUserByUsername';
|
|
||||||
import Local from 'passport-local';
|
import Local from 'passport-local';
|
||||||
|
import { findUserByUsername } from '@/services/users/auth';
|
||||||
import ServerError from '../util/ServerError';
|
import ServerError from '../util/ServerError';
|
||||||
import { validatePassword } from './passwordFns';
|
import { validatePassword } from './passwordFns';
|
||||||
|
|
||||||
const localStrat = new Local.Strategy(async (username, password, done) => {
|
const localStrat = new Local.Strategy(async (username, password, done) => {
|
||||||
try {
|
try {
|
||||||
const user = await findUserByUsername(username);
|
const user = await findUserByUsername({ username });
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new ServerError('Username or password is incorrect.', 401);
|
throw new ServerError('Username or password is incorrect.', 401);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { NextApiResponse } from 'next';
|
import { NextApiResponse } from 'next';
|
||||||
import { NextHandler } from 'next-connect';
|
import { NextHandler } from 'next-connect';
|
||||||
import findUserById from '@/services/users/auth/findUserById';
|
|
||||||
import ServerError from '@/config/util/ServerError';
|
import ServerError from '@/config/util/ServerError';
|
||||||
import { getLoginSession } from '../../auth/session';
|
import { getLoginSession } from '../../auth/session';
|
||||||
import { UserExtendedNextApiRequest } from '../../auth/types';
|
import { UserExtendedNextApiRequest } from '../../auth/types';
|
||||||
|
import { findUserById } from '@/services/users/auth';
|
||||||
|
|
||||||
/** Get the current user from the session. Adds the user to the request object. */
|
/** Get the current user from the session. Adds the user to the request object. */
|
||||||
const getCurrentUser = async (
|
const getCurrentUser = async (
|
||||||
@@ -12,7 +13,7 @@ const getCurrentUser = async (
|
|||||||
next: NextHandler,
|
next: NextHandler,
|
||||||
) => {
|
) => {
|
||||||
const session = await getLoginSession(req);
|
const session = await getLoginSession(req);
|
||||||
const user = await findUserById(session?.id);
|
const user = await findUserById({ userId: session?.id });
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new ServerError('User is not logged in.', 401);
|
throw new ServerError('User is not logged in.', 401);
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { UserExtendedNextApiRequest } from '@/config/auth/types';
|
import { UserExtendedNextApiRequest } from '@/config/auth/types';
|
||||||
import ServerError from '@/config/util/ServerError';
|
import ServerError from '@/config/util/ServerError';
|
||||||
import DBClient from '@/prisma/DBClient';
|
|
||||||
import {
|
import {
|
||||||
createBreweryPostLikeService,
|
createBreweryPostLikeService,
|
||||||
findBreweryPostLikeService,
|
findBreweryPostLikeService,
|
||||||
getBreweryPostLikeCountService,
|
getBreweryPostLikeCountService,
|
||||||
removeBreweryPostLikeService,
|
removeBreweryPostLikeService,
|
||||||
} from '@/services/likes/brewery-post-like';
|
} from '@/services/likes/brewery-post-like';
|
||||||
|
import { getBreweryPostByIdService } from '@/services/posts/brewery-post';
|
||||||
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
|
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
|
||||||
import { NextApiResponse, NextApiRequest } from 'next';
|
import { NextApiResponse, NextApiRequest } from 'next';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
@@ -18,9 +19,7 @@ export const sendBreweryPostLikeRequest = async (
|
|||||||
const id = req.query.id! as string;
|
const id = req.query.id! as string;
|
||||||
const user = req.user!;
|
const user = req.user!;
|
||||||
|
|
||||||
const breweryPost = await DBClient.instance.breweryPost.findUnique({
|
const breweryPost = await getBreweryPostByIdService({ breweryPostId: id });
|
||||||
where: { id },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!breweryPost) {
|
if (!breweryPost) {
|
||||||
throw new ServerError('Could not find a brewery post with that id', 404);
|
throw new ServerError('Could not find a brewery post with that id', 404);
|
||||||
@@ -59,10 +58,7 @@ export const getBreweryPostLikeCount = async (
|
|||||||
) => {
|
) => {
|
||||||
const id = req.query.id! as string;
|
const id = req.query.id! as string;
|
||||||
|
|
||||||
const breweryPost = await DBClient.instance.breweryPost.findUnique({
|
const breweryPost = await getBreweryPostByIdService({ breweryPostId: id });
|
||||||
where: { id },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!breweryPost) {
|
if (!breweryPost) {
|
||||||
throw new ServerError('Could not find a brewery post with that id', 404);
|
throw new ServerError('Could not find a brewery post with that id', 404);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,24 +3,33 @@ import localStrat from '@/config/auth/localStrat';
|
|||||||
import { getLoginSession, setLoginSession } from '@/config/auth/session';
|
import { getLoginSession, setLoginSession } from '@/config/auth/session';
|
||||||
import { UserExtendedNextApiRequest } from '@/config/auth/types';
|
import { UserExtendedNextApiRequest } from '@/config/auth/types';
|
||||||
import ServerError from '@/config/util/ServerError';
|
import ServerError from '@/config/util/ServerError';
|
||||||
import createNewUser from '@/services/users/auth/createNewUser';
|
|
||||||
import findUserByEmail from '@/services/users/auth/findUserByEmail';
|
|
||||||
import { NextApiRequest, NextApiResponse } from 'next';
|
import { NextApiRequest, NextApiResponse } from 'next';
|
||||||
import { expressWrapper } from 'next-connect';
|
import { expressWrapper } from 'next-connect';
|
||||||
import passport from 'passport';
|
import passport from 'passport';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import findUserByUsername from '@/services/users/auth/findUserByUsername';
|
|
||||||
import GetUserSchema from '@/services/users/auth/schema/GetUserSchema';
|
import GetUserSchema from '@/services/users/auth/schema/GetUserSchema';
|
||||||
import sendConfirmationEmail from '@/services/users/auth/sendConfirmationEmail';
|
|
||||||
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
|
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
|
||||||
import type { NextFunction } from 'express';
|
import type { NextFunction } from 'express';
|
||||||
import { verifyConfirmationToken } from '@/config/jwt';
|
import { verifyConfirmationToken } from '@/config/jwt';
|
||||||
import updateUserToBeConfirmedById from '@/services/users/auth/updateUserToBeConfirmedById';
|
|
||||||
import DBClient from '@/prisma/DBClient';
|
|
||||||
import sendResetPasswordEmail from '@/services/users/auth/sendResetPasswordEmail';
|
|
||||||
import { hashPassword } from '@/config/auth/passwordFns';
|
import { hashPassword } from '@/config/auth/passwordFns';
|
||||||
import deleteUserById from '@/services/users/auth/deleteUserById';
|
|
||||||
|
import {
|
||||||
|
createNewUser,
|
||||||
|
deleteUserById,
|
||||||
|
findUserByEmail,
|
||||||
|
findUserByUsername,
|
||||||
|
sendConfirmationEmail,
|
||||||
|
sendResetPasswordEmail,
|
||||||
|
updateUserById,
|
||||||
|
updateUserPassword,
|
||||||
|
updateUserToBeConfirmedById,
|
||||||
|
} from '@/services/users/auth';
|
||||||
|
|
||||||
|
import { EditUserRequest, UserRouteRequest } from '@/controllers/users/profile/types';
|
||||||
import {
|
import {
|
||||||
CheckEmailRequest,
|
CheckEmailRequest,
|
||||||
CheckUsernameRequest,
|
CheckUsernameRequest,
|
||||||
@@ -29,7 +38,6 @@ import {
|
|||||||
TokenValidationRequest,
|
TokenValidationRequest,
|
||||||
UpdatePasswordRequest,
|
UpdatePasswordRequest,
|
||||||
} from './types';
|
} from './types';
|
||||||
import { EditUserRequest, UserRouteRequest } from '../profile/types';
|
|
||||||
|
|
||||||
export const authenticateUser = expressWrapper(
|
export const authenticateUser = expressWrapper(
|
||||||
async (
|
async (
|
||||||
@@ -88,10 +96,12 @@ export const registerUser = async (
|
|||||||
req: RegisterUserRequest,
|
req: RegisterUserRequest,
|
||||||
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
|
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
|
||||||
) => {
|
) => {
|
||||||
const [usernameTaken, emailTaken] = await Promise.all([
|
const [usernameTaken, emailTaken] = (
|
||||||
findUserByUsername(req.body.username),
|
await Promise.all([
|
||||||
findUserByEmail(req.body.email),
|
findUserByUsername({ username: req.body.username }),
|
||||||
]);
|
findUserByEmail({ email: req.body.email }),
|
||||||
|
])
|
||||||
|
).map((user) => !!user);
|
||||||
|
|
||||||
if (usernameTaken) {
|
if (usernameTaken) {
|
||||||
throw new ServerError(
|
throw new ServerError(
|
||||||
@@ -114,7 +124,11 @@ export const registerUser = async (
|
|||||||
username: user.username,
|
username: user.username,
|
||||||
});
|
});
|
||||||
|
|
||||||
await sendConfirmationEmail(user);
|
await sendConfirmationEmail({
|
||||||
|
email: user.email,
|
||||||
|
username: user.username,
|
||||||
|
userId: user.id,
|
||||||
|
});
|
||||||
|
|
||||||
res.status(201).json({
|
res.status(201).json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -141,7 +155,7 @@ export const confirmUser = async (
|
|||||||
throw new ServerError('Could not confirm user.', 401);
|
throw new ServerError('Could not confirm user.', 401);
|
||||||
}
|
}
|
||||||
|
|
||||||
await updateUserToBeConfirmedById(id);
|
await updateUserToBeConfirmedById({ userId: id });
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
message: 'User confirmed successfully.',
|
message: 'User confirmed successfully.',
|
||||||
@@ -156,12 +170,14 @@ export const resetPassword = async (
|
|||||||
) => {
|
) => {
|
||||||
const { email } = req.body;
|
const { email } = req.body;
|
||||||
|
|
||||||
const user = await DBClient.instance.user.findUnique({
|
const user = await findUserByEmail({ email });
|
||||||
where: { email },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
await sendResetPasswordEmail(user);
|
await sendResetPasswordEmail({
|
||||||
|
email: user.email,
|
||||||
|
username: user.username,
|
||||||
|
userId: user.id,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
@@ -188,7 +204,7 @@ export const sendCurrentUser = async (
|
|||||||
export const checkEmail = async (req: CheckEmailRequest, res: NextApiResponse) => {
|
export const checkEmail = async (req: CheckEmailRequest, res: NextApiResponse) => {
|
||||||
const { email: emailToCheck } = req.query;
|
const { email: emailToCheck } = req.query;
|
||||||
|
|
||||||
const email = await findUserByEmail(emailToCheck);
|
const email = await findUserByEmail({ email: emailToCheck });
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -201,7 +217,7 @@ export const checkEmail = async (req: CheckEmailRequest, res: NextApiResponse) =
|
|||||||
export const checkUsername = async (req: CheckUsernameRequest, res: NextApiResponse) => {
|
export const checkUsername = async (req: CheckUsernameRequest, res: NextApiResponse) => {
|
||||||
const { username: usernameToCheck } = req.query;
|
const { username: usernameToCheck } = req.query;
|
||||||
|
|
||||||
const username = await findUserByUsername(usernameToCheck);
|
const username = await findUserByUsername({ username: usernameToCheck });
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -215,14 +231,10 @@ export const updatePassword = async (
|
|||||||
req: UpdatePasswordRequest,
|
req: UpdatePasswordRequest,
|
||||||
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
|
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
|
||||||
) => {
|
) => {
|
||||||
const { password } = req.body;
|
|
||||||
const hash = await hashPassword(password);
|
|
||||||
|
|
||||||
const user = req.user!;
|
const user = req.user!;
|
||||||
await DBClient.instance.user.update({
|
const { password } = req.body;
|
||||||
data: { hash },
|
|
||||||
where: { id: user.id },
|
await updateUserPassword({ userId: user.id, password: await hashPassword(password) });
|
||||||
});
|
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
message: 'Updated user password.',
|
message: 'Updated user password.',
|
||||||
@@ -237,7 +249,11 @@ export const resendConfirmation = async (
|
|||||||
) => {
|
) => {
|
||||||
const user = req.user!;
|
const user = req.user!;
|
||||||
|
|
||||||
await sendConfirmationEmail(user);
|
await sendConfirmationEmail({
|
||||||
|
userId: user.id,
|
||||||
|
username: user.username,
|
||||||
|
email: user.email,
|
||||||
|
});
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
message: `Resent the confirmation email for ${user.username}.`,
|
message: `Resent the confirmation email for ${user.username}.`,
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
@@ -251,31 +267,9 @@ export const editUserInfo = async (
|
|||||||
) => {
|
) => {
|
||||||
const { email, firstName, lastName, username } = req.body;
|
const { email, firstName, lastName, username } = req.body;
|
||||||
|
|
||||||
const [usernameIsTaken, emailIsTaken] = await Promise.all([
|
const updatedUser = await updateUserById({
|
||||||
findUserByUsername(username),
|
userId: req.user!.id,
|
||||||
findUserByEmail(email),
|
data: { email, firstName, lastName, username },
|
||||||
]);
|
|
||||||
|
|
||||||
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({
|
res.json({
|
||||||
@@ -291,7 +285,7 @@ export const deleteAccount = async (
|
|||||||
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
|
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
|
||||||
) => {
|
) => {
|
||||||
const { id } = req.query;
|
const { id } = req.query;
|
||||||
const deletedUser = await deleteUserById(id);
|
const deletedUser = await deleteUserById({ userId: id });
|
||||||
|
|
||||||
if (!deletedUser) {
|
if (!deletedUser) {
|
||||||
throw new ServerError('Could not find a user with that id.', 400);
|
throw new ServerError('Could not find a user with that id.', 400);
|
||||||
|
|||||||
@@ -1,17 +1,23 @@
|
|||||||
import ServerError from '@/config/util/ServerError';
|
import ServerError from '@/config/util/ServerError';
|
||||||
import DBClient from '@/prisma/DBClient';
|
|
||||||
import findUserById from '@/services/users/auth/findUserById';
|
|
||||||
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
|
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
|
||||||
import { NextApiResponse } from 'next';
|
import { NextApiResponse } from 'next';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { NextHandler } from 'next-connect';
|
import { NextHandler } from 'next-connect';
|
||||||
import updateUserAvatarById, {
|
|
||||||
UpdateUserAvatarByIdParams,
|
|
||||||
} from '@/services/users/account/UpdateUserAvatarByIdParams';
|
|
||||||
import { UserExtendedNextApiRequest } from '@/config/auth/types';
|
import { UserExtendedNextApiRequest } from '@/config/auth/types';
|
||||||
import updateUserProfileById from '@/services/users/auth/updateUserProfileById';
|
|
||||||
import getUsersFollowingUser from '@/services/users/follows/getUsersFollowingUser';
|
import { findUserById } from '@/services/users/auth';
|
||||||
import getUsersFollowedByUser from '@/services/users/follows/getUsersFollowedByUser';
|
|
||||||
|
import {
|
||||||
|
createUserFollow,
|
||||||
|
deleteUserFollow,
|
||||||
|
findUserFollow,
|
||||||
|
getUsersFollowedByUser,
|
||||||
|
getUsersFollowingUser,
|
||||||
|
updateUserAvatar,
|
||||||
|
updateUserProfileById,
|
||||||
|
} from '@/services/users/profile';
|
||||||
import {
|
import {
|
||||||
UserRouteRequest,
|
UserRouteRequest,
|
||||||
GetUserFollowInfoRequest,
|
GetUserFollowInfoRequest,
|
||||||
@@ -26,24 +32,19 @@ export const followUser = async (
|
|||||||
) => {
|
) => {
|
||||||
const { id } = req.query;
|
const { id } = req.query;
|
||||||
|
|
||||||
const user = await findUserById(id);
|
const user = await findUserById({ userId: id });
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new ServerError('User not found', 404);
|
throw new ServerError('User not found', 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUser = req.user!;
|
const currentUser = req.user!;
|
||||||
const userIsFollowedBySessionUser = await DBClient.instance.userFollow.findFirst({
|
const userIsFollowedBySessionUser = await findUserFollow({
|
||||||
where: {
|
|
||||||
followerId: currentUser.id,
|
followerId: currentUser.id,
|
||||||
followingId: id,
|
followingId: id,
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!userIsFollowedBySessionUser) {
|
if (!userIsFollowedBySessionUser) {
|
||||||
await DBClient.instance.userFollow.create({
|
await createUserFollow({ followerId: currentUser.id, followingId: id });
|
||||||
data: { followerId: currentUser.id, followingId: id },
|
|
||||||
});
|
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
message: 'Now following user.',
|
message: 'Now following user.',
|
||||||
success: true,
|
success: true,
|
||||||
@@ -53,14 +54,7 @@ export const followUser = async (
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await DBClient.instance.userFollow.delete({
|
await deleteUserFollow({ followerId: currentUser.id, followingId: id });
|
||||||
where: {
|
|
||||||
followerId_followingId: {
|
|
||||||
followerId: currentUser.id,
|
|
||||||
followingId: id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
message: 'No longer following user.',
|
message: 'No longer following user.',
|
||||||
@@ -76,7 +70,7 @@ export const getUserFollowers = async (
|
|||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
const { id, page_num, page_size } = req.query;
|
const { id, page_num, page_size } = req.query;
|
||||||
|
|
||||||
const user = await findUserById(id);
|
const user = await findUserById({ userId: id });
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new ServerError('User not found', 404);
|
throw new ServerError('User not found', 404);
|
||||||
}
|
}
|
||||||
@@ -84,20 +78,17 @@ export const getUserFollowers = async (
|
|||||||
const pageNum = parseInt(page_num, 10);
|
const pageNum = parseInt(page_num, 10);
|
||||||
const pageSize = parseInt(page_size, 10);
|
const pageSize = parseInt(page_size, 10);
|
||||||
|
|
||||||
const following = await getUsersFollowingUser({
|
const { follows, count } = await getUsersFollowingUser({
|
||||||
userId: id,
|
userId: id,
|
||||||
pageNum,
|
pageNum,
|
||||||
pageSize,
|
pageSize,
|
||||||
});
|
});
|
||||||
const followingCount = await DBClient.instance.userFollow.count({
|
|
||||||
where: { following: { id } },
|
|
||||||
});
|
|
||||||
|
|
||||||
res.setHeader('X-Total-Count', followingCount);
|
res.setHeader('X-Total-Count', count);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
message: 'Retrieved users that are followed by queried user',
|
message: 'Retrieved users that are followed by queried user',
|
||||||
payload: following,
|
payload: follows,
|
||||||
success: true,
|
success: true,
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
});
|
});
|
||||||
@@ -110,7 +101,7 @@ export const getUsersFollowed = async (
|
|||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
const { id, page_num, page_size } = req.query;
|
const { id, page_num, page_size } = req.query;
|
||||||
|
|
||||||
const user = await findUserById(id);
|
const user = await findUserById({ userId: id });
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new ServerError('User not found', 404);
|
throw new ServerError('User not found', 404);
|
||||||
}
|
}
|
||||||
@@ -118,20 +109,17 @@ export const getUsersFollowed = async (
|
|||||||
const pageNum = parseInt(page_num, 10);
|
const pageNum = parseInt(page_num, 10);
|
||||||
const pageSize = parseInt(page_size, 10);
|
const pageSize = parseInt(page_size, 10);
|
||||||
|
|
||||||
const following = await getUsersFollowedByUser({
|
const { follows, count } = await getUsersFollowedByUser({
|
||||||
userId: id,
|
userId: id,
|
||||||
pageNum,
|
pageNum,
|
||||||
pageSize,
|
pageSize,
|
||||||
});
|
});
|
||||||
const followingCount = await DBClient.instance.userFollow.count({
|
|
||||||
where: { follower: { id } },
|
|
||||||
});
|
|
||||||
|
|
||||||
res.setHeader('X-Total-Count', followingCount);
|
res.setHeader('X-Total-Count', count);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
message: 'Retrieved users that are followed by queried user',
|
message: 'Retrieved users that are followed by queried user',
|
||||||
payload: following,
|
payload: follows,
|
||||||
success: true,
|
success: true,
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
});
|
});
|
||||||
@@ -143,33 +131,27 @@ export const checkIfUserIsFollowedBySessionUser = async (
|
|||||||
) => {
|
) => {
|
||||||
const { id } = req.query;
|
const { id } = req.query;
|
||||||
|
|
||||||
const user = await findUserById(id);
|
const user = await findUserById({ userId: id });
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new ServerError('User not found', 404);
|
throw new ServerError('User not found', 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUser = req.user!;
|
const currentUser = req.user!;
|
||||||
|
|
||||||
const userIsFollowedBySessionUser = await DBClient.instance.userFollow.findFirst({
|
const userFollow = await findUserFollow({
|
||||||
where: { followerId: currentUser.id, followingId: id },
|
followerId: currentUser.id,
|
||||||
|
followingId: id,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!userIsFollowedBySessionUser) {
|
const isFollowed = !!userFollow;
|
||||||
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({
|
res.status(200).json({
|
||||||
message: 'User is followed by the current user.',
|
message: isFollowed
|
||||||
|
? 'User is followed by the session user.'
|
||||||
|
: 'User is not followed by the session user.',
|
||||||
success: true,
|
success: true,
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
payload: { isFollowed: true },
|
payload: { isFollowed },
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -180,7 +162,7 @@ export const checkIfUserCanEditUser = async (
|
|||||||
) => {
|
) => {
|
||||||
const authenticatedUser = req.user!;
|
const authenticatedUser = req.user!;
|
||||||
|
|
||||||
const userToUpdate = await findUserById(req.query.id);
|
const userToUpdate = await findUserById({ userId: req.query.id });
|
||||||
if (!userToUpdate) {
|
if (!userToUpdate) {
|
||||||
throw new ServerError('User not found', 404);
|
throw new ServerError('User not found', 404);
|
||||||
}
|
}
|
||||||
@@ -209,13 +191,10 @@ export const checkIfUserCanUpdateProfile = async <T extends UserExtendedNextApiR
|
|||||||
export const updateAvatar = async (req: UpdateAvatarRequest, res: NextApiResponse) => {
|
export const updateAvatar = async (req: UpdateAvatarRequest, res: NextApiResponse) => {
|
||||||
const { file, user } = req;
|
const { file, user } = req;
|
||||||
|
|
||||||
const avatar: UpdateUserAvatarByIdParams['data']['avatar'] = {
|
await updateUserAvatar({
|
||||||
alt: file.originalname,
|
userId: user!.id,
|
||||||
path: file.path,
|
data: { alt: file.originalname, path: file.path, caption: '' },
|
||||||
caption: '',
|
});
|
||||||
};
|
|
||||||
|
|
||||||
await updateUserAvatarById({ id: user!.id, data: { avatar } });
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
message: 'User avatar updated successfully.',
|
message: 'User avatar updated successfully.',
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
@@ -227,7 +206,7 @@ export const updateProfile = async (req: UpdateProfileRequest, res: NextApiRespo
|
|||||||
const user = req.user!;
|
const user = req.user!;
|
||||||
const { body } = req;
|
const { body } = req;
|
||||||
|
|
||||||
await updateUserProfileById({ id: user!.id, data: { bio: body.bio } });
|
await updateUserProfileById({ userId: user!.id, data: { bio: body.bio } });
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
message: 'Profile updated successfully.',
|
message: 'Profile updated successfully.',
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ import { Tailwind } from '@react-email/tailwind';
|
|||||||
|
|
||||||
import { FC } from 'react';
|
import { FC } from 'react';
|
||||||
|
|
||||||
interface ForgotEmailProps {
|
interface ResetPasswordEmailProps {
|
||||||
name?: string;
|
name?: string;
|
||||||
url?: string;
|
url?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ForgotEmail: FC<ForgotEmailProps> = ({ name, url }) => {
|
const ResetPasswordEmail: FC<ResetPasswordEmailProps> = ({ name, url }) => {
|
||||||
return (
|
return (
|
||||||
<Tailwind>
|
<Tailwind>
|
||||||
<Container className="mx-auto">
|
<Container className="mx-auto">
|
||||||
@@ -36,4 +36,4 @@ const ForgotEmail: FC<ForgotEmailProps> = ({ name, url }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ForgotEmail;
|
export default ResetPasswordEmail;
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ import { Tailwind } from '@react-email/tailwind';
|
|||||||
|
|
||||||
import { FC } from 'react';
|
import { FC } from 'react';
|
||||||
|
|
||||||
interface WelcomeEmail {
|
interface WelcomeEmailProps {
|
||||||
subject?: string;
|
subject?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
url?: string;
|
url?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Welcome: FC<WelcomeEmail> = ({ name, url }) => (
|
const WelcomeEmail: FC<WelcomeEmailProps> = ({ name, url }) => (
|
||||||
<Tailwind>
|
<Tailwind>
|
||||||
<Container className="flex h-full w-full flex-col items-center justify-center">
|
<Container className="flex h-full w-full flex-col items-center justify-center">
|
||||||
<Section>
|
<Section>
|
||||||
@@ -43,4 +43,4 @@ const Welcome: FC<WelcomeEmail> = ({ name, url }) => (
|
|||||||
</Tailwind>
|
</Tailwind>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default Welcome;
|
export default WelcomeEmail;
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
* - `mutate` A function to mutate the data.
|
* - `mutate` A function to mutate the data.
|
||||||
* - `error` The error object, if any.
|
* - `error` The error object, if any.
|
||||||
*/
|
*/
|
||||||
import FollowInfoSchema from '@/services/users/follows/schema/FollowInfoSchema';
|
import FollowInfoSchema from '@/services/users/profile/schema/FollowInfoSchema';
|
||||||
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
|
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
|
||||||
import useSWRInfinite from 'swr/infinite';
|
import useSWRInfinite from 'swr/infinite';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import FollowInfoSchema from '@/services/users/follows/schema/FollowInfoSchema';
|
import FollowInfoSchema from '@/services/users/profile/schema/FollowInfoSchema';
|
||||||
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
|
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
|
||||||
import useSWRInfinite from 'swr/infinite';
|
import useSWRInfinite from 'swr/infinite';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import useMediaQuery from '@/hooks/utilities/useMediaQuery';
|
import useMediaQuery from '@/hooks/utilities/useMediaQuery';
|
||||||
import findUserById from '@/services/users/auth/findUserById';
|
|
||||||
import GetUserSchema from '@/services/users/auth/schema/GetUserSchema';
|
import GetUserSchema from '@/services/users/auth/schema/GetUserSchema';
|
||||||
|
|
||||||
import Head from 'next/head';
|
import Head from 'next/head';
|
||||||
@@ -7,6 +7,7 @@ import { FC } from 'react';
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import withPageAuthRequired from '@/util/withPageAuthRequired';
|
import withPageAuthRequired from '@/util/withPageAuthRequired';
|
||||||
import UserHeader from '@/components/UserPage/UserHeader';
|
import UserHeader from '@/components/UserPage/UserHeader';
|
||||||
|
import { findUserById } from '@/services/users/auth';
|
||||||
|
|
||||||
interface UserInfoPageProps {
|
interface UserInfoPageProps {
|
||||||
user: z.infer<typeof GetUserSchema>;
|
user: z.infer<typeof GetUserSchema>;
|
||||||
@@ -39,7 +40,7 @@ export default UserInfoPage;
|
|||||||
export const getServerSideProps = withPageAuthRequired<UserInfoPageProps>(
|
export const getServerSideProps = withPageAuthRequired<UserInfoPageProps>(
|
||||||
async (context) => {
|
async (context) => {
|
||||||
const { id } = context.params!;
|
const { id } = context.params!;
|
||||||
const user = await findUserById(id as string);
|
const user = await findUserById({ userId: id as string });
|
||||||
return user
|
return user
|
||||||
? { props: { user: JSON.parse(JSON.stringify(user)) } }
|
? { props: { user: JSON.parse(JSON.stringify(user)) } }
|
||||||
: { notFound: true };
|
: { notFound: true };
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { setLoginSession } from '@/config/auth/session';
|
import { setLoginSession } from '@/config/auth/session';
|
||||||
import { verifyResetPasswordToken } from '@/config/jwt';
|
import { verifyResetPasswordToken } from '@/config/jwt';
|
||||||
import ServerError from '@/config/util/ServerError';
|
import ServerError from '@/config/util/ServerError';
|
||||||
import findUserById from '@/services/users/auth/findUserById';
|
import { findUserById } from '@/services/users/auth';
|
||||||
|
|
||||||
import { GetServerSideProps, NextApiResponse, NextPage } from 'next';
|
import { GetServerSideProps, NextApiResponse, NextPage } from 'next';
|
||||||
|
|
||||||
@@ -29,14 +29,14 @@ export const getServerSideProps: GetServerSideProps = async (context) => {
|
|||||||
|
|
||||||
const { id } = await verifyResetPasswordToken(token as string);
|
const { id } = await verifyResetPasswordToken(token as string);
|
||||||
|
|
||||||
const user = await findUserById(id);
|
const user = await findUserById({ userId: id as string });
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new ServerError('User not found', 404);
|
throw new ServerError('User not found', 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
await setLoginSession(context.res as NextApiResponse, user);
|
await setLoginSession(context.res as NextApiResponse, user);
|
||||||
|
|
||||||
return { redirect: { destination: '/account', permanent: false } };
|
return { redirect: { destination: '/users/account', permanent: false } };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return { props: {} };
|
return { props: {} };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
import DBClient from '@/prisma/DBClient';
|
|
||||||
import GetUserSchema from '@/services/users/auth/schema/GetUserSchema';
|
|
||||||
import { z } from 'zod';
|
|
||||||
|
|
||||||
export interface UpdateUserAvatarByIdParams {
|
|
||||||
id: string;
|
|
||||||
data: {
|
|
||||||
avatar: {
|
|
||||||
alt: string;
|
|
||||||
path: string;
|
|
||||||
caption: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const updateUserAvatarById = async ({ id, data }: UpdateUserAvatarByIdParams) => {
|
|
||||||
const user: z.infer<typeof GetUserSchema> = await DBClient.instance.user.update({
|
|
||||||
where: { id },
|
|
||||||
data: {
|
|
||||||
userAvatar: data.avatar
|
|
||||||
? {
|
|
||||||
upsert: {
|
|
||||||
create: {
|
|
||||||
alt: data.avatar.alt,
|
|
||||||
path: data.avatar.path,
|
|
||||||
caption: data.avatar.caption,
|
|
||||||
},
|
|
||||||
update: {
|
|
||||||
alt: data.avatar.alt,
|
|
||||||
path: data.avatar.path,
|
|
||||||
caption: data.avatar.caption,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
username: true,
|
|
||||||
email: true,
|
|
||||||
bio: true,
|
|
||||||
userAvatar: true,
|
|
||||||
accountIsVerified: true,
|
|
||||||
createdAt: true,
|
|
||||||
firstName: true,
|
|
||||||
lastName: true,
|
|
||||||
updatedAt: true,
|
|
||||||
dateOfBirth: true,
|
|
||||||
role: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return user;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default updateUserAvatarById;
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
import { hashPassword } from '@/config/auth/passwordFns';
|
|
||||||
import DBClient from '@/prisma/DBClient';
|
|
||||||
import { z } from 'zod';
|
|
||||||
import { CreateUserValidationSchema } from './schema/CreateUserValidationSchemas';
|
|
||||||
import GetUserSchema from './schema/GetUserSchema';
|
|
||||||
|
|
||||||
const createNewUser = async ({
|
|
||||||
email,
|
|
||||||
password,
|
|
||||||
firstName,
|
|
||||||
lastName,
|
|
||||||
dateOfBirth,
|
|
||||||
username,
|
|
||||||
}: z.infer<typeof CreateUserValidationSchema>) => {
|
|
||||||
const hash = await hashPassword(password);
|
|
||||||
const user: z.infer<typeof GetUserSchema> = await DBClient.instance.user.create({
|
|
||||||
data: {
|
|
||||||
username,
|
|
||||||
email,
|
|
||||||
hash,
|
|
||||||
firstName,
|
|
||||||
lastName,
|
|
||||||
dateOfBirth: new Date(dateOfBirth),
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
username: true,
|
|
||||||
email: true,
|
|
||||||
firstName: true,
|
|
||||||
lastName: true,
|
|
||||||
dateOfBirth: true,
|
|
||||||
createdAt: true,
|
|
||||||
accountIsVerified: true,
|
|
||||||
updatedAt: true,
|
|
||||||
role: true,
|
|
||||||
userAvatar: true,
|
|
||||||
bio: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return user;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default createNewUser;
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import DBClient from '@/prisma/DBClient';
|
|
||||||
import { z } from 'zod';
|
|
||||||
import GetUserSchema from './schema/GetUserSchema';
|
|
||||||
|
|
||||||
const deleteUserById = async (id: string) => {
|
|
||||||
const deletedUser: z.infer<typeof GetUserSchema> | null =
|
|
||||||
await DBClient.instance.user.delete({
|
|
||||||
where: { id },
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
username: true,
|
|
||||||
email: true,
|
|
||||||
firstName: true,
|
|
||||||
lastName: true,
|
|
||||||
dateOfBirth: true,
|
|
||||||
createdAt: true,
|
|
||||||
accountIsVerified: true,
|
|
||||||
updatedAt: true,
|
|
||||||
role: true,
|
|
||||||
userAvatar: true,
|
|
||||||
bio: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return deletedUser;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default deleteUserById;
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import DBClient from '../../../prisma/DBClient';
|
|
||||||
|
|
||||||
const findUserByEmail = async (email: string) =>
|
|
||||||
DBClient.instance.user.findFirst({
|
|
||||||
where: { email },
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
username: true,
|
|
||||||
hash: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default findUserByEmail;
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
import DBClient from '@/prisma/DBClient';
|
|
||||||
import { z } from 'zod';
|
|
||||||
import GetUserSchema from './schema/GetUserSchema';
|
|
||||||
|
|
||||||
const findUserById = async (id: string) => {
|
|
||||||
const user: z.infer<typeof GetUserSchema> | null =
|
|
||||||
await DBClient.instance.user.findUnique({
|
|
||||||
where: { id },
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
username: true,
|
|
||||||
email: true,
|
|
||||||
firstName: true,
|
|
||||||
lastName: true,
|
|
||||||
dateOfBirth: true,
|
|
||||||
createdAt: true,
|
|
||||||
accountIsVerified: true,
|
|
||||||
updatedAt: true,
|
|
||||||
role: true,
|
|
||||||
userAvatar: {
|
|
||||||
select: {
|
|
||||||
path: true,
|
|
||||||
alt: true,
|
|
||||||
caption: true,
|
|
||||||
createdAt: true,
|
|
||||||
id: true,
|
|
||||||
updatedAt: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
bio: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return user;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default findUserById;
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import DBClient from '@/prisma/DBClient';
|
|
||||||
import { z } from 'zod';
|
|
||||||
|
|
||||||
import PublicUserSchema from './schema/PublicUserSchema';
|
|
||||||
|
|
||||||
const findUserByIdPublic = async (id: string) => {
|
|
||||||
const user: z.infer<typeof PublicUserSchema> | null =
|
|
||||||
await DBClient.instance.user.findUnique({
|
|
||||||
where: { id },
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
username: true,
|
|
||||||
firstName: true,
|
|
||||||
lastName: true,
|
|
||||||
createdAt: true,
|
|
||||||
role: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return user;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default findUserByIdPublic;
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import DBClient from '../../../prisma/DBClient';
|
|
||||||
|
|
||||||
const findUserByUsername = async (username: string) =>
|
|
||||||
DBClient.instance.user.findFirst({
|
|
||||||
where: { username },
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
username: true,
|
|
||||||
hash: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default findUserByUsername;
|
|
||||||
307
src/services/users/auth/index.ts
Normal file
307
src/services/users/auth/index.ts
Normal file
@@ -0,0 +1,307 @@
|
|||||||
|
/* eslint-disable import/prefer-default-export */
|
||||||
|
import { hashPassword } from '@/config/auth/passwordFns';
|
||||||
|
import DBClient from '@/prisma/DBClient';
|
||||||
|
import { BASE_URL } from '@/config/env';
|
||||||
|
import { generateConfirmationToken, generateResetPasswordToken } from '@/config/jwt';
|
||||||
|
import sendEmail from '@/config/sparkpost/sendEmail';
|
||||||
|
|
||||||
|
import { ReactElement } from 'react';
|
||||||
|
import ServerError from '@/config/util/ServerError';
|
||||||
|
import { render } from '@react-email/render';
|
||||||
|
import WelcomeEmail from '@/emails/WelcomeEmail';
|
||||||
|
import ResetPasswordEmail from '@/emails/ForgotEmail';
|
||||||
|
|
||||||
|
import {
|
||||||
|
CreateNewUser,
|
||||||
|
DeleteUserById,
|
||||||
|
FindUserByEmail,
|
||||||
|
FindUserByUsername,
|
||||||
|
FindUserById,
|
||||||
|
SendConfirmationEmail,
|
||||||
|
SendResetPasswordEmail,
|
||||||
|
UpdateUserToBeConfirmedById,
|
||||||
|
UpdateUserPassword,
|
||||||
|
UpdateUserById,
|
||||||
|
} from './types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The select object for retrieving users.
|
||||||
|
*
|
||||||
|
* Satisfies the GetUserSchema zod schema.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const users = await DBClient.instance.user.findMany({
|
||||||
|
* select: userSelect,
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
const userSelect = {
|
||||||
|
id: true,
|
||||||
|
username: true,
|
||||||
|
email: true,
|
||||||
|
firstName: true,
|
||||||
|
lastName: true,
|
||||||
|
dateOfBirth: true,
|
||||||
|
createdAt: true,
|
||||||
|
accountIsVerified: true,
|
||||||
|
updatedAt: true,
|
||||||
|
role: true,
|
||||||
|
userAvatar: true,
|
||||||
|
bio: true,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The select object for retrieving users without sensitive information.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const user = await DBClient.instance.user.findUnique({
|
||||||
|
* where: { id: userId },
|
||||||
|
* select: AuthUserSelect,
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
const authUserSelect = {
|
||||||
|
id: true,
|
||||||
|
username: true,
|
||||||
|
hash: true,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new user.
|
||||||
|
*
|
||||||
|
* @param args The arguments for service.
|
||||||
|
* @param args.email The email of the user to create.
|
||||||
|
* @param args.password The password of the user to create.
|
||||||
|
* @param args.firstName The first name of the user to create.
|
||||||
|
* @param args.lastName The last name of the user to create.
|
||||||
|
* @param args.dateOfBirth The date of birth of the user to create.
|
||||||
|
* @param args.username The username of the user to create.
|
||||||
|
* @returns The user.
|
||||||
|
*/
|
||||||
|
export const createNewUser: CreateNewUser = async ({
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
dateOfBirth,
|
||||||
|
username,
|
||||||
|
}) => {
|
||||||
|
const hash = await hashPassword(password);
|
||||||
|
|
||||||
|
const user = await DBClient.instance.user.create({
|
||||||
|
data: {
|
||||||
|
username,
|
||||||
|
email,
|
||||||
|
hash,
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
dateOfBirth: new Date(dateOfBirth),
|
||||||
|
},
|
||||||
|
select: userSelect,
|
||||||
|
});
|
||||||
|
|
||||||
|
return user;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a user by id.
|
||||||
|
*
|
||||||
|
* @param args The arguments for service.
|
||||||
|
* @param args.userId The id of the user to delete.
|
||||||
|
* @returns The user that was deleted if found, otherwise null.
|
||||||
|
*/
|
||||||
|
export const deleteUserById: DeleteUserById = ({ userId }) => {
|
||||||
|
return DBClient.instance.user.delete({ where: { id: userId }, select: authUserSelect });
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds a user by username.
|
||||||
|
*
|
||||||
|
* @param args The arguments for service.
|
||||||
|
* @param args.username The username of the user to find.
|
||||||
|
* @returns The user if found, otherwise null.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const findUserByUsername: FindUserByUsername = async ({ username }) => {
|
||||||
|
return DBClient.instance.user.findUnique({
|
||||||
|
where: { username },
|
||||||
|
select: authUserSelect,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds a user by email.
|
||||||
|
*
|
||||||
|
* @param args The arguments for service.
|
||||||
|
* @param args.email The email of the user to find.
|
||||||
|
*/
|
||||||
|
export const findUserByEmail: FindUserByEmail = async ({ email }) => {
|
||||||
|
return DBClient.instance.user.findUnique({ where: { email }, select: userSelect });
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds a user by id.
|
||||||
|
*
|
||||||
|
* @param args The arguments for service.
|
||||||
|
* @param args.userId The id of the user to find.
|
||||||
|
* @returns The user if found, otherwise null.
|
||||||
|
*/
|
||||||
|
export const findUserById: FindUserById = ({ userId }) => {
|
||||||
|
return DBClient.instance.user.findUnique({ where: { id: userId }, select: userSelect });
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a confirmation email to the user using React Email and SparkPost.
|
||||||
|
*
|
||||||
|
* @param args The arguments for service.
|
||||||
|
* @param args.userId The id of the user to send the confirmation email to.
|
||||||
|
* @param args.username The username of the user to send the confirmation email to.
|
||||||
|
* @param args.email The email of the user to send the confirmation email to.
|
||||||
|
* @returns The user if found, otherwise null.
|
||||||
|
*/
|
||||||
|
export const sendConfirmationEmail: SendConfirmationEmail = async ({
|
||||||
|
userId,
|
||||||
|
username,
|
||||||
|
email,
|
||||||
|
}) => {
|
||||||
|
const confirmationToken = generateConfirmationToken({ id: userId, username });
|
||||||
|
const url = `${BASE_URL}/users/confirm?token=${confirmationToken}`;
|
||||||
|
|
||||||
|
const name = username;
|
||||||
|
const address = email;
|
||||||
|
const subject = 'Confirm your email';
|
||||||
|
|
||||||
|
const component = WelcomeEmail({ name, url, subject })! as ReactElement<
|
||||||
|
unknown,
|
||||||
|
string
|
||||||
|
>;
|
||||||
|
|
||||||
|
const html = render(component);
|
||||||
|
const text = render(component, { plainText: true });
|
||||||
|
|
||||||
|
await sendEmail({ address, subject, text, html });
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a reset password email to the specified user.
|
||||||
|
*
|
||||||
|
* @param args The arguments for service.
|
||||||
|
* @param args.userId The id of the user to send the reset password email to.
|
||||||
|
* @param args.username The username of the user to send the reset password email to.
|
||||||
|
* @param args.email The email of the user to send the reset password email to.
|
||||||
|
* @returns A promise that resolves to void.
|
||||||
|
*/
|
||||||
|
export const sendResetPasswordEmail: SendResetPasswordEmail = async ({
|
||||||
|
userId,
|
||||||
|
username,
|
||||||
|
email,
|
||||||
|
}) => {
|
||||||
|
const token = generateResetPasswordToken({ id: userId, username });
|
||||||
|
|
||||||
|
const url = `${BASE_URL}/users/reset-password?token=${token}`;
|
||||||
|
|
||||||
|
const component = ResetPasswordEmail({ name: username, url })! as ReactElement<
|
||||||
|
unknown,
|
||||||
|
string
|
||||||
|
>;
|
||||||
|
|
||||||
|
const html = render(component);
|
||||||
|
const text = render(component, { plainText: true });
|
||||||
|
|
||||||
|
await sendEmail({
|
||||||
|
address: email,
|
||||||
|
subject: 'Reset Password',
|
||||||
|
html,
|
||||||
|
text,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a user to be confirmed by id.
|
||||||
|
*
|
||||||
|
* @param args The arguments for service.
|
||||||
|
* @param args.userId The id of the user to update.
|
||||||
|
* @returns The user.
|
||||||
|
*/
|
||||||
|
export const updateUserToBeConfirmedById: UpdateUserToBeConfirmedById = async ({
|
||||||
|
userId,
|
||||||
|
}) => {
|
||||||
|
return DBClient.instance.user.update({
|
||||||
|
where: { id: userId },
|
||||||
|
data: { accountIsVerified: true, updatedAt: new Date() },
|
||||||
|
select: userSelect,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateUserPassword: UpdateUserPassword = async ({ password, userId }) => {
|
||||||
|
const hash = await hashPassword(password);
|
||||||
|
|
||||||
|
const user = await DBClient.instance.user.update({
|
||||||
|
where: { id: userId },
|
||||||
|
data: { hash, updatedAt: new Date() },
|
||||||
|
select: authUserSelect,
|
||||||
|
});
|
||||||
|
|
||||||
|
return user;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a user by id.
|
||||||
|
*
|
||||||
|
* @param args The arguments for service.
|
||||||
|
* @param args.userId The id of the user to update.
|
||||||
|
* @param args.data The data to update the user with.
|
||||||
|
* @param args.data.email The email of the user to update.
|
||||||
|
* @param args.data.firstName The first name of the user to update.
|
||||||
|
* @param args.data.lastName The last name of the user to update.
|
||||||
|
* @param args.data.username The username of the user to update.
|
||||||
|
*/
|
||||||
|
export const updateUserById: UpdateUserById = async ({ userId, data }) => {
|
||||||
|
const user = await DBClient.instance.user.findUnique({
|
||||||
|
where: { id: userId },
|
||||||
|
select: userSelect,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new ServerError('User not found', 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedFields = {
|
||||||
|
email: data.email !== user.email,
|
||||||
|
username: data.username !== user.username,
|
||||||
|
firstName: data.firstName !== user.firstName,
|
||||||
|
lastName: data.lastName !== user.lastName,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
if (updatedFields.email) {
|
||||||
|
const emailIsTaken = await findUserByEmail({ email: data.email });
|
||||||
|
if (emailIsTaken) {
|
||||||
|
throw new ServerError('Email is already taken', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
await sendConfirmationEmail({
|
||||||
|
userId,
|
||||||
|
username: data.username,
|
||||||
|
email: data.email,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updatedFields.username) {
|
||||||
|
const usernameIsTaken = await findUserByUsername({ username: data.username });
|
||||||
|
if (usernameIsTaken) {
|
||||||
|
throw new ServerError('Username is already taken', 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedUser = await DBClient.instance.user.update({
|
||||||
|
where: { id: userId },
|
||||||
|
data: {
|
||||||
|
email: updatedFields.email ? data.email : undefined,
|
||||||
|
username: updatedFields.username ? data.username : undefined,
|
||||||
|
firstName: updatedFields.firstName ? data.firstName : undefined,
|
||||||
|
lastName: updatedFields.lastName ? data.lastName : undefined,
|
||||||
|
accountIsVerified: updatedFields.email ? false : undefined,
|
||||||
|
},
|
||||||
|
select: userSelect,
|
||||||
|
});
|
||||||
|
|
||||||
|
return updatedUser;
|
||||||
|
};
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import GetUserSchema from '@/services/users/auth/schema/GetUserSchema';
|
|
||||||
|
|
||||||
const PublicUserSchema = GetUserSchema.pick({
|
|
||||||
id: true,
|
|
||||||
name: true,
|
|
||||||
createdAt: true,
|
|
||||||
username: true,
|
|
||||||
role: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default PublicUserSchema;
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
import { generateConfirmationToken } from '@/config/jwt';
|
|
||||||
import sendEmail from '@/config/sparkpost/sendEmail';
|
|
||||||
|
|
||||||
import Welcome from '@/emails/Welcome';
|
|
||||||
import { render } from '@react-email/render';
|
|
||||||
import { z } from 'zod';
|
|
||||||
import { BASE_URL } from '@/config/env';
|
|
||||||
import { ReactElement } from 'react';
|
|
||||||
import GetUserSchema from './schema/GetUserSchema';
|
|
||||||
|
|
||||||
type UserSchema = z.infer<typeof GetUserSchema>;
|
|
||||||
|
|
||||||
const sendConfirmationEmail = async ({ id, username, email }: UserSchema) => {
|
|
||||||
const confirmationToken = generateConfirmationToken({ id, username });
|
|
||||||
|
|
||||||
const subject = 'Confirm your email';
|
|
||||||
const name = username;
|
|
||||||
const url = `${BASE_URL}/users/confirm?token=${confirmationToken}`;
|
|
||||||
const address = email;
|
|
||||||
|
|
||||||
const component = Welcome({ name, url, subject })! as ReactElement<unknown, string>;
|
|
||||||
|
|
||||||
const html = render(component);
|
|
||||||
const text = render(component, { plainText: true });
|
|
||||||
|
|
||||||
await sendEmail({ address, subject, text, html });
|
|
||||||
};
|
|
||||||
|
|
||||||
export default sendConfirmationEmail;
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
import { BASE_URL } from '@/config/env';
|
|
||||||
import { generateResetPasswordToken } from '@/config/jwt';
|
|
||||||
import sendEmail from '@/config/sparkpost/sendEmail';
|
|
||||||
import ForgotEmail from '@/emails/ForgotEmail';
|
|
||||||
import { User } from '@prisma/client';
|
|
||||||
import type { ReactElement } from 'react';
|
|
||||||
import { render } from '@react-email/render';
|
|
||||||
|
|
||||||
const sendResetPasswordEmail = async (user: User) => {
|
|
||||||
const token = generateResetPasswordToken({ id: user.id, username: user.username });
|
|
||||||
|
|
||||||
const url = `${BASE_URL}/users/reset-password?token=${token}`;
|
|
||||||
|
|
||||||
const component = ForgotEmail({ name: user.username, url })! as ReactElement<
|
|
||||||
unknown,
|
|
||||||
string
|
|
||||||
>;
|
|
||||||
|
|
||||||
const html = render(component);
|
|
||||||
const text = render(component, { plainText: true });
|
|
||||||
|
|
||||||
await sendEmail({
|
|
||||||
address: user.email,
|
|
||||||
subject: 'Reset Password',
|
|
||||||
html,
|
|
||||||
text,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export default sendResetPasswordEmail;
|
|
||||||
47
src/services/users/auth/types/index.ts
Normal file
47
src/services/users/auth/types/index.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
import GetUserSchema from '../schema/GetUserSchema';
|
||||||
|
import { CreateUserValidationSchema } from '../schema/CreateUserValidationSchemas';
|
||||||
|
|
||||||
|
type User = z.infer<typeof GetUserSchema>;
|
||||||
|
type AuthUser = { username: string; hash: string; id: string };
|
||||||
|
|
||||||
|
export type CreateNewUser = (
|
||||||
|
args: z.infer<typeof CreateUserValidationSchema>,
|
||||||
|
) => Promise<User>;
|
||||||
|
|
||||||
|
export type DeleteUserById = (args: { userId: string }) => Promise<AuthUser | null>;
|
||||||
|
|
||||||
|
export type FindUserById = (args: { userId: string }) => Promise<User | null>;
|
||||||
|
|
||||||
|
export type FindUserByUsername = (args: { username: string }) => Promise<AuthUser | null>;
|
||||||
|
|
||||||
|
export type FindUserByEmail = (args: { email: string }) => Promise<User | null>;
|
||||||
|
|
||||||
|
export type UpdateUserPassword = (args: {
|
||||||
|
userId: string;
|
||||||
|
password: string;
|
||||||
|
}) => Promise<AuthUser | null>;
|
||||||
|
|
||||||
|
export type SendConfirmationEmail = (args: {
|
||||||
|
userId: string;
|
||||||
|
username: string;
|
||||||
|
email: string;
|
||||||
|
}) => Promise<void>;
|
||||||
|
|
||||||
|
export type SendResetPasswordEmail = (args: {
|
||||||
|
userId: string;
|
||||||
|
username: string;
|
||||||
|
email: string;
|
||||||
|
}) => Promise<void>;
|
||||||
|
|
||||||
|
export type UpdateUserToBeConfirmedById = (args: { userId: string }) => Promise<User>;
|
||||||
|
|
||||||
|
export type UpdateUserById = (args: {
|
||||||
|
userId: string;
|
||||||
|
data: {
|
||||||
|
email: string;
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
username: string;
|
||||||
|
};
|
||||||
|
}) => Promise<User>;
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
import DBClient from '@/prisma/DBClient';
|
|
||||||
import { z } from 'zod';
|
|
||||||
import GetUserSchema from './schema/GetUserSchema';
|
|
||||||
|
|
||||||
interface UpdateUserProfileByIdParams {
|
|
||||||
id: string;
|
|
||||||
data: { bio: string };
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateUserProfileById = async ({ id, data }: UpdateUserProfileByIdParams) => {
|
|
||||||
const user: z.infer<typeof GetUserSchema> = await DBClient.instance.user.update({
|
|
||||||
where: { id },
|
|
||||||
data: { bio: data.bio },
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
username: true,
|
|
||||||
email: true,
|
|
||||||
bio: true,
|
|
||||||
userAvatar: true,
|
|
||||||
accountIsVerified: true,
|
|
||||||
createdAt: true,
|
|
||||||
firstName: true,
|
|
||||||
lastName: true,
|
|
||||||
updatedAt: true,
|
|
||||||
dateOfBirth: true,
|
|
||||||
role: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return user;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default updateUserProfileById;
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
import GetUserSchema from '@/services/users/auth/schema/GetUserSchema';
|
|
||||||
import DBClient from '@/prisma/DBClient';
|
|
||||||
import { z } from 'zod';
|
|
||||||
|
|
||||||
const updateUserToBeConfirmedById = async (id: string) => {
|
|
||||||
const user: z.infer<typeof GetUserSchema> = await DBClient.instance.user.update({
|
|
||||||
where: { id },
|
|
||||||
data: { accountIsVerified: true, updatedAt: new Date() },
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
username: true,
|
|
||||||
email: true,
|
|
||||||
accountIsVerified: true,
|
|
||||||
createdAt: true,
|
|
||||||
firstName: true,
|
|
||||||
lastName: true,
|
|
||||||
updatedAt: true,
|
|
||||||
dateOfBirth: true,
|
|
||||||
role: true,
|
|
||||||
bio: true,
|
|
||||||
userAvatar: {
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
path: true,
|
|
||||||
alt: true,
|
|
||||||
caption: true,
|
|
||||||
createdAt: true,
|
|
||||||
updatedAt: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return user;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default updateUserToBeConfirmedById;
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import DBClient from '@/prisma/DBClient';
|
|
||||||
import { z } from 'zod';
|
|
||||||
import FollowInfoSchema from './schema/FollowInfoSchema';
|
|
||||||
|
|
||||||
interface GetFollowingInfoByUserIdArgs {
|
|
||||||
userId: string;
|
|
||||||
pageNum: number;
|
|
||||||
pageSize: number;
|
|
||||||
}
|
|
||||||
const getUsersFollowedByUser = async ({
|
|
||||||
userId,
|
|
||||||
pageNum,
|
|
||||||
pageSize,
|
|
||||||
}: GetFollowingInfoByUserIdArgs): Promise<z.infer<typeof FollowInfoSchema>[]> => {
|
|
||||||
const usersFollowedByQueriedUser = await DBClient.instance.userFollow.findMany({
|
|
||||||
take: pageSize,
|
|
||||||
skip: (pageNum - 1) * pageSize,
|
|
||||||
where: { following: { id: userId } },
|
|
||||||
select: {
|
|
||||||
follower: { select: { username: true, userAvatar: true, id: true } },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return usersFollowedByQueriedUser.map((u) => u.follower);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default getUsersFollowedByUser;
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import DBClient from '@/prisma/DBClient';
|
|
||||||
import { z } from 'zod';
|
|
||||||
import FollowInfoSchema from './schema/FollowInfoSchema';
|
|
||||||
|
|
||||||
interface GetFollowingInfoByUserIdArgs {
|
|
||||||
userId: string;
|
|
||||||
pageNum: number;
|
|
||||||
pageSize: number;
|
|
||||||
}
|
|
||||||
const getUsersFollowingUser = async ({
|
|
||||||
userId,
|
|
||||||
pageNum,
|
|
||||||
pageSize,
|
|
||||||
}: GetFollowingInfoByUserIdArgs): Promise<z.infer<typeof FollowInfoSchema>[]> => {
|
|
||||||
const usersFollowingQueriedUser = await DBClient.instance.userFollow.findMany({
|
|
||||||
take: pageSize,
|
|
||||||
skip: (pageNum - 1) * pageSize,
|
|
||||||
where: { follower: { id: userId } },
|
|
||||||
select: {
|
|
||||||
following: { select: { username: true, userAvatar: true, id: true } },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return usersFollowingQueriedUser.map((u) => u.following);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default getUsersFollowingUser;
|
|
||||||
193
src/services/users/profile/index.ts
Normal file
193
src/services/users/profile/index.ts
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
import DBClient from '@/prisma/DBClient';
|
||||||
|
import ServerError from '@/config/util/ServerError';
|
||||||
|
import {
|
||||||
|
GetUsersFollowedByOrFollowingUser,
|
||||||
|
UpdateUserAvatar,
|
||||||
|
UpdateUserProfileById,
|
||||||
|
UserFollowService,
|
||||||
|
} from './types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The select object for retrieving users.
|
||||||
|
*
|
||||||
|
* Satisfies the GetUserSchema zod schema.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const users = await DBClient.instance.user.findMany({
|
||||||
|
* select: userSelect,
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
const userSelect = {
|
||||||
|
id: true,
|
||||||
|
username: true,
|
||||||
|
email: true,
|
||||||
|
firstName: true,
|
||||||
|
lastName: true,
|
||||||
|
dateOfBirth: true,
|
||||||
|
createdAt: true,
|
||||||
|
accountIsVerified: true,
|
||||||
|
updatedAt: true,
|
||||||
|
role: true,
|
||||||
|
userAvatar: true,
|
||||||
|
bio: true,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds a user follow by the followerId and followingId.
|
||||||
|
*
|
||||||
|
* @returns The user follow if found, otherwise null.
|
||||||
|
*/
|
||||||
|
export const findUserFollow: UserFollowService = ({ followerId, followingId }) => {
|
||||||
|
return DBClient.instance.userFollow.findFirst({ where: { followerId, followingId } });
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new user follow.
|
||||||
|
*
|
||||||
|
* @param args The arguments for service.
|
||||||
|
* @param args.followerId The follower id of the user follow to create.
|
||||||
|
* @param args.followingId The following id of the user follow to create.
|
||||||
|
* @returns The user follow.
|
||||||
|
*/
|
||||||
|
export const createUserFollow: UserFollowService = ({ followerId, followingId }) => {
|
||||||
|
return DBClient.instance.userFollow.create({ data: { followerId, followingId } });
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a user follow.
|
||||||
|
*
|
||||||
|
* @param args The arguments for service.
|
||||||
|
* @param args.followerId The follower id of the user follow to delete.
|
||||||
|
* @param args.followingId The following id of the user follow to delete.
|
||||||
|
* @returns The user follow.
|
||||||
|
*/
|
||||||
|
export const deleteUserFollow: UserFollowService = ({ followerId, followingId }) => {
|
||||||
|
return DBClient.instance.userFollow.delete({
|
||||||
|
where: { followerId_followingId: { followerId, followingId } },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the users followed by the session user.
|
||||||
|
*
|
||||||
|
* @param args The arguments for service.
|
||||||
|
* @param args.userId The id of the user to check if followed by the session user.
|
||||||
|
* @param args.pageNum The page number of the users to retrieve.
|
||||||
|
* @param args.pageSize The page size of the users to retrieve.
|
||||||
|
* @returns The users followed by the queried user and the count of users followed by the
|
||||||
|
* queried user.
|
||||||
|
*/
|
||||||
|
export const getUsersFollowedByUser: GetUsersFollowedByOrFollowingUser = async ({
|
||||||
|
userId,
|
||||||
|
pageNum,
|
||||||
|
pageSize,
|
||||||
|
}) => {
|
||||||
|
const usersFollowedByQueriedUser = await DBClient.instance.userFollow.findMany({
|
||||||
|
take: pageSize,
|
||||||
|
skip: (pageNum - 1) * pageSize,
|
||||||
|
where: { follower: { id: userId } },
|
||||||
|
select: {
|
||||||
|
follower: { select: { username: true, userAvatar: true, id: true } },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const count = await DBClient.instance.userFollow.count({
|
||||||
|
where: { follower: { id: userId } },
|
||||||
|
});
|
||||||
|
const follows = usersFollowedByQueriedUser.map((u) => u.follower);
|
||||||
|
|
||||||
|
return { follows, count };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the users following the session user.
|
||||||
|
*
|
||||||
|
* @param args The arguments for service.
|
||||||
|
* @param args.userId The id of the user to check if followed by the session user.
|
||||||
|
* @param args.pageNum The page number of the users to retrieve.
|
||||||
|
* @param args.pageSize The page size of the users to retrieve.
|
||||||
|
*/
|
||||||
|
export const getUsersFollowingUser: GetUsersFollowedByOrFollowingUser = async ({
|
||||||
|
userId,
|
||||||
|
pageNum,
|
||||||
|
pageSize,
|
||||||
|
}) => {
|
||||||
|
const usersFollowingQueriedUser = await DBClient.instance.userFollow.findMany({
|
||||||
|
take: pageSize,
|
||||||
|
skip: (pageNum - 1) * pageSize,
|
||||||
|
where: { following: { id: userId } },
|
||||||
|
select: {
|
||||||
|
following: { select: { username: true, userAvatar: true, id: true } },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const count = await DBClient.instance.userFollow.count({
|
||||||
|
where: { following: { id: userId } },
|
||||||
|
});
|
||||||
|
|
||||||
|
const follows = usersFollowingQueriedUser.map((u) => u.following);
|
||||||
|
return { follows, count };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the user avatar of the user.
|
||||||
|
*
|
||||||
|
* @param args The arguments for service.
|
||||||
|
* @param args.userId The id of the user to update the avatar of.
|
||||||
|
* @param args.data The data to update the user avatar with.
|
||||||
|
* @param args.data.alt The alt text of the user avatar.
|
||||||
|
* @param args.data.path The path of the user avatar.
|
||||||
|
* @param args.data.caption The caption of the user avatar.
|
||||||
|
* @returns The updated user.
|
||||||
|
*/
|
||||||
|
export const updateUserAvatar: UpdateUserAvatar = async ({ userId, data }) => {
|
||||||
|
const user = await DBClient.instance.user.findUnique({
|
||||||
|
where: { id: userId },
|
||||||
|
select: userSelect,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new ServerError('User not found', 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedUser = await DBClient.instance.user.update({
|
||||||
|
where: { id: userId },
|
||||||
|
data: {
|
||||||
|
userAvatar: {
|
||||||
|
upsert: {
|
||||||
|
create: {
|
||||||
|
alt: data.alt,
|
||||||
|
path: data.path,
|
||||||
|
caption: data.caption,
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
alt: data.alt,
|
||||||
|
path: data.path,
|
||||||
|
caption: data.caption,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
select: userSelect,
|
||||||
|
});
|
||||||
|
|
||||||
|
return updatedUser;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a user's profile by id.
|
||||||
|
*
|
||||||
|
* @param args The arguments for service.
|
||||||
|
* @param args.userId The id of the user to update.
|
||||||
|
* @param args.data The data to update the user with.
|
||||||
|
* @param args.data.bio The bio of the user.
|
||||||
|
* @returns The user.
|
||||||
|
*/
|
||||||
|
export const updateUserProfileById: UpdateUserProfileById = async ({ userId, data }) => {
|
||||||
|
const user = await DBClient.instance.user.update({
|
||||||
|
where: { id: userId },
|
||||||
|
data: { bio: data.bio },
|
||||||
|
select: userSelect,
|
||||||
|
});
|
||||||
|
|
||||||
|
return user;
|
||||||
|
};
|
||||||
38
src/services/users/profile/types/index.ts
Normal file
38
src/services/users/profile/types/index.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { UserFollow } from '@prisma/client';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import FollowInfoSchema from '../schema/FollowInfoSchema';
|
||||||
|
import GetUserSchema from '../../auth/schema/GetUserSchema';
|
||||||
|
|
||||||
|
type FollowInfo = z.infer<typeof FollowInfoSchema>;
|
||||||
|
type User = z.infer<typeof GetUserSchema>;
|
||||||
|
|
||||||
|
export type UserFollowService = (args: {
|
||||||
|
followerId: string;
|
||||||
|
followingId: string;
|
||||||
|
}) => Promise<UserFollow | null>;
|
||||||
|
|
||||||
|
export type UpdateUserProfileById = (args: {
|
||||||
|
userId: string;
|
||||||
|
data: { bio: string };
|
||||||
|
}) => Promise<User>;
|
||||||
|
|
||||||
|
export type CheckIfUserIsFollowedBySessionUser = (args: {
|
||||||
|
followerId: string;
|
||||||
|
followingId: string;
|
||||||
|
}) => Promise<boolean>;
|
||||||
|
|
||||||
|
export type GetUsersFollowedByOrFollowingUser = (args: {
|
||||||
|
userId: string;
|
||||||
|
pageNum: number;
|
||||||
|
pageSize: number;
|
||||||
|
}) => Promise<{ follows: FollowInfo[]; count: number }>;
|
||||||
|
|
||||||
|
export type UpdateUserAvatar = (args: {
|
||||||
|
userId: string;
|
||||||
|
data: {
|
||||||
|
alt: string;
|
||||||
|
path: string;
|
||||||
|
caption: string;
|
||||||
|
};
|
||||||
|
}) => Promise<User>;
|
||||||
Reference in New Issue
Block a user