mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-02-16 20:13:49 +00:00
Update user auth services
This commit is contained in:
@@ -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