From e26961c3f5950b003a74b5f25a484c94a83cf71b Mon Sep 17 00:00:00 2001 From: Disura Randunu <37591051+Disura-Randunu@users.noreply.github.com> Date: Mon, 16 Sep 2024 21:47:09 +0530 Subject: [PATCH] Admin API for Mentor Details Update (#169) --- src/controllers/admin/mentor.controller.ts | 62 +++++++++++ src/middlewares/requestValidator.ts | 6 +- src/routes/admin/mentor/mentor.route.ts | 4 +- .../admin/admin.mentor-routes.schema.ts | 8 ++ src/services/admin/mentor.service.ts | 100 +++++++++++++++++- src/utils.ts | 9 ++ 6 files changed, 186 insertions(+), 3 deletions(-) diff --git a/src/controllers/admin/mentor.controller.ts b/src/controllers/admin/mentor.controller.ts index 18c5f0c..b0a2b26 100644 --- a/src/controllers/admin/mentor.controller.ts +++ b/src/controllers/admin/mentor.controller.ts @@ -6,6 +6,7 @@ import { findAllMentorEmails, getAllMentors, getMentor, + updateMentorDetails, updateMentorStatus } from '../../services/admin/mentor.service' import { @@ -13,6 +14,67 @@ import { updateAvailability } from '../../services/mentor.service' import type { ApiResponse, PaginatedApiResponse } from '../../types' +import { IMG_HOST } from '../../configs/envConfig' +import { formatValidationErrors, upload } from '../../utils' +import { mentorUpdateSchema } from '../../schemas/admin/admin.mentor-routes.schema' + +export const updateMentorHandler = async ( + req: Request, + res: Response +): Promise> => { + const user = req.user as Profile + + if (user.type !== ProfileTypes.ADMIN) { + return res.status(403).json({ message: 'Only Admins are allowed' }) + } + + try { + await new Promise((resolve, reject) => { + upload(req, res, (err) => { + if (err) { + reject(err) + } else { + resolve() + } + }) + }) + + const data = req.body.data ? JSON.parse(req.body.data) : req.body + + const result = mentorUpdateSchema.safeParse(data) + if (!result.success) { + return res.status(400).json({ + error: 'Invalid data', + details: formatValidationErrors(result.error) + }) + } + + const mentorUpdateData: Partial = { ...data } + const profileUpdateData: Partial = { ...data.profile } + + if (req.file) { + profileUpdateData.image_url = `${IMG_HOST}/${req.file.filename}` + } + + const { mentorId } = req.params + + const { mentor, statusCode, message } = await updateMentorDetails( + mentorId, + mentorUpdateData, + profileUpdateData + ) + return res.status(statusCode).json({ mentor, message }) + } catch (err) { + if (err instanceof Error) { + console.error('Error updating mentor details:', err) + return res.status(500).json({ + error: 'Internal server error', + message: err.message + }) + } + throw err + } +} export const mentorStatusHandler = async ( req: Request, diff --git a/src/middlewares/requestValidator.ts b/src/middlewares/requestValidator.ts index 6545928..7dd160d 100644 --- a/src/middlewares/requestValidator.ts +++ b/src/middlewares/requestValidator.ts @@ -4,7 +4,11 @@ import { ZodError, type ZodSchema } from 'zod' export const requestBodyValidator = (schema: T) => { return (req: Request, res: Response, next: NextFunction) => { try { - schema.parse(req.body) + if (req.body.data) { + schema.parse(JSON.parse(req.body.data)) + } else { + schema.parse(req.body) + } next() } catch (err) { console.log(err) diff --git a/src/routes/admin/mentor/mentor.route.ts b/src/routes/admin/mentor/mentor.route.ts index a312299..98902ef 100644 --- a/src/routes/admin/mentor/mentor.route.ts +++ b/src/routes/admin/mentor/mentor.route.ts @@ -5,7 +5,8 @@ import { mentorDetailsHandler, mentorStatusHandler, searchMentors, - updateMentorAvailability + updateMentorAvailability, + updateMentorHandler } from '../../../controllers/admin/mentor.controller' import { requireAuth } from '../../../controllers/auth.controller' import { @@ -23,6 +24,7 @@ import { paginationSchema } from '../../../schemas/common/pagination-request.sch const mentorRouter = express.Router() +mentorRouter.put('/:mentorId', requireAuth, updateMentorHandler) mentorRouter.put( '/:mentorId/state', [requireAuth, requestBodyValidator(mentorStatusSchema)], diff --git a/src/schemas/admin/admin.mentor-routes.schema.ts b/src/schemas/admin/admin.mentor-routes.schema.ts index 64006cf..46de531 100644 --- a/src/schemas/admin/admin.mentor-routes.schema.ts +++ b/src/schemas/admin/admin.mentor-routes.schema.ts @@ -1,5 +1,6 @@ import { z } from 'zod' import { MentorApplicationStatus } from '../../enums' +import { updateProfileSchema } from '../profile-routes.schema' export const mentorStatusSchema = z.object({ state: z.nativeEnum(MentorApplicationStatus) @@ -20,3 +21,10 @@ export const updateMentorAvailabilitySchema = z.object({ export const searchMentorsSchema = z.object({ q: z.string().or(z.undefined()) }) + +export const mentorUpdateSchema = z.object({ + availability: z.boolean().optional(), + application: z.record(z.string(), z.any()).optional(), + category: z.string().uuid().optional(), + profile: updateProfileSchema.optional() +}) diff --git a/src/services/admin/mentor.service.ts b/src/services/admin/mentor.service.ts index aa062fb..bd2fc85 100644 --- a/src/services/admin/mentor.service.ts +++ b/src/services/admin/mentor.service.ts @@ -1,7 +1,9 @@ import { dataSource } from '../../configs/dbConfig' +import Category from '../../entities/category.entity' import Mentor from '../../entities/mentor.entity' +import Profile from '../../entities/profile.entity' import type { MentorApplicationStatus } from '../../enums' -import { type PaginatedApiResponse } from '../../types' +import { type CreateProfile, type PaginatedApiResponse } from '../../types' import { getEmailContent } from '../../utils' import { sendEmail } from './email.service' @@ -54,6 +56,102 @@ export const updateMentorStatus = async ( } } +export const updateMentorDetails = async ( + mentorId: string, + mentorData: Partial, + profileData?: Partial +): Promise<{ + statusCode: number + mentor?: Mentor | null + message: string +}> => { + try { + const mentorRepository = dataSource.getRepository(Mentor) + const profileRepository = dataSource.getRepository(Profile) + const categoryRepository = dataSource.getRepository(Category) + + const mentor = await mentorRepository.findOne({ + where: { uuid: mentorId }, + relations: ['profile'] + }) + + if (!mentor) { + return { + statusCode: 404, + message: 'Mentor not found' + } + } + + if (mentorData.availability !== undefined) { + mentor.availability = mentorData.availability + } + + if (mentorData.category) { + if (typeof mentorData.category === 'string') { + const category = await categoryRepository.findOne({ + where: { uuid: mentorData.category } + }) + + if (!category) { + return { + statusCode: 404, + message: 'Category not found' + } + } + mentor.category = category + } + } + + // will override values of keys if exisitng keys provided. add new key-value pairs if not exists + if (mentorData.application) { + mentor.application = { + ...mentor.application, + ...mentorData.application + } + } + + await mentorRepository.save(mentor) + + if (profileData && mentor.profile) { + const updatedProfileData: Partial = {} + + if (profileData.primary_email) { + updatedProfileData.primary_email = profileData.primary_email + } + if (profileData.first_name) { + updatedProfileData.first_name = profileData.first_name + } + if (profileData.last_name) { + updatedProfileData.last_name = profileData.last_name + } + if (profileData.image_url) { + updatedProfileData.image_url = profileData.image_url + } + + if (Object.keys(updatedProfileData).length > 0) { + await profileRepository.update( + { uuid: mentor.profile.uuid }, + updatedProfileData as CreateProfile + ) + } + } + + const updatedMentor = await mentorRepository.findOne({ + where: { uuid: mentorId }, + relations: ['profile', 'category'] + }) + + return { + statusCode: 200, + mentor: updatedMentor, + message: 'Updated Mentor details successfully' + } + } catch (err) { + console.error('Error updating the mentor details', err) + throw new Error('Error updating the mentor details') + } +} + export const getAllMentors = async ({ status, pageNumber, diff --git a/src/utils.ts b/src/utils.ts index 839e423..51d1a0f 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -10,6 +10,7 @@ import { generateCertificate } from './services/admin/generateCertificate' import { randomUUID } from 'crypto' import { certificatesDir } from './app' import type Mentee from './entities/mentee.entity' +import { type ZodError } from 'zod' export const signAndSetCookie = (res: Response, uuid: string): void => { const token = jwt.sign({ userId: uuid }, JWT_SECRET ?? '') @@ -290,3 +291,11 @@ export const getPasswordChangedEmailContent = ( export const capitalizeFirstLetter = (word: string): string => { return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase() } + +export const formatValidationErrors = ( + err: ZodError +): Array<{ message: string }> => { + return err.errors.map((issue) => ({ + message: `${issue.path.join('.')} is ${issue.message}` + })) +}