init commit,
This commit is contained in:
124
03_source/api_server.del/src/services/auth.service.ts
Normal file
124
03_source/api_server.del/src/services/auth.service.ts
Normal 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
|
||||
};
|
66
03_source/api_server.del/src/services/email.service.ts
Normal file
66
03_source/api_server.del/src/services/email.service.ts
Normal 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
|
||||
};
|
156
03_source/api_server.del/src/services/event.service.ts
Normal file
156
03_source/api_server.del/src/services/event.service.ts
Normal 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
|
||||
};
|
5
03_source/api_server.del/src/services/index.ts
Normal file
5
03_source/api_server.del/src/services/index.ts
Normal 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';
|
147
03_source/api_server.del/src/services/member.service.ts
Normal file
147
03_source/api_server.del/src/services/member.service.ts
Normal 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
|
||||
};
|
164
03_source/api_server.del/src/services/order.service.ts
Normal file
164
03_source/api_server.del/src/services/order.service.ts
Normal 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
|
||||
};
|
147
03_source/api_server.del/src/services/profile.service.ts
Normal file
147
03_source/api_server.del/src/services/profile.service.ts
Normal 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
|
||||
};
|
140
03_source/api_server.del/src/services/token.service.ts
Normal file
140
03_source/api_server.del/src/services/token.service.ts
Normal 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
|
||||
};
|
170
03_source/api_server.del/src/services/user.service.ts
Normal file
170
03_source/api_server.del/src/services/user.service.ts
Normal 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
|
||||
};
|
Reference in New Issue
Block a user