Skip to content

Commit

Permalink
Admin API for Mentor Details Update (#169)
Browse files Browse the repository at this point in the history
  • Loading branch information
Disura-Randunu authored Sep 16, 2024
1 parent d1d6d7a commit e26961c
Show file tree
Hide file tree
Showing 6 changed files with 186 additions and 3 deletions.
62 changes: 62 additions & 0 deletions src/controllers/admin/mentor.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,75 @@ import {
findAllMentorEmails,
getAllMentors,
getMentor,
updateMentorDetails,
updateMentorStatus
} from '../../services/admin/mentor.service'
import {
searchMentorsByQuery,
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<ApiResponse<Mentor>> => {
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<void>((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<Mentor> = { ...data }
const profileUpdateData: Partial<Profile> = { ...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,
Expand Down
6 changes: 5 additions & 1 deletion src/middlewares/requestValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import { ZodError, type ZodSchema } from 'zod'
export const requestBodyValidator = <T extends ZodSchema>(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)
Expand Down
4 changes: 3 additions & 1 deletion src/routes/admin/mentor/mentor.route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import {
mentorDetailsHandler,
mentorStatusHandler,
searchMentors,
updateMentorAvailability
updateMentorAvailability,
updateMentorHandler
} from '../../../controllers/admin/mentor.controller'
import { requireAuth } from '../../../controllers/auth.controller'
import {
Expand All @@ -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)],
Expand Down
8 changes: 8 additions & 0 deletions src/schemas/admin/admin.mentor-routes.schema.ts
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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()
})
100 changes: 99 additions & 1 deletion src/services/admin/mentor.service.ts
Original file line number Diff line number Diff line change
@@ -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'

Expand Down Expand Up @@ -54,6 +56,102 @@ export const updateMentorStatus = async (
}
}

export const updateMentorDetails = async (
mentorId: string,
mentorData: Partial<Mentor>,
profileData?: Partial<Profile>
): 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<Profile> = {}

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,
Expand Down
9 changes: 9 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 ?? '')
Expand Down Expand Up @@ -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}`
}))
}

0 comments on commit e26961c

Please sign in to comment.