init commit,

This commit is contained in:
louiscklaw
2025-05-28 09:55:51 +08:00
commit efe70ceb69
8042 changed files with 951668 additions and 0 deletions

View File

@@ -0,0 +1,124 @@
import httpStatus from 'http-status';
import tokenService from './token.service';
import userService from './user.service';
import ApiError from '../utils/ApiError';
import { TokenType, User } from '@prisma/client';
import prisma from '../client';
import { encryptPassword, isPasswordMatch } from '../utils/encryption';
import { AuthTokensResponse } from '../types/response';
import exclude from '../utils/exclude';
/**
* Login with username and password
* @param {string} email
* @param {string} password
* @returns {Promise<Omit<User, 'password'>>}
*/
const loginUserWithEmailAndPassword = async (
email: string,
password: string
): Promise<Omit<User, 'password'>> => {
const user = await userService.getUserByEmail(email, [
'id',
'email',
'name',
'password',
'role',
'isEmailVerified',
'createdAt',
'updatedAt'
]);
if (!user || !(await isPasswordMatch(password, user.password as string))) {
throw new ApiError(httpStatus.UNAUTHORIZED, 'Incorrect email or password');
}
return exclude(user, ['password']);
};
/**
* Logout
* @param {string} refreshToken
* @returns {Promise<void>}
*/
const logout = async (refreshToken: string): Promise<void> => {
const refreshTokenData = await prisma.token.findFirst({
where: {
token: refreshToken,
type: TokenType.REFRESH,
blacklisted: false
}
});
if (!refreshTokenData) {
throw new ApiError(httpStatus.NOT_FOUND, 'Not found');
}
await prisma.token.delete({ where: { id: refreshTokenData.id } });
};
/**
* Refresh auth tokens
* @param {string} refreshToken
* @returns {Promise<AuthTokensResponse>}
*/
const refreshAuth = async (refreshToken: string): Promise<AuthTokensResponse> => {
try {
const refreshTokenData = await tokenService.verifyToken(refreshToken, TokenType.REFRESH);
const { userId } = refreshTokenData;
await prisma.token.delete({ where: { id: refreshTokenData.id } });
return tokenService.generateAuthTokens({ id: userId });
} catch (error) {
throw new ApiError(httpStatus.UNAUTHORIZED, 'Please authenticate');
}
};
/**
* Reset password
* @param {string} resetPasswordToken
* @param {string} newPassword
* @returns {Promise<void>}
*/
const resetPassword = async (resetPasswordToken: string, newPassword: string): Promise<void> => {
try {
const resetPasswordTokenData = await tokenService.verifyToken(
resetPasswordToken,
TokenType.RESET_PASSWORD
);
const user = await userService.getUserById(resetPasswordTokenData.userId);
if (!user) {
throw new Error();
}
const encryptedPassword = await encryptPassword(newPassword);
await userService.updateUserById(user.id, { password: encryptedPassword });
await prisma.token.deleteMany({ where: { userId: user.id, type: TokenType.RESET_PASSWORD } });
} catch (error) {
throw new ApiError(httpStatus.UNAUTHORIZED, 'Password reset failed');
}
};
/**
* Verify email
* @param {string} verifyEmailToken
* @returns {Promise<void>}
*/
const verifyEmail = async (verifyEmailToken: string): Promise<void> => {
try {
const verifyEmailTokenData = await tokenService.verifyToken(
verifyEmailToken,
TokenType.VERIFY_EMAIL
);
await prisma.token.deleteMany({
where: { userId: verifyEmailTokenData.userId, type: TokenType.VERIFY_EMAIL }
});
await userService.updateUserById(verifyEmailTokenData.userId, { isEmailVerified: true });
} catch (error) {
throw new ApiError(httpStatus.UNAUTHORIZED, 'Email verification failed');
}
};
export default {
loginUserWithEmailAndPassword,
isPasswordMatch,
encryptPassword,
logout,
refreshAuth,
resetPassword,
verifyEmail
};

View File

@@ -0,0 +1,66 @@
import nodemailer from 'nodemailer';
import config from '../config/config';
import logger from '../config/logger';
const transport = nodemailer.createTransport(config.email.smtp);
/* istanbul ignore next */
if (config.env !== 'test') {
transport
.verify()
.then(() => logger.info('Connected to email server'))
.catch(() =>
logger.warn(
'Unable to connect to email server. Make sure you have configured the SMTP options in .env'
)
);
}
/**
* Send an email
* @param {string} to
* @param {string} subject
* @param {string} text
* @returns {Promise}
*/
const sendEmail = async (to: string, subject: string, text: string) => {
const msg = { from: config.email.from, to, subject, text };
await transport.sendMail(msg);
};
/**
* Send reset password email
* @param {string} to
* @param {string} token
* @returns {Promise}
*/
const sendResetPasswordEmail = async (to: string, token: string) => {
const subject = 'Reset password';
// replace this url with the link to the reset password page of your front-end app
const resetPasswordUrl = `http://link-to-app/reset-password?token=${token}`;
const text = `Dear user,
To reset your password, click on this link: ${resetPasswordUrl}
If you did not request any password resets, then ignore this email.`;
await sendEmail(to, subject, text);
};
/**
* Send verification email
* @param {string} to
* @param {string} token
* @returns {Promise}
*/
const sendVerificationEmail = async (to: string, token: string) => {
const subject = 'Email Verification';
// replace this url with the link to the email verification page of your front-end app
const verificationEmailUrl = `http://link-to-app/verify-email?token=${token}`;
const text = `Dear user,
To verify your email, click on this link: ${verificationEmailUrl}`;
await sendEmail(to, subject, text);
};
export default {
transport,
sendEmail,
sendResetPasswordEmail,
sendVerificationEmail
};

View File

@@ -0,0 +1,156 @@
import { User, Role, Prisma, Event } from '@prisma/client';
import httpStatus from 'http-status';
import prisma from '../client';
import ApiError from '../utils/ApiError';
import { encryptPassword } from '../utils/encryption';
/**
* Create a user
* @param {Object} userBody
* @returns {Promise<User>}
*/
// const createUser = async (
// email: string,
// password: string,
// name?: string,
// role: Role = Role.USER
// ): Promise<User> => {
// if (await getEventByEmail(email)) {
// throw new ApiError(httpStatus.BAD_REQUEST, 'Email already taken');
// }
// return prisma.user.create({
// data: {
// email,
// name,
// password: await encryptPassword(password),
// role
// }
// });
// };
/**
* Query for users
* @param {Object} filter - Prisma filter
* @param {Object} options - Query options
* @param {string} [options.sortBy] - Sort option in the format: sortField:(desc|asc)
* @param {number} [options.limit] - Maximum number of results per page (default = 10)
* @param {number} [options.page] - Current page (default = 1)
* @returns {Promise<QueryResult>}
*/
const queryEvents = async <Key extends keyof Event>(
filter: object,
options: {
limit?: number;
page?: number;
sortBy?: string;
sortType?: 'asc' | 'desc';
},
keys: Key[] = [
'id',
'email',
'name',
'password',
'role',
'isEmailVerified',
'createdAt',
'updatedAt'
] as Key[]
): Promise<Pick<Event, Key>[]> => {
// const page = options.page ?? 1;
// const limit = options.limit ?? 10;
// const sortBy = options.sortBy;
// const sortType = options.sortType ?? 'desc';
const events = await prisma.event.findMany();
return events as Pick<Event, Key>[];
};
/**
* Get event by id
* @param {ObjectId} id
* @param {Array<Key>} keys
* @returns {Promise<Pick<Event, Key> | null>}
*/
const getEventById = async <Key extends keyof Event>(
id: number,
keys: Key[] = ['id'] as Key[]
): Promise<Pick<Event, Key> | null> => {
return prisma.event.findUnique({
where: { id }
// select: keys.reduce((obj, k) => ({ ...obj, [k]: true }), {})
}) as Promise<Pick<Event, Key> | null>;
};
/**
* Get event by email
* @param {string} email
* @param {Array<Key>} keys
* @returns {Promise<Pick<User, Key> | null>}
*/
// const getEventByEmail = async <Key extends keyof User>(
// email: string,
// keys: Key[] = [
// 'id',
// 'email',
// 'name',
// 'password',
// 'role',
// 'isEmailVerified',
// 'createdAt',
// 'updatedAt'
// ] as Key[]
// ): Promise<Pick<User, Key> | null> => {
// return prisma.user.findUnique({
// where: { email },
// select: keys.reduce((obj, k) => ({ ...obj, [k]: true }), {})
// }) as Promise<Pick<User, Key> | null>;
// };
/**
* Update user by id
* @param {ObjectId} userId
* @param {Object} updateBody
* @returns {Promise<User>}
*/
// const updateUserById = async <Key extends keyof User>(
// userId: number,
// updateBody: Prisma.UserUpdateInput,
// keys: Key[] = ['id', 'email', 'name', 'role'] as Key[]
// ): Promise<Pick<User, Key> | null> => {
// const user = await getEventById(userId, ['id', 'email', 'name']);
// if (!user) {
// throw new ApiError(httpStatus.NOT_FOUND, 'User not found');
// }
// if (updateBody.email && (await getEventByEmail(updateBody.email as string))) {
// throw new ApiError(httpStatus.BAD_REQUEST, 'Email already taken');
// }
// const updatedUser = await prisma.user.update({
// where: { id: user.id },
// data: updateBody,
// select: keys.reduce((obj, k) => ({ ...obj, [k]: true }), {})
// });
// return updatedUser as Pick<User, Key> | null;
// };
/**
* Delete user by id
* @param {ObjectId} userId
* @returns {Promise<User>}
*/
// const deleteUserById = async (userId: number): Promise<User> => {
// const user = await getEventById(userId);
// if (!user) {
// throw new ApiError(httpStatus.NOT_FOUND, 'User not found');
// }
// await prisma.user.delete({ where: { id: user.id } });
// return user;
// };
export default {
// createUser,
queryEvents,
getEventById
// getEventById,
// getEventByEmail,
// updateUserById,
// deleteUserById
};

View File

@@ -0,0 +1,5 @@
export { default as authService } from './auth.service';
export { default as userService } from './user.service';
export { default as tokenService } from './token.service';
export { default as emailService } from './email.service';
export { default as eventService } from './event.service';

View File

@@ -0,0 +1,147 @@
import { User, Role, Prisma, Event, Member } from '@prisma/client';
import httpStatus from 'http-status';
import prisma from '../client';
import ApiError from '../utils/ApiError';
import { encryptPassword } from '../utils/encryption';
/**
* Create a user
* @param {Object} userBody
* @returns {Promise<User>}
*/
// const createUser = async (
// email: string,
// password: string,
// name?: string,
// role: Role = Role.USER
// ): Promise<User> => {
// if (await getEventByEmail(email)) {
// throw new ApiError(httpStatus.BAD_REQUEST, 'Email already taken');
// }
// return prisma.user.create({
// data: {
// email,
// name,
// password: await encryptPassword(password),
// role
// }
// });
// };
/**
* Query for users
* @param {Object} filter - Prisma filter
* @param {Object} options - Query options
* @param {string} [options.sortBy] - Sort option in the format: sortField:(desc|asc)
* @param {number} [options.limit] - Maximum number of results per page (default = 10)
* @param {number} [options.page] - Current page (default = 1)
* @returns {Promise<QueryResult>}
*/
const queryMembers = async <Key extends keyof Member>(
filter: object,
options: {
limit?: number;
page?: number;
sortBy?: string;
sortType?: 'asc' | 'desc';
},
keys: Key[] = ['id'] as Key[]
): Promise<Pick<Member, Key>[]> => {
// const page = options.page ?? 1;
// const limit = options.limit ?? 10;
// const sortBy = options.sortBy;
// const sortType = options.sortType ?? 'desc';
const members = await prisma.member.findMany();
return members as Pick<Member, Key>[];
};
/**
* Get event by id
* @param {ObjectId} id
* @param {Array<Key>} keys
* @returns {Promise<Pick<Event, Key> | null>}
*/
const getMemberById = async <Key extends keyof Member>(
id: number,
keys: Key[] = ['id'] as Key[]
): Promise<Pick<Member, Key> | null> => {
return prisma.member.findUnique({
where: { id }
// select: keys.reduce((obj, k) => ({ ...obj, [k]: true }), {})
}) as Promise<Pick<Member, Key> | null>;
};
/**
* Get event by email
* @param {string} email
* @param {Array<Key>} keys
* @returns {Promise<Pick<User, Key> | null>}
*/
// const getEventByEmail = async <Key extends keyof User>(
// email: string,
// keys: Key[] = [
// 'id',
// 'email',
// 'name',
// 'password',
// 'role',
// 'isEmailVerified',
// 'createdAt',
// 'updatedAt'
// ] as Key[]
// ): Promise<Pick<User, Key> | null> => {
// return prisma.user.findUnique({
// where: { email },
// select: keys.reduce((obj, k) => ({ ...obj, [k]: true }), {})
// }) as Promise<Pick<User, Key> | null>;
// };
/**
* Update user by id
* @param {ObjectId} userId
* @param {Object} updateBody
* @returns {Promise<User>}
*/
// const updateUserById = async <Key extends keyof User>(
// userId: number,
// updateBody: Prisma.UserUpdateInput,
// keys: Key[] = ['id', 'email', 'name', 'role'] as Key[]
// ): Promise<Pick<User, Key> | null> => {
// const user = await getEventById(userId, ['id', 'email', 'name']);
// if (!user) {
// throw new ApiError(httpStatus.NOT_FOUND, 'User not found');
// }
// if (updateBody.email && (await getEventByEmail(updateBody.email as string))) {
// throw new ApiError(httpStatus.BAD_REQUEST, 'Email already taken');
// }
// const updatedUser = await prisma.user.update({
// where: { id: user.id },
// data: updateBody,
// select: keys.reduce((obj, k) => ({ ...obj, [k]: true }), {})
// });
// return updatedUser as Pick<User, Key> | null;
// };
/**
* Delete user by id
* @param {ObjectId} userId
* @returns {Promise<User>}
*/
// const deleteUserById = async (userId: number): Promise<User> => {
// const user = await getEventById(userId);
// if (!user) {
// throw new ApiError(httpStatus.NOT_FOUND, 'User not found');
// }
// await prisma.user.delete({ where: { id: user.id } });
// return user;
// };
export default {
// createUser,
queryMembers,
getMemberById
// getEventById,
// getEventByEmail,
// updateUserById,
// deleteUserById
};

View File

@@ -0,0 +1,164 @@
// REQ0047/order-page
//
// PURPOSE:
// - provide api access to backend db for orders
//
// RULES:
// - T.B.A.
//
import { User, Role, Prisma, Event, Order } from '@prisma/client';
import httpStatus from 'http-status';
import prisma from '../client';
import ApiError from '../utils/ApiError';
import { encryptPassword } from '../utils/encryption';
/**
* Create a user
* @param {Object} userBody
* @returns {Promise<User>}
*/
// const createUser = async (
// email: string,
// password: string,
// name?: string,
// role: Role = Role.USER
// ): Promise<User> => {
// if (await getEventByEmail(email)) {
// throw new ApiError(httpStatus.BAD_REQUEST, 'Email already taken');
// }
// return prisma.user.create({
// data: {
// email,
// name,
// password: await encryptPassword(password),
// role
// }
// });
// };
/**
* Query for users
* @param {Object} filter - Prisma filter
* @param {Object} options - Query options
* @param {string} [options.sortBy] - Sort option in the format: sortField:(desc|asc)
* @param {number} [options.limit] - Maximum number of results per page (default = 10)
* @param {number} [options.page] - Current page (default = 1)
* @returns {Promise<QueryResult>}
*/
const queryOrders = async <Key extends keyof Order>(
filter: object,
options: {
limit?: number;
page?: number;
sortBy?: string;
sortType?: 'asc' | 'desc';
},
keys: Key[] = [
'id',
'email',
'name',
'password',
'role',
'isEmailVerified',
'createdAt',
'updatedAt'
] as Key[]
): Promise<Pick<Order, Key>[]> => {
// const page = options.page ?? 1;
// const limit = options.limit ?? 10;
// const sortBy = options.sortBy;
// const sortType = options.sortType ?? 'desc';
const events = await prisma.order.findMany();
return events as Pick<Order, Key>[];
};
/**
* Get event by id
* @param {ObjectId} id
* @param {Array<Key>} keys
* @returns {Promise<Pick<Event, Key> | null>}
*/
const getOrderById = async <Key extends keyof Order>(
id: number,
keys: Key[] = ['id'] as Key[]
): Promise<Pick<Order, Key> | null> => {
return prisma.order.findUnique({
where: { id }
// select: keys.reduce((obj, k) => ({ ...obj, [k]: true }), {})
}) as Promise<Pick<Order, Key> | null>;
};
/**
* Get event by email
* @param {string} email
* @param {Array<Key>} keys
* @returns {Promise<Pick<User, Key> | null>}
*/
// const getEventByEmail = async <Key extends keyof User>(
// email: string,
// keys: Key[] = [
// 'id',
// 'email',
// 'name',
// 'password',
// 'role',
// 'isEmailVerified',
// 'createdAt',
// 'updatedAt'
// ] as Key[]
// ): Promise<Pick<User, Key> | null> => {
// return prisma.user.findUnique({
// where: { email },
// select: keys.reduce((obj, k) => ({ ...obj, [k]: true }), {})
// }) as Promise<Pick<User, Key> | null>;
// };
/**
* Update user by id
* @param {ObjectId} userId
* @param {Object} updateBody
* @returns {Promise<User>}
*/
// const updateUserById = async <Key extends keyof User>(
// userId: number,
// updateBody: Prisma.UserUpdateInput,
// keys: Key[] = ['id', 'email', 'name', 'role'] as Key[]
// ): Promise<Pick<User, Key> | null> => {
// const user = await getEventById(userId, ['id', 'email', 'name']);
// if (!user) {
// throw new ApiError(httpStatus.NOT_FOUND, 'User not found');
// }
// if (updateBody.email && (await getEventByEmail(updateBody.email as string))) {
// throw new ApiError(httpStatus.BAD_REQUEST, 'Email already taken');
// }
// const updatedUser = await prisma.user.update({
// where: { id: user.id },
// data: updateBody,
// select: keys.reduce((obj, k) => ({ ...obj, [k]: true }), {})
// });
// return updatedUser as Pick<User, Key> | null;
// };
/**
* Delete user by id
* @param {ObjectId} userId
* @returns {Promise<User>}
*/
// const deleteUserById = async (userId: number): Promise<User> => {
// const user = await getEventById(userId);
// if (!user) {
// throw new ApiError(httpStatus.NOT_FOUND, 'User not found');
// }
// await prisma.user.delete({ where: { id: user.id } });
// return user;
// };
export default {
// createUser,
queryOrders,
getOrderById
// getEventById,
// getEventByEmail,
// updateUserById,
// deleteUserById
};

View File

@@ -0,0 +1,147 @@
import { User, Role, Prisma, Event, Member } from '@prisma/client';
import httpStatus from 'http-status';
import prisma from '../client';
import ApiError from '../utils/ApiError';
import { encryptPassword } from '../utils/encryption';
/**
* Create a user
* @param {Object} userBody
* @returns {Promise<User>}
*/
// const createUser = async (
// email: string,
// password: string,
// name?: string,
// role: Role = Role.USER
// ): Promise<User> => {
// if (await getEventByEmail(email)) {
// throw new ApiError(httpStatus.BAD_REQUEST, 'Email already taken');
// }
// return prisma.user.create({
// data: {
// email,
// name,
// password: await encryptPassword(password),
// role
// }
// });
// };
/**
* Query for users
* @param {Object} filter - Prisma filter
* @param {Object} options - Query options
* @param {string} [options.sortBy] - Sort option in the format: sortField:(desc|asc)
* @param {number} [options.limit] - Maximum number of results per page (default = 10)
* @param {number} [options.page] - Current page (default = 1)
* @returns {Promise<QueryResult>}
*/
const queryMembers = async <Key extends keyof Member>(
filter: object,
options: {
limit?: number;
page?: number;
sortBy?: string;
sortType?: 'asc' | 'desc';
},
keys: Key[] = ['id'] as Key[]
): Promise<Pick<Member, Key>[]> => {
// const page = options.page ?? 1;
// const limit = options.limit ?? 10;
// const sortBy = options.sortBy;
// const sortType = options.sortType ?? 'desc';
const members = await prisma.member.findMany();
return members as Pick<Member, Key>[];
};
/**
* Get event by id
* @param {ObjectId} id
* @param {Array<Key>} keys
* @returns {Promise<Pick<Event, Key> | null>}
*/
const getProfileById = async <Key extends keyof Member>(
id: number,
keys: Key[] = ['id'] as Key[]
): Promise<Pick<Member, Key> | null> => {
return prisma.member.findUnique({
where: { id }
// select: keys.reduce((obj, k) => ({ ...obj, [k]: true }), {})
}) as Promise<Pick<Member, Key> | null>;
};
/**
* Get event by email
* @param {string} email
* @param {Array<Key>} keys
* @returns {Promise<Pick<User, Key> | null>}
*/
// const getEventByEmail = async <Key extends keyof User>(
// email: string,
// keys: Key[] = [
// 'id',
// 'email',
// 'name',
// 'password',
// 'role',
// 'isEmailVerified',
// 'createdAt',
// 'updatedAt'
// ] as Key[]
// ): Promise<Pick<User, Key> | null> => {
// return prisma.user.findUnique({
// where: { email },
// select: keys.reduce((obj, k) => ({ ...obj, [k]: true }), {})
// }) as Promise<Pick<User, Key> | null>;
// };
/**
* Update user by id
* @param {ObjectId} userId
* @param {Object} updateBody
* @returns {Promise<User>}
*/
// const updateUserById = async <Key extends keyof User>(
// userId: number,
// updateBody: Prisma.UserUpdateInput,
// keys: Key[] = ['id', 'email', 'name', 'role'] as Key[]
// ): Promise<Pick<User, Key> | null> => {
// const user = await getEventById(userId, ['id', 'email', 'name']);
// if (!user) {
// throw new ApiError(httpStatus.NOT_FOUND, 'User not found');
// }
// if (updateBody.email && (await getEventByEmail(updateBody.email as string))) {
// throw new ApiError(httpStatus.BAD_REQUEST, 'Email already taken');
// }
// const updatedUser = await prisma.user.update({
// where: { id: user.id },
// data: updateBody,
// select: keys.reduce((obj, k) => ({ ...obj, [k]: true }), {})
// });
// return updatedUser as Pick<User, Key> | null;
// };
/**
* Delete user by id
* @param {ObjectId} userId
* @returns {Promise<User>}
*/
// const deleteUserById = async (userId: number): Promise<User> => {
// const user = await getEventById(userId);
// if (!user) {
// throw new ApiError(httpStatus.NOT_FOUND, 'User not found');
// }
// await prisma.user.delete({ where: { id: user.id } });
// return user;
// };
export default {
// createUser,
queryMembers,
getProfileById
// getEventById,
// getEventByEmail,
// updateUserById,
// deleteUserById
};

View File

@@ -0,0 +1,140 @@
import jwt from 'jsonwebtoken';
import moment, { Moment } from 'moment';
import httpStatus from 'http-status';
import config from '../config/config';
import userService from './user.service';
import ApiError from '../utils/ApiError';
import { Token, TokenType } from '@prisma/client';
import prisma from '../client';
import { AuthTokensResponse } from '../types/response';
/**
* Generate token
* @param {number} userId
* @param {Moment} expires
* @param {string} type
* @param {string} [secret]
* @returns {string}
*/
const generateToken = (
userId: number,
expires: Moment,
type: TokenType,
secret = config.jwt.secret
): string => {
const payload = {
sub: userId,
iat: moment().unix(),
exp: expires.unix(),
type
};
return jwt.sign(payload, secret);
};
/**
* Save a token
* @param {string} token
* @param {number} userId
* @param {Moment} expires
* @param {string} type
* @param {boolean} [blacklisted]
* @returns {Promise<Token>}
*/
const saveToken = async (
token: string,
userId: number,
expires: Moment,
type: TokenType,
blacklisted = false
): Promise<Token> => {
const createdToken = prisma.token.create({
data: {
token,
userId: userId,
expires: expires.toDate(),
type,
blacklisted
}
});
return createdToken;
};
/**
* Verify token and return token doc (or throw an error if it is not valid)
* @param {string} token
* @param {string} type
* @returns {Promise<Token>}
*/
const verifyToken = async (token: string, type: TokenType): Promise<Token> => {
const payload = jwt.verify(token, config.jwt.secret);
const userId = Number(payload.sub);
const tokenData = await prisma.token.findFirst({
where: { token, type, userId, blacklisted: false }
});
if (!tokenData) {
throw new Error('Token not found');
}
return tokenData;
};
/**
* Generate auth tokens
* @param {User} user
* @returns {Promise<AuthTokensResponse>}
*/
const generateAuthTokens = async (user: { id: number }): Promise<AuthTokensResponse> => {
const accessTokenExpires = moment().add(config.jwt.accessExpirationMinutes, 'minutes');
const accessToken = generateToken(user.id, accessTokenExpires, TokenType.ACCESS);
const refreshTokenExpires = moment().add(config.jwt.refreshExpirationDays, 'days');
const refreshToken = generateToken(user.id, refreshTokenExpires, TokenType.REFRESH);
await saveToken(refreshToken, user.id, refreshTokenExpires, TokenType.REFRESH);
return {
access: {
token: accessToken,
expires: accessTokenExpires.toDate()
},
refresh: {
token: refreshToken,
expires: refreshTokenExpires.toDate()
}
};
};
/**
* Generate reset password token
* @param {string} email
* @returns {Promise<string>}
*/
const generateResetPasswordToken = async (email: string): Promise<string> => {
const user = await userService.getUserByEmail(email);
if (!user) {
throw new ApiError(httpStatus.NOT_FOUND, 'No users found with this email');
}
const expires = moment().add(config.jwt.resetPasswordExpirationMinutes, 'minutes');
const resetPasswordToken = generateToken(user.id as number, expires, TokenType.RESET_PASSWORD);
await saveToken(resetPasswordToken, user.id as number, expires, TokenType.RESET_PASSWORD);
return resetPasswordToken;
};
/**
* Generate verify email token
* @param {User} user
* @returns {Promise<string>}
*/
const generateVerifyEmailToken = async (user: { id: number }): Promise<string> => {
const expires = moment().add(config.jwt.verifyEmailExpirationMinutes, 'minutes');
const verifyEmailToken = generateToken(user.id, expires, TokenType.VERIFY_EMAIL);
await saveToken(verifyEmailToken, user.id, expires, TokenType.VERIFY_EMAIL);
return verifyEmailToken;
};
export default {
generateToken,
saveToken,
verifyToken,
generateAuthTokens,
generateResetPasswordToken,
generateVerifyEmailToken
};

View File

@@ -0,0 +1,170 @@
import { User, Role, Prisma } from '@prisma/client';
import httpStatus from 'http-status';
import prisma from '../client';
import ApiError from '../utils/ApiError';
import { encryptPassword } from '../utils/encryption';
/**
* Create a user
* @param {Object} userBody
* @returns {Promise<User>}
*/
const createUser = async (
email: string,
password: string,
name?: string,
role: Role = Role.USER
): Promise<User> => {
if (await getUserByEmail(email)) {
throw new ApiError(httpStatus.BAD_REQUEST, 'Email already taken');
}
return prisma.user.create({
data: {
email,
name,
password: await encryptPassword(password),
role
}
});
};
/**
* Query for users
* @param {Object} filter - Prisma filter
* @param {Object} options - Query options
* @param {string} [options.sortBy] - Sort option in the format: sortField:(desc|asc)
* @param {number} [options.limit] - Maximum number of results per page (default = 10)
* @param {number} [options.page] - Current page (default = 1)
* @returns {Promise<QueryResult>}
*/
const queryUsers = async <Key extends keyof User>(
filter: object,
options: {
limit?: number;
page?: number;
sortBy?: string;
sortType?: 'asc' | 'desc';
},
keys: Key[] = [
'id',
'email',
'name',
'password',
'role',
'isEmailVerified',
'createdAt',
'updatedAt'
] as Key[]
): Promise<Pick<User, Key>[]> => {
const page = options.page ?? 1;
const limit = options.limit ?? 10;
const sortBy = options.sortBy;
const sortType = options.sortType ?? 'desc';
const users = await prisma.user.findMany({
where: filter,
select: keys.reduce((obj, k) => ({ ...obj, [k]: true }), {}),
skip: page * limit,
take: limit,
orderBy: sortBy ? { [sortBy]: sortType } : undefined
});
return users as Pick<User, Key>[];
};
/**
* Get user by id
* @param {ObjectId} id
* @param {Array<Key>} keys
* @returns {Promise<Pick<User, Key> | null>}
*/
const getUserById = async <Key extends keyof User>(
id: number,
keys: Key[] = [
'id',
'email',
'name',
'password',
'role',
'isEmailVerified',
'createdAt',
'updatedAt'
] as Key[]
): Promise<Pick<User, Key> | null> => {
return prisma.user.findUnique({
where: { id },
select: keys.reduce((obj, k) => ({ ...obj, [k]: true }), {})
}) as Promise<Pick<User, Key> | null>;
};
/**
* Get user by email
* @param {string} email
* @param {Array<Key>} keys
* @returns {Promise<Pick<User, Key> | null>}
*/
const getUserByEmail = async <Key extends keyof User>(
email: string,
keys: Key[] = [
'id',
'email',
'name',
'password',
'role',
'isEmailVerified',
'createdAt',
'updatedAt'
] as Key[]
): Promise<Pick<User, Key> | null> => {
return prisma.user.findUnique({
where: { email },
select: keys.reduce((obj, k) => ({ ...obj, [k]: true }), {})
}) as Promise<Pick<User, Key> | null>;
};
/**
* Update user by id
* @param {ObjectId} userId
* @param {Object} updateBody
* @returns {Promise<User>}
*/
const updateUserById = async <Key extends keyof User>(
userId: number,
updateBody: Prisma.UserUpdateInput,
keys: Key[] = ['id', 'email', 'name', 'role'] as Key[]
): Promise<Pick<User, Key> | null> => {
const user = await getUserById(userId, ['id', 'email', 'name']);
if (!user) {
throw new ApiError(httpStatus.NOT_FOUND, 'User not found');
}
if (updateBody.email && (await getUserByEmail(updateBody.email as string))) {
throw new ApiError(httpStatus.BAD_REQUEST, 'Email already taken');
}
const updatedUser = await prisma.user.update({
where: { id: user.id },
data: updateBody,
select: keys.reduce((obj, k) => ({ ...obj, [k]: true }), {})
});
return updatedUser as Pick<User, Key> | null;
};
/**
* Delete user by id
* @param {ObjectId} userId
* @returns {Promise<User>}
*/
const deleteUserById = async (userId: number): Promise<User> => {
const user = await getUserById(userId);
if (!user) {
throw new ApiError(httpStatus.NOT_FOUND, 'User not found');
}
await prisma.user.delete({ where: { id: user.id } });
return user;
};
export default {
createUser,
queryUsers,
getUserById,
getUserByEmail,
updateUserById,
deleteUserById
};