From 68f183933988d498ccc4e62fdf01a86a5c4ec900 Mon Sep 17 00:00:00 2001 From: Krishnadeva Date: Wed, 11 Oct 2023 00:48:03 +0530 Subject: [PATCH 01/15] Revert "Refactored controller" This reverts commit e2416f6d4706062b18a38ff9c3bb103df031bb5c. --- src/controllers/admin/mentor.controller.ts | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/controllers/admin/mentor.controller.ts b/src/controllers/admin/mentor.controller.ts index 15a37fa7..a77f9a64 100644 --- a/src/controllers/admin/mentor.controller.ts +++ b/src/controllers/admin/mentor.controller.ts @@ -78,7 +78,7 @@ export const getAllMentorsByStatus = async ( export const getAllMentorEmails = async ( req: Request, res: Response -): Promise> => { +): Promise => { try { const user = req.user as Profile const status: ApplicationStatus | undefined = req.query.status as @@ -86,22 +86,21 @@ export const getAllMentorEmails = async ( | undefined if (user.type !== ProfileTypes.ADMIN) { - return res.status(403).json({ message: 'Only Admins are allowed' }) - } - - if (status && !(status?.toUpperCase() in ApplicationStatus)) { - return res.status(400).json({ message: 'Please provide a valid status' }) + res.status(403).json({ message: 'Only Admins are allowed' }) + } else { + if (status && !(status?.toUpperCase() in ApplicationStatus)) { + res.status(400).json({ message: 'Please provide a valid status' }) + return + } + const { emails, statusCode, message } = await findAllMentorEmails(status) + res.status(statusCode).json({ emails, message }) } - - const { emails, statusCode, message } = await findAllMentorEmails(status) - return res.status(statusCode).json({ emails, message }) } catch (err) { if (err instanceof Error) { console.error('Error executing query', err) - return res + res .status(500) .json({ error: 'Internal server error', message: err.message }) } - throw err } } From 690d6e980de51da266f604ea49eaee4898e32199 Mon Sep 17 00:00:00 2001 From: Krishnadeva Date: Wed, 11 Oct 2023 00:53:12 +0530 Subject: [PATCH 02/15] Implemented Set mentor availability endpoint (Admin) #28 --- src/controllers/admin/mentor.controller.ts | 52 ++++++++++++++++---- src/routes/admin/mentor/mentor.route.test.ts | 36 ++++++++++++++ src/routes/admin/mentor/mentor.route.ts | 8 ++- src/services/admin/mentor.service.ts | 42 ++++++++++++++++ 4 files changed, 127 insertions(+), 11 deletions(-) diff --git a/src/controllers/admin/mentor.controller.ts b/src/controllers/admin/mentor.controller.ts index a77f9a64..a7f678eb 100644 --- a/src/controllers/admin/mentor.controller.ts +++ b/src/controllers/admin/mentor.controller.ts @@ -2,6 +2,7 @@ import type { Request, Response } from 'express' import { findAllMentorEmails, getAllMentors, + updateAvailability, updateMentorStatus } from '../../services/admin/mentor.service' import { ApplicationStatus, ProfileTypes } from '../../enums' @@ -78,7 +79,7 @@ export const getAllMentorsByStatus = async ( export const getAllMentorEmails = async ( req: Request, res: Response -): Promise => { +): Promise> => { try { const user = req.user as Profile const status: ApplicationStatus | undefined = req.query.status as @@ -86,21 +87,52 @@ export const getAllMentorEmails = async ( | undefined if (user.type !== ProfileTypes.ADMIN) { - res.status(403).json({ message: 'Only Admins are allowed' }) - } else { - if (status && !(status?.toUpperCase() in ApplicationStatus)) { - res.status(400).json({ message: 'Please provide a valid status' }) - return - } - const { emails, statusCode, message } = await findAllMentorEmails(status) - res.status(statusCode).json({ emails, message }) + return res.status(403).json({ message: 'Only Admins are allowed' }) + } + + if (status && !(status?.toUpperCase() in ApplicationStatus)) { + return res.status(400).json({ message: 'Please provide a valid status' }) + } + + const { emails, statusCode, message } = await findAllMentorEmails(status) + return res.status(statusCode).json({ emails, message }) + } catch (err) { + if (err instanceof Error) { + console.error('Error executing query', err) + return res + .status(500) + .json({ error: 'Internal server error', message: err.message }) + } + throw err + } +} + +export const updateMentorAvailability = async ( + req: Request, + res: Response +): Promise> => { + try { + const user = req.user as Profile + const { availability } = req.body + const { mentorId } = req.params + + if (user.type !== ProfileTypes.ADMIN) { + return res.status(403).json({ message: 'Only Admins are allowed' }) } + + const { mentor, statusCode, message } = await updateAvailability( + mentorId, + availability + ) + return res.status(statusCode).json({ mentor, message }) } catch (err) { if (err instanceof Error) { console.error('Error executing query', err) - res + return res .status(500) .json({ error: 'Internal server error', message: err.message }) } + + throw err } } diff --git a/src/routes/admin/mentor/mentor.route.test.ts b/src/routes/admin/mentor/mentor.route.test.ts index d466bd6d..be934908 100644 --- a/src/routes/admin/mentor/mentor.route.test.ts +++ b/src/routes/admin/mentor/mentor.route.test.ts @@ -5,6 +5,7 @@ import Profile from '../../../entities/profile.entity' import { ApplicationStatus, ProfileTypes } from '../../../enums' import { dataSource } from '../../../configs/dbConfig' import bcrypt from 'bcrypt' +import { v4 as uuidv4 } from 'uuid' import { mentorApplicationInfo, mockAdmin, mockMentor } from '../../../../mocks' const port = Math.floor(Math.random() * (9999 - 3000 + 1)) + 3000 @@ -117,4 +118,39 @@ describe('Admin mentor routes', () => { .get(`/api/admin/mentors/emails?status=${ApplicationStatus.APPROVED}`) .expect(403) }) + + it.each([true, false])( + 'should update mentor availability and return a 201 with the updated availability', + async (availability) => { + const response = await adminAgent + .put(`/api/admin/mentors/${mentorId}/availability`) + .send({ availability }) + .expect(200) + + const mentor = response.body.mentor + + expect(mentor).toHaveProperty('availability', availability) + } + ) + + it.each([true, false])( + 'should only allow admins to update the mentor availability', + async (availability) => { + await mentorAgent + .put(`/api/admin/mentors/${mentorId}/availability`) + .send({ availability }) + .expect(403) + } + ) + + it.each([true, false])( + 'should return no mentor found', + async (availability) => { + const nonExistentMentorId = uuidv4() + await adminAgent + .put(`/api/admin/mentors/${nonExistentMentorId}/availability`) + .send({ availability }) + .expect(404) + } + ) }) diff --git a/src/routes/admin/mentor/mentor.route.ts b/src/routes/admin/mentor/mentor.route.ts index c8bdf20c..e7f53b07 100644 --- a/src/routes/admin/mentor/mentor.route.ts +++ b/src/routes/admin/mentor/mentor.route.ts @@ -3,7 +3,8 @@ import { requireAuth } from '../../../controllers/auth.controller' import { getAllMentorEmails, getAllMentorsByStatus, - mentorStatusHandler + mentorStatusHandler, + updateMentorAvailability } from '../../../controllers/admin/mentor.controller' const mentorRouter = express.Router() @@ -11,5 +12,10 @@ const mentorRouter = express.Router() mentorRouter.put('/:mentorId/status', requireAuth, mentorStatusHandler) mentorRouter.get('/', requireAuth, getAllMentorsByStatus) mentorRouter.get('/emails', requireAuth, getAllMentorEmails) +mentorRouter.put( + '/:mentorId/availability', + requireAuth, + updateMentorAvailability +) export default mentorRouter diff --git a/src/services/admin/mentor.service.ts b/src/services/admin/mentor.service.ts index b826d4a8..dc095d8c 100644 --- a/src/services/admin/mentor.service.ts +++ b/src/services/admin/mentor.service.ts @@ -109,3 +109,45 @@ export const findAllMentorEmails = async ( throw new Error('Error getting mentors emails') } } + +export const updateAvailability = async ( + mentorId: string, + availability: boolean +): Promise<{ statusCode: number; mentor?: Mentor | null; message: string }> => { + try { + const mentorRepository = dataSource.getRepository(Mentor) + + const existingMentor = await mentorRepository.findOne({ + where: { uuid: mentorId }, + select: [ + 'application', + 'state', + 'availability', + 'uuid', + 'category', + 'profile', + 'created_at' + ], + relations: ['category', 'profile'] + }) + + if (!existingMentor) { + return { + statusCode: 404, + message: 'Mentor not found' + } + } + + existingMentor.availability = availability + const mentor = await mentorRepository.save(existingMentor) + + return { + statusCode: 200, + mentor, + message: 'Mentor Availability updated' + } + } catch (err) { + console.error('Error creating mentor', err) + throw new Error('Error creating mentor') + } +} From 5f9c5a71094e1e6d54dc31611d9290d2d10b440e Mon Sep 17 00:00:00 2001 From: Krishnadeva Date: Thu, 12 Oct 2023 21:45:00 +0530 Subject: [PATCH 03/15] Fixed update mentor availability service --- src/controllers/admin/mentor.controller.ts | 10 ++--- src/controllers/mentor.controller.ts | 2 +- src/routes/admin/mentor/mentor.route.test.ts | 13 ++++-- src/services/admin/mentor.service.ts | 42 -------------------- src/services/mentor.service.ts | 18 ++++++--- 5 files changed, 28 insertions(+), 57 deletions(-) diff --git a/src/controllers/admin/mentor.controller.ts b/src/controllers/admin/mentor.controller.ts index a7f678eb..0d36086a 100644 --- a/src/controllers/admin/mentor.controller.ts +++ b/src/controllers/admin/mentor.controller.ts @@ -2,13 +2,13 @@ import type { Request, Response } from 'express' import { findAllMentorEmails, getAllMentors, - updateAvailability, updateMentorStatus } from '../../services/admin/mentor.service' import { ApplicationStatus, ProfileTypes } from '../../enums' import type Profile from '../../entities/profile.entity' import type Mentor from '../../entities/mentor.entity' import type { ApiResponse } from '../../types' +import { updateAvailability } from '../../services/mentor.service' export const mentorStatusHandler = async ( req: Request, @@ -120,11 +120,9 @@ export const updateMentorAvailability = async ( return res.status(403).json({ message: 'Only Admins are allowed' }) } - const { mentor, statusCode, message } = await updateAvailability( - mentorId, - availability - ) - return res.status(statusCode).json({ mentor, message }) + const { statusCode, updatedMentorApplication, message } = + await updateAvailability(mentorId, availability) + return res.status(statusCode).json({ updatedMentorApplication, message }) } catch (err) { if (err instanceof Error) { console.error('Error executing query', err) diff --git a/src/controllers/mentor.controller.ts b/src/controllers/mentor.controller.ts index 3a517791..1336d912 100644 --- a/src/controllers/mentor.controller.ts +++ b/src/controllers/mentor.controller.ts @@ -41,7 +41,7 @@ export const mentorAvailabilityHandler = async ( try { const user = req.user as Profile const { availability } = req.body - const result = await updateAvailability(user, availability) + const result = await updateAvailability(user.uuid, availability) return res.status(result.statusCode).json(result.updatedMentorApplication) } catch (err) { if (err instanceof Error) { diff --git a/src/routes/admin/mentor/mentor.route.test.ts b/src/routes/admin/mentor/mentor.route.test.ts index be934908..551038c1 100644 --- a/src/routes/admin/mentor/mentor.route.test.ts +++ b/src/routes/admin/mentor/mentor.route.test.ts @@ -14,6 +14,7 @@ let server: Express let mentorAgent: supertest.SuperAgentTest let adminAgent: supertest.SuperAgentTest let mentorId: string +let mentorProfileId: string describe('Admin mentor routes', () => { beforeAll(async () => { @@ -54,6 +55,7 @@ describe('Admin mentor routes', () => { .expect(201) mentorId = response.body.mentor.uuid + mentorProfileId = response.body.mentor.profile.uuid }, 5000) it('should update the mentor application state', async () => { @@ -123,11 +125,11 @@ describe('Admin mentor routes', () => { 'should update mentor availability and return a 201 with the updated availability', async (availability) => { const response = await adminAgent - .put(`/api/admin/mentors/${mentorId}/availability`) + .put(`/api/admin/mentors/${mentorProfileId}/availability`) .send({ availability }) .expect(200) - const mentor = response.body.mentor + const mentor = response.body.updatedMentorApplication expect(mentor).toHaveProperty('availability', availability) } @@ -136,8 +138,9 @@ describe('Admin mentor routes', () => { it.each([true, false])( 'should only allow admins to update the mentor availability', async (availability) => { + console.log(mentorProfileId) await mentorAgent - .put(`/api/admin/mentors/${mentorId}/availability`) + .put(`/api/admin/mentors/${mentorProfileId}/availability`) .send({ availability }) .expect(403) } @@ -153,4 +156,8 @@ describe('Admin mentor routes', () => { .expect(404) } ) + + afterAll(async () => { + await dataSource.destroy() + }) }) diff --git a/src/services/admin/mentor.service.ts b/src/services/admin/mentor.service.ts index dc095d8c..b826d4a8 100644 --- a/src/services/admin/mentor.service.ts +++ b/src/services/admin/mentor.service.ts @@ -109,45 +109,3 @@ export const findAllMentorEmails = async ( throw new Error('Error getting mentors emails') } } - -export const updateAvailability = async ( - mentorId: string, - availability: boolean -): Promise<{ statusCode: number; mentor?: Mentor | null; message: string }> => { - try { - const mentorRepository = dataSource.getRepository(Mentor) - - const existingMentor = await mentorRepository.findOne({ - where: { uuid: mentorId }, - select: [ - 'application', - 'state', - 'availability', - 'uuid', - 'category', - 'profile', - 'created_at' - ], - relations: ['category', 'profile'] - }) - - if (!existingMentor) { - return { - statusCode: 404, - message: 'Mentor not found' - } - } - - existingMentor.availability = availability - const mentor = await mentorRepository.save(existingMentor) - - return { - statusCode: 200, - mentor, - message: 'Mentor Availability updated' - } - } catch (err) { - console.error('Error creating mentor', err) - throw new Error('Error creating mentor') - } -} diff --git a/src/services/mentor.service.ts b/src/services/mentor.service.ts index 34612da9..98ca228f 100644 --- a/src/services/mentor.service.ts +++ b/src/services/mentor.service.ts @@ -74,13 +74,17 @@ export const createMentor = async ( } export const updateAvailability = async ( - user: Profile, + mentorId: string, availability: boolean -): Promise<{ statusCode: number; updatedMentorApplication: Mentor }> => { +): Promise<{ + statusCode: number + updatedMentorApplication?: Mentor + message: string +}> => { try { const mentorRepository = dataSource.getRepository(Mentor) const existingMentorApplications = await mentorRepository.find({ - where: { profile: { uuid: user.uuid } } + where: { profile: { uuid: mentorId } } }) const mentorApplication = existingMentorApplications[0] @@ -92,10 +96,14 @@ export const updateAvailability = async ( ) return { statusCode: 200, - updatedMentorApplication + updatedMentorApplication, + message: 'Mentor availability updated sucessfully' } } else { - throw new Error('Mentor application not found') + return { + statusCode: 404, + message: 'Mentor not found' + } } } catch (err) { console.error('Error creating mentor', err) From ccb20344e2b5afc0b14db854eb3f29517a4464bb Mon Sep 17 00:00:00 2001 From: Krishnadeva Date: Sat, 14 Oct 2023 13:59:49 +0530 Subject: [PATCH 04/15] Removed console log --- src/routes/admin/mentor/mentor.route.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/routes/admin/mentor/mentor.route.test.ts b/src/routes/admin/mentor/mentor.route.test.ts index 551038c1..748ff4c3 100644 --- a/src/routes/admin/mentor/mentor.route.test.ts +++ b/src/routes/admin/mentor/mentor.route.test.ts @@ -138,7 +138,6 @@ describe('Admin mentor routes', () => { it.each([true, false])( 'should only allow admins to update the mentor availability', async (availability) => { - console.log(mentorProfileId) await mentorAgent .put(`/api/admin/mentors/${mentorProfileId}/availability`) .send({ availability }) From bd249e8c834d72b9613697aee7ab61944bf7c1e7 Mon Sep 17 00:00:00 2001 From: Krishnadeva Date: Sat, 14 Oct 2023 20:07:46 +0530 Subject: [PATCH 05/15] Implement Add platform details endpoint (Admin) #37 --- mocks.ts | 34 ++++++++++++ src/controllers/admin/platform.controller.ts | 25 +++++++++ src/routes/admin/admin.route.ts | 2 + .../admin/platform/platform.route.test.ts | 53 +++++++++++++++++++ src/routes/admin/platform/platform.route.ts | 9 ++++ src/services/admin/platform.service.ts | 39 ++++++++++++++ 6 files changed, 162 insertions(+) create mode 100644 src/controllers/admin/platform.controller.ts create mode 100644 src/routes/admin/platform/platform.route.test.ts create mode 100644 src/routes/admin/platform/platform.route.ts create mode 100644 src/services/admin/platform.service.ts diff --git a/mocks.ts b/mocks.ts index 14c0b33a..f77707d9 100644 --- a/mocks.ts +++ b/mocks.ts @@ -32,3 +32,37 @@ export const mentorApplicationInfo = { ], categoryId: '60b5b847-99a2-4e47-b35b-81b4284311dd' } + +export const platformInfo = { + description: 'This is a sample description.', + mentor_questions: [ + { + question: 'What are your career goals?', + question_type: 'text', + options: [] + }, + { + question: 'How do you handle challenges?', + question_type: 'text', + options: [] + }, + { + question: 'Tell me about a time when you demonstrated leadership skills.', + question_type: 'text', + options: [] + } + ], + image_url: 'https://example.com/images/sample.jpg', + landing_page_url: 'https://example.com/landing-page', + email_templates: { + template1: { + subject: 'Welcome to our mentoring program!', + body: 'Dear {{mentor_name}},\n\nWe are excited to have you join our mentoring program...' + }, + template2: { + subject: 'Follow-up on your mentoring session', + body: 'Dear {{mentee_name}},\n\nI wanted to follow up on our recent mentoring session...' + } + }, + title: 'Sample Mentoring Program' +} diff --git a/src/controllers/admin/platform.controller.ts b/src/controllers/admin/platform.controller.ts new file mode 100644 index 00000000..e6431109 --- /dev/null +++ b/src/controllers/admin/platform.controller.ts @@ -0,0 +1,25 @@ +import type { Request, Response } from 'express' +import type { ApiResponse } from '../../types' +import type Platform from '../../entities/platform.entity' +import type Profile from '../../entities/profile.entity' +import { ProfileTypes } from '../../enums' +import { createPlatform } from '../../services/admin/platform.service' + +export const addPlatform = async ( + req: Request, + res: Response +): Promise> => { + try { + const user = req.user as Profile + + if (user.type !== ProfileTypes.ADMIN) { + return res.status(403).json({ message: 'Only Admins are allowed' }) + } + + const { platform, statusCode, message } = await createPlatform(req.body) + return res.status(statusCode).json({ platform, message }) + } catch (err) { + console.error('Error executing query', err) + return res.status(500).json({ error: err }) + } +} diff --git a/src/routes/admin/admin.route.ts b/src/routes/admin/admin.route.ts index 17e5d20b..91812339 100644 --- a/src/routes/admin/admin.route.ts +++ b/src/routes/admin/admin.route.ts @@ -2,11 +2,13 @@ import express from 'express' import userRouter from './user/user.route' import mentorRouter from './mentor/mentor.route' import categoryRouter from './category/category.route' +import platformRouter from './platform/platform.route' const adminRouter = express() adminRouter.use('/users', userRouter) adminRouter.use('/mentors', mentorRouter) adminRouter.use('/categories', categoryRouter) +adminRouter.use('/platform', platformRouter) export default adminRouter diff --git a/src/routes/admin/platform/platform.route.test.ts b/src/routes/admin/platform/platform.route.test.ts new file mode 100644 index 00000000..afb6985c --- /dev/null +++ b/src/routes/admin/platform/platform.route.test.ts @@ -0,0 +1,53 @@ +import { startServer } from '../../../app' +import type { Express } from 'express' +import supertest from 'supertest' +import bcrypt from 'bcrypt' +import { mockAdmin, mockUser, platformInfo } from '../../../../mocks' +import { ProfileTypes } from '../../../enums' +import { dataSource } from '../../../configs/dbConfig' +import Profile from '../../../entities/profile.entity' + +const port = Math.floor(Math.random() * (9999 - 3000 + 1)) + 3000 + +let server: Express +let agent: supertest.SuperAgentTest +let adminAgent: supertest.SuperAgentTest + +describe('Admin platform routes', () => { + beforeAll(async () => { + server = await startServer(port) + agent = supertest.agent(server) + adminAgent = supertest.agent(server) + + await supertest(server) + .post('/api/auth/register') + .send(mockUser) + .expect(201) + await agent.post('/api/auth/login').send(mockUser).expect(200) + + const profileRepository = dataSource.getRepository(Profile) + + const hashedPassword = await bcrypt.hash(mockAdmin.password, 10) + const newProfile = profileRepository.create({ + primary_email: mockAdmin.email, + password: hashedPassword, + contact_email: '', + first_name: '', + last_name: '', + image_url: '', + linkedin_url: '', + type: ProfileTypes.ADMIN + }) + + await profileRepository.save(newProfile) + + await adminAgent.post('/api/auth/login').send(mockAdmin).expect(200) + }, 5000) + + it('should add a platform', async () => { + await adminAgent.post('/api/admin/platform').send(platformInfo).expect(201) + }) + it('should only allow admins to add a platform', async () => { + await agent.post('/api/admin/categories').send(platformInfo).expect(403) + }) +}) diff --git a/src/routes/admin/platform/platform.route.ts b/src/routes/admin/platform/platform.route.ts new file mode 100644 index 00000000..a2b16237 --- /dev/null +++ b/src/routes/admin/platform/platform.route.ts @@ -0,0 +1,9 @@ +import express from 'express' +import { requireAuth } from '../../../controllers/auth.controller' +import { addPlatform } from '../../../controllers/admin/platform.controller' + +const platformRouter = express.Router() + +platformRouter.post('/', requireAuth, addPlatform) + +export default platformRouter diff --git a/src/services/admin/platform.service.ts b/src/services/admin/platform.service.ts new file mode 100644 index 00000000..dd42f291 --- /dev/null +++ b/src/services/admin/platform.service.ts @@ -0,0 +1,39 @@ +import Platform from '../../entities/platform.entity' +import { dataSource } from '../../configs/dbConfig' + +export const createPlatform = async ({ + description, + mentor_questions, + image_url, + landing_page_url, + email_templates, + title +}: Platform): Promise<{ + statusCode: number + platform?: Platform | null + message: string +}> => { + try { + const platformRepository = dataSource.getRepository(Platform) + + const newPlatform = new Platform( + description, + mentor_questions, + image_url, + landing_page_url, + email_templates, + title + ) + + const savedPlatform = await platformRepository.save(newPlatform) + + return { + statusCode: 201, + platform: savedPlatform, + message: 'Platform created successfully ' + } + } catch (err) { + console.error('Error creating Platform', err) + throw new Error('Error creating Platform') + } +} From b5b6cbc7c169e6f3fcf18de62855f15ce2f0814c Mon Sep 17 00:00:00 2001 From: Krishnadeva Date: Sat, 14 Oct 2023 21:50:07 +0530 Subject: [PATCH 06/15] Implement Get platform details endpoint (Admin) #35 --- src/controllers/admin/platform.controller.ts | 24 ++++++++++++++++++- .../admin/platform/platform.route.test.ts | 20 ++++++++++++++++ src/routes/admin/platform/platform.route.ts | 6 ++++- src/services/admin/platform.service.ts | 21 ++++++++++++++++ 4 files changed, 69 insertions(+), 2 deletions(-) diff --git a/src/controllers/admin/platform.controller.ts b/src/controllers/admin/platform.controller.ts index e6431109..197b64b4 100644 --- a/src/controllers/admin/platform.controller.ts +++ b/src/controllers/admin/platform.controller.ts @@ -3,7 +3,10 @@ import type { ApiResponse } from '../../types' import type Platform from '../../entities/platform.entity' import type Profile from '../../entities/profile.entity' import { ProfileTypes } from '../../enums' -import { createPlatform } from '../../services/admin/platform.service' +import { + createPlatform, + getPlatformDetails +} from '../../services/admin/platform.service' export const addPlatform = async ( req: Request, @@ -23,3 +26,22 @@ export const addPlatform = async ( return res.status(500).json({ error: err }) } } + +export const getPlatform = async ( + req: Request, + res: Response +): Promise> => { + try { + const user = req.user as Profile + + if (user.type !== ProfileTypes.ADMIN) { + return res.status(403).json({ message: 'Only Admins are allowed' }) + } + + const { platform, statusCode, message } = await getPlatformDetails() + return res.status(statusCode).json({ platform, message }) + } catch (err) { + console.error('Error executing query', err) + return res.status(500).json({ error: err }) + } +} diff --git a/src/routes/admin/platform/platform.route.test.ts b/src/routes/admin/platform/platform.route.test.ts index afb6985c..19fa7fe9 100644 --- a/src/routes/admin/platform/platform.route.test.ts +++ b/src/routes/admin/platform/platform.route.test.ts @@ -6,6 +6,7 @@ import { mockAdmin, mockUser, platformInfo } from '../../../../mocks' import { ProfileTypes } from '../../../enums' import { dataSource } from '../../../configs/dbConfig' import Profile from '../../../entities/profile.entity' +import type Platform from '../../../entities/platform.entity' const port = Math.floor(Math.random() * (9999 - 3000 + 1)) + 3000 @@ -50,4 +51,23 @@ describe('Admin platform routes', () => { it('should only allow admins to add a platform', async () => { await agent.post('/api/admin/categories').send(platformInfo).expect(403) }) + + it('should return a 200 with platform details if user is admin', async () => { + const response = await adminAgent.get('/api/admin/platform').expect(200) + + const platformDetails = response.body.platform + + platformDetails.forEach((platform: Platform) => { + expect(platform).toHaveProperty('description') + expect(platform).toHaveProperty('mentor_questions') + expect(platform).toHaveProperty('image_url') + expect(platform).toHaveProperty('landing_page_url') + expect(platform).toHaveProperty('email_templates') + expect(platform).toHaveProperty('title') + }) + }) + + it('should only allow admins to add a platform', async () => { + await agent.get('/api/admin/platform').expect(403) + }) }) diff --git a/src/routes/admin/platform/platform.route.ts b/src/routes/admin/platform/platform.route.ts index a2b16237..ecac31f4 100644 --- a/src/routes/admin/platform/platform.route.ts +++ b/src/routes/admin/platform/platform.route.ts @@ -1,9 +1,13 @@ import express from 'express' import { requireAuth } from '../../../controllers/auth.controller' -import { addPlatform } from '../../../controllers/admin/platform.controller' +import { + addPlatform, + getPlatform +} from '../../../controllers/admin/platform.controller' const platformRouter = express.Router() platformRouter.post('/', requireAuth, addPlatform) +platformRouter.get('/', requireAuth, getPlatform) export default platformRouter diff --git a/src/services/admin/platform.service.ts b/src/services/admin/platform.service.ts index dd42f291..62263849 100644 --- a/src/services/admin/platform.service.ts +++ b/src/services/admin/platform.service.ts @@ -37,3 +37,24 @@ export const createPlatform = async ({ throw new Error('Error creating Platform') } } + +export const getPlatformDetails = async (): Promise<{ + statusCode: number + platform?: Platform[] | null + message: string +}> => { + try { + const platformRepository = dataSource.getRepository(Platform) + + const platform = await platformRepository.find() + + return { + statusCode: 200, + platform, + message: 'Get Platform details successfully' + } + } catch (err) { + console.error('Error creating Platform', err) + throw new Error('Error creating Platform') + } +} From bce9cab48696c94cf76a00eff94e1188a2ada1a4 Mon Sep 17 00:00:00 2001 From: Krishnadeva Date: Sun, 15 Oct 2023 12:09:34 +0530 Subject: [PATCH 07/15] Implement Create email template endpoint (Admin) #40 --- mocks.ts | 9 +++ src/controllers/admin/email.controller.ts | 36 +++++++++++ src/routes/admin/admin.route.ts | 2 + src/routes/admin/email/email.route.test.ts | 60 +++++++++++++++++++ src/routes/admin/email/email.route.ts | 9 +++ .../admin/platform/platform.route.test.ts | 3 +- src/services/admin/email.service.ts | 31 ++++++++++ 7 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 src/controllers/admin/email.controller.ts create mode 100644 src/routes/admin/email/email.route.test.ts create mode 100644 src/routes/admin/email/email.route.ts create mode 100644 src/services/admin/email.service.ts diff --git a/mocks.ts b/mocks.ts index f77707d9..6957004a 100644 --- a/mocks.ts +++ b/mocks.ts @@ -1,3 +1,5 @@ +import { EmailStatusTypes } from './src/enums' + const randomString = Math.random().toString(36) export const mockMentor = { @@ -66,3 +68,10 @@ export const platformInfo = { }, title: 'Sample Mentoring Program' } + +export const emailTemplateInfo = { + recipient: '', + subject: 'Follow-up on your mentoring session', + content: 'Sample content', + state: EmailStatusTypes.SENT +} diff --git a/src/controllers/admin/email.controller.ts b/src/controllers/admin/email.controller.ts new file mode 100644 index 00000000..67bb1380 --- /dev/null +++ b/src/controllers/admin/email.controller.ts @@ -0,0 +1,36 @@ +import type { Request, Response } from 'express' +import type { ApiResponse } from '../../types' +import type Email from '../../entities/email.entity' +import type Profile from '../../entities/profile.entity' +import { ProfileTypes } from '../../enums' +import { createEmailTemplate } from '../../services/admin/email.service' + +export const addEmailTemplate = async ( + req: Request, + res: Response +): Promise> => { + try { + const user = req.user as Profile + const { recipient, subject, content, state } = req.body + + if (user.type !== ProfileTypes.ADMIN) { + return res.status(403).json({ message: 'Only Admins are allowed' }) + } + + if (!recipient || !subject || !content || !state) { + return res.status(400).json({ + error: 'Receipent, subject, content and state are required fields' + }) + } + const { emailTemplate, statusCode, message } = await createEmailTemplate( + recipient, + subject, + content, + state + ) + return res.status(statusCode).json({ emailTemplate, message }) + } catch (err) { + console.error('Error executing query', err) + return res.status(500).json({ error: err }) + } +} diff --git a/src/routes/admin/admin.route.ts b/src/routes/admin/admin.route.ts index 91812339..235cf5e7 100644 --- a/src/routes/admin/admin.route.ts +++ b/src/routes/admin/admin.route.ts @@ -3,6 +3,7 @@ import userRouter from './user/user.route' import mentorRouter from './mentor/mentor.route' import categoryRouter from './category/category.route' import platformRouter from './platform/platform.route' +import emailRouter from './email/email.route' const adminRouter = express() @@ -10,5 +11,6 @@ adminRouter.use('/users', userRouter) adminRouter.use('/mentors', mentorRouter) adminRouter.use('/categories', categoryRouter) adminRouter.use('/platform', platformRouter) +adminRouter.use('/emails', emailRouter) export default adminRouter diff --git a/src/routes/admin/email/email.route.test.ts b/src/routes/admin/email/email.route.test.ts new file mode 100644 index 00000000..630c13e4 --- /dev/null +++ b/src/routes/admin/email/email.route.test.ts @@ -0,0 +1,60 @@ +import { startServer } from '../../../app' +import type { Express } from 'express' +import supertest from 'supertest' +import bcrypt from 'bcrypt' +import { emailTemplateInfo, mockAdmin, mockUser } from '../../../../mocks' +import { dataSource } from '../../../configs/dbConfig' +import Profile from '../../../entities/profile.entity' +import { ProfileTypes } from '../../../enums' + +const port = Math.floor(Math.random() * (9999 - 3000 + 1)) + 3000 + +let server: Express +let agent: supertest.SuperAgentTest +let adminAgent: supertest.SuperAgentTest + +describe('Admin email template routes', () => { + beforeAll(async () => { + server = await startServer(port) + agent = supertest.agent(server) + adminAgent = supertest.agent(server) + + await supertest(server) + .post('/api/auth/register') + .send(mockUser) + .expect(201) + await agent.post('/api/auth/login').send(mockUser).expect(200) + + const profileRepository = dataSource.getRepository(Profile) + + const hashedPassword = await bcrypt.hash(mockAdmin.password, 10) + const newProfile = profileRepository.create({ + primary_email: mockAdmin.email, + password: hashedPassword, + contact_email: '', + first_name: '', + last_name: '', + image_url: '', + linkedin_url: '', + type: ProfileTypes.ADMIN + }) + + await profileRepository.save(newProfile) + + await adminAgent.post('/api/auth/login').send(mockAdmin).expect(200) + }, 5000) + + it('should add a email template', async () => { + await adminAgent + .post('/api/admin/emails/templates') + .send({ ...emailTemplateInfo, recipient: mockUser.email }) + .expect(201) + }) + + it('should only allow admins to add a email template', async () => { + await agent + .post('/api/admin/emails/templates') + .send({ ...emailTemplateInfo, recipient: mockUser.email }) + .expect(403) + }) +}) diff --git a/src/routes/admin/email/email.route.ts b/src/routes/admin/email/email.route.ts new file mode 100644 index 00000000..b5f1fcd6 --- /dev/null +++ b/src/routes/admin/email/email.route.ts @@ -0,0 +1,9 @@ +import express from 'express' +import { requireAuth } from '../../../controllers/auth.controller' +import { addEmailTemplate } from '../../../controllers/admin/email.controller' + +const emailRouter = express.Router() + +emailRouter.post('/templates', requireAuth, addEmailTemplate) + +export default emailRouter diff --git a/src/routes/admin/platform/platform.route.test.ts b/src/routes/admin/platform/platform.route.test.ts index 19fa7fe9..3bf102c9 100644 --- a/src/routes/admin/platform/platform.route.test.ts +++ b/src/routes/admin/platform/platform.route.test.ts @@ -48,6 +48,7 @@ describe('Admin platform routes', () => { it('should add a platform', async () => { await adminAgent.post('/api/admin/platform').send(platformInfo).expect(201) }) + it('should only allow admins to add a platform', async () => { await agent.post('/api/admin/categories').send(platformInfo).expect(403) }) @@ -67,7 +68,7 @@ describe('Admin platform routes', () => { }) }) - it('should only allow admins to add a platform', async () => { + it('should only allow admins to get a platform', async () => { await agent.get('/api/admin/platform').expect(403) }) }) diff --git a/src/services/admin/email.service.ts b/src/services/admin/email.service.ts new file mode 100644 index 00000000..1bc54d0f --- /dev/null +++ b/src/services/admin/email.service.ts @@ -0,0 +1,31 @@ +import { dataSource } from '../../configs/dbConfig' +import Email from '../../entities/email.entity' +import type { EmailStatusTypes } from '../../enums' + +export const createEmailTemplate = async ( + recipient: string, + subject: string, + content: string, + state: EmailStatusTypes +): Promise<{ + statusCode: number + emailTemplate?: Email | null + message: string +}> => { + try { + const emailRepositroy = dataSource.getRepository(Email) + + const newEmailTemplate = new Email(recipient, subject, content, state) + + const savedEmailTemplate = await emailRepositroy.save(newEmailTemplate) + + return { + statusCode: 201, + emailTemplate: savedEmailTemplate, + message: 'Email Template created successfully' + } + } catch (err) { + console.error('Error creating category', err) + throw new Error('Error creating category') + } +} From 368badfb95da9cff3762e1c1a48153b21b55826e Mon Sep 17 00:00:00 2001 From: Krishnadeva Date: Sun, 15 Oct 2023 12:55:48 +0530 Subject: [PATCH 08/15] Implement Get email template endpoint (Admin) #38 --- src/controllers/admin/email.controller.ts | 27 +++++++++++++++- src/routes/admin/email/email.route.test.ts | 16 +++++++++- src/routes/admin/email/email.route.ts | 6 +++- src/services/admin/email.service.ts | 37 ++++++++++++++++++++-- 4 files changed, 81 insertions(+), 5 deletions(-) diff --git a/src/controllers/admin/email.controller.ts b/src/controllers/admin/email.controller.ts index 67bb1380..51e6d0e9 100644 --- a/src/controllers/admin/email.controller.ts +++ b/src/controllers/admin/email.controller.ts @@ -3,7 +3,10 @@ import type { ApiResponse } from '../../types' import type Email from '../../entities/email.entity' import type Profile from '../../entities/profile.entity' import { ProfileTypes } from '../../enums' -import { createEmailTemplate } from '../../services/admin/email.service' +import { + createEmailTemplate, + getEmailTemplateById +} from '../../services/admin/email.service' export const addEmailTemplate = async ( req: Request, @@ -34,3 +37,25 @@ export const addEmailTemplate = async ( return res.status(500).json({ error: err }) } } + +export const getEmailTemplate = async ( + req: Request, + res: Response +): Promise> => { + try { + const user = req.user as Profile + const templateId = req.params.mentorId + + if (user.type !== ProfileTypes.ADMIN) { + return res.status(403).json({ message: 'Only Admins are allowed' }) + } + + const { emailTemplate, statusCode, message } = await getEmailTemplateById( + templateId + ) + return res.status(statusCode).json({ emailTemplate, message }) + } catch (err) { + console.error('Error executing query', err) + return res.status(500).json({ error: err }) + } +} diff --git a/src/routes/admin/email/email.route.test.ts b/src/routes/admin/email/email.route.test.ts index 630c13e4..4aed5a6d 100644 --- a/src/routes/admin/email/email.route.test.ts +++ b/src/routes/admin/email/email.route.test.ts @@ -6,12 +6,14 @@ import { emailTemplateInfo, mockAdmin, mockUser } from '../../../../mocks' import { dataSource } from '../../../configs/dbConfig' import Profile from '../../../entities/profile.entity' import { ProfileTypes } from '../../../enums' +import type Email from '../../../entities/email.entity' const port = Math.floor(Math.random() * (9999 - 3000 + 1)) + 3000 let server: Express let agent: supertest.SuperAgentTest let adminAgent: supertest.SuperAgentTest +let savedEmailTemplate: Email describe('Admin email template routes', () => { beforeAll(async () => { @@ -45,10 +47,12 @@ describe('Admin email template routes', () => { }, 5000) it('should add a email template', async () => { - await adminAgent + const response = await adminAgent .post('/api/admin/emails/templates') .send({ ...emailTemplateInfo, recipient: mockUser.email }) .expect(201) + + savedEmailTemplate = response.body.emailTemplate }) it('should only allow admins to add a email template', async () => { @@ -57,4 +61,14 @@ describe('Admin email template routes', () => { .send({ ...emailTemplateInfo, recipient: mockUser.email }) .expect(403) }) + + it('should get a email template by passing template id', async () => { + const response = await adminAgent + .get(`/api/admin/emails/templates/${savedEmailTemplate.uuid}`) + .expect(200) + + const emailTemplate = response.body.emailTemplate + + expect(emailTemplate).toHaveProperty('content') + }) }) diff --git a/src/routes/admin/email/email.route.ts b/src/routes/admin/email/email.route.ts index b5f1fcd6..85627d9b 100644 --- a/src/routes/admin/email/email.route.ts +++ b/src/routes/admin/email/email.route.ts @@ -1,9 +1,13 @@ import express from 'express' import { requireAuth } from '../../../controllers/auth.controller' -import { addEmailTemplate } from '../../../controllers/admin/email.controller' +import { + addEmailTemplate, + getEmailTemplate +} from '../../../controllers/admin/email.controller' const emailRouter = express.Router() emailRouter.post('/templates', requireAuth, addEmailTemplate) +emailRouter.get('/templates/:templateId', requireAuth, getEmailTemplate) export default emailRouter diff --git a/src/services/admin/email.service.ts b/src/services/admin/email.service.ts index 1bc54d0f..b804ba6e 100644 --- a/src/services/admin/email.service.ts +++ b/src/services/admin/email.service.ts @@ -25,7 +25,40 @@ export const createEmailTemplate = async ( message: 'Email Template created successfully' } } catch (err) { - console.error('Error creating category', err) - throw new Error('Error creating category') + console.error('Error creating Email Template', err) + throw new Error('Error creating Email Template') + } +} + +export const getEmailTemplateById = async ( + templateId: string +): Promise<{ + statusCode: number + emailTemplate?: Email | null + message: string +}> => { + try { + const emailRepositroy = dataSource.getRepository(Email) + + const emailTemplate = await emailRepositroy.findOne({ + where: { uuid: templateId }, + select: ['uuid', 'content', 'subject'] + }) + + if (!emailTemplate) { + return { + statusCode: 404, + message: 'Email template not found' + } + } + + return { + statusCode: 200, + emailTemplate, + message: 'Email template found' + } + } catch (err) { + console.error('Error getting email template', err) + throw new Error('Error getting email template') } } From 13c1538356078e3a4bd7e3dc8f257f19656c823a Mon Sep 17 00:00:00 2001 From: Krishnadeva Date: Tue, 17 Oct 2023 20:05:28 +0530 Subject: [PATCH 09/15] Refactored the code --- mocks.ts | 6 +----- src/controllers/admin/email.controller.ts | 8 +++----- src/services/admin/email.service.ts | 13 ++++++++----- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/mocks.ts b/mocks.ts index 6957004a..478ef0bf 100644 --- a/mocks.ts +++ b/mocks.ts @@ -1,5 +1,3 @@ -import { EmailStatusTypes } from './src/enums' - const randomString = Math.random().toString(36) export const mockMentor = { @@ -70,8 +68,6 @@ export const platformInfo = { } export const emailTemplateInfo = { - recipient: '', subject: 'Follow-up on your mentoring session', - content: 'Sample content', - state: EmailStatusTypes.SENT + content: 'Sample content' } diff --git a/src/controllers/admin/email.controller.ts b/src/controllers/admin/email.controller.ts index 51e6d0e9..a2949b0e 100644 --- a/src/controllers/admin/email.controller.ts +++ b/src/controllers/admin/email.controller.ts @@ -14,22 +14,20 @@ export const addEmailTemplate = async ( ): Promise> => { try { const user = req.user as Profile - const { recipient, subject, content, state } = req.body + const { subject, content } = req.body if (user.type !== ProfileTypes.ADMIN) { return res.status(403).json({ message: 'Only Admins are allowed' }) } - if (!recipient || !subject || !content || !state) { + if (!subject || !content) { return res.status(400).json({ error: 'Receipent, subject, content and state are required fields' }) } const { emailTemplate, statusCode, message } = await createEmailTemplate( - recipient, subject, - content, - state + content ) return res.status(statusCode).json({ emailTemplate, message }) } catch (err) { diff --git a/src/services/admin/email.service.ts b/src/services/admin/email.service.ts index b804ba6e..c193034f 100644 --- a/src/services/admin/email.service.ts +++ b/src/services/admin/email.service.ts @@ -1,12 +1,10 @@ import { dataSource } from '../../configs/dbConfig' import Email from '../../entities/email.entity' -import type { EmailStatusTypes } from '../../enums' +import { EmailStatusTypes } from '../../enums' export const createEmailTemplate = async ( - recipient: string, subject: string, - content: string, - state: EmailStatusTypes + content: string ): Promise<{ statusCode: number emailTemplate?: Email | null @@ -15,7 +13,12 @@ export const createEmailTemplate = async ( try { const emailRepositroy = dataSource.getRepository(Email) - const newEmailTemplate = new Email(recipient, subject, content, state) + const newEmailTemplate = new Email( + '', + subject, + content, + EmailStatusTypes.SENT + ) const savedEmailTemplate = await emailRepositroy.save(newEmailTemplate) From 5b006cb1acb04bd1d9354c3a5ad78ab7e723e7b7 Mon Sep 17 00:00:00 2001 From: Krishnadeva Date: Tue, 17 Oct 2023 22:08:24 +0530 Subject: [PATCH 10/15] Implement Search mentor endpoint (Admin) #25 --- src/controllers/admin/mentor.controller.ts | 29 +++++++++++++++- src/routes/admin/mentor/mentor.route.test.ts | 24 +++++++++++++ src/routes/admin/mentor/mentor.route.ts | 2 ++ src/services/mentor.service.ts | 36 ++++++++++++++++++++ 4 files changed, 90 insertions(+), 1 deletion(-) diff --git a/src/controllers/admin/mentor.controller.ts b/src/controllers/admin/mentor.controller.ts index 0d36086a..0c5e8674 100644 --- a/src/controllers/admin/mentor.controller.ts +++ b/src/controllers/admin/mentor.controller.ts @@ -8,7 +8,7 @@ import { ApplicationStatus, ProfileTypes } from '../../enums' import type Profile from '../../entities/profile.entity' import type Mentor from '../../entities/mentor.entity' import type { ApiResponse } from '../../types' -import { updateAvailability } from '../../services/mentor.service' +import { searchMentorsByQuery, updateAvailability } from '../../services/mentor.service' export const mentorStatusHandler = async ( req: Request, @@ -134,3 +134,30 @@ export const updateMentorAvailability = async ( throw err } } + +export const searchMentors = async ( + req: Request, + res: Response +): Promise> => { + try { + const user = req.user as Profile + const q: string | undefined = req.query.q as string | undefined; + + if (user.type !== ProfileTypes.ADMIN) { + return res.status(403).json({ message: 'Only Admins are allowed' }) + } + + const { statusCode, mentors, message } = + await searchMentorsByQuery(q) + return res.status(statusCode).json({ mentors, message }) + } catch (err) { + if (err instanceof Error) { + console.error('Error executing query', err) + return res + .status(500) + .json({ error: 'Internal server error', message: err.message }) + } + + throw err + } +} diff --git a/src/routes/admin/mentor/mentor.route.test.ts b/src/routes/admin/mentor/mentor.route.test.ts index 748ff4c3..d83e5e74 100644 --- a/src/routes/admin/mentor/mentor.route.test.ts +++ b/src/routes/admin/mentor/mentor.route.test.ts @@ -156,6 +156,30 @@ describe('Admin mentor routes', () => { } ) + it('should return mentors first name starts with john and a success message when mentors are found', async () => { + const updatedProfile = { + contact_email: 'test_contact@example.com', + first_name: 'John', + last_name: 'Doe', + image_url: 'https://example.com/test_profile_image.jpg', + linkedin_url: 'https://www.linkedin.com/in/johndoe' + } + + await mentorAgent.put('/api/me/profile').send(updatedProfile).expect(200) + + const response = await adminAgent + .get(`/api/admin/mentors/search?q=john`) + .expect(200) + + console.log(response.body) + + expect(response.body).toHaveProperty('mentors') + }) + + it('should only allow admins to search mentors', async () => { + await mentorAgent.get(`/api/admin/mentors/search?q=john`).expect(403) + }) + afterAll(async () => { await dataSource.destroy() }) diff --git a/src/routes/admin/mentor/mentor.route.ts b/src/routes/admin/mentor/mentor.route.ts index e7f53b07..6db479b8 100644 --- a/src/routes/admin/mentor/mentor.route.ts +++ b/src/routes/admin/mentor/mentor.route.ts @@ -4,6 +4,7 @@ import { getAllMentorEmails, getAllMentorsByStatus, mentorStatusHandler, + searchMentors, updateMentorAvailability } from '../../../controllers/admin/mentor.controller' @@ -17,5 +18,6 @@ mentorRouter.put( requireAuth, updateMentorAvailability ) +mentorRouter.get('/search', requireAuth, searchMentors) export default mentorRouter diff --git a/src/services/mentor.service.ts b/src/services/mentor.service.ts index 98ca228f..fbfa8efb 100644 --- a/src/services/mentor.service.ts +++ b/src/services/mentor.service.ts @@ -142,3 +142,39 @@ export const getMentor = async ( throw new Error('Error getting mentor') } } + +export const searchMentorsByQuery = async ( + query?: string +): Promise<{ + statusCode: number + mentors?: Mentor[] | null + message: string +}> => { + try { + const mentorRepository = dataSource.getRepository(Mentor) + + const mentors: Mentor[] | null = await mentorRepository + .createQueryBuilder('mentor') + .innerJoinAndSelect('mentor.profile', 'profile') + .where('profile.first_name ILIKE :name OR profile.last_name ILIKE :name', { + name: `${query}%` + }) + .getMany() + + if (!mentors) { + return { + statusCode: 404, + message: 'Mentors not found' + } + } + + return { + statusCode: 200, + mentors: mentors, + message: 'All search Mentors found' + } + } catch (err) { + console.error('Error getting mentor', err) + throw new Error('Error getting mentor') + } +} From a2359450ac1c12aa282cbe5099891b61f3ccc209 Mon Sep 17 00:00:00 2001 From: Krishnadeva Date: Tue, 17 Oct 2023 22:21:06 +0530 Subject: [PATCH 11/15] Refactored code --- src/controllers/admin/mentor.controller.ts | 12 +++++++----- src/routes/admin/mentor/mentor.route.test.ts | 2 -- src/services/mentor.service.ts | 16 ++++++++++------ 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/controllers/admin/mentor.controller.ts b/src/controllers/admin/mentor.controller.ts index 0c5e8674..ca94bad3 100644 --- a/src/controllers/admin/mentor.controller.ts +++ b/src/controllers/admin/mentor.controller.ts @@ -8,7 +8,10 @@ import { ApplicationStatus, ProfileTypes } from '../../enums' import type Profile from '../../entities/profile.entity' import type Mentor from '../../entities/mentor.entity' import type { ApiResponse } from '../../types' -import { searchMentorsByQuery, updateAvailability } from '../../services/mentor.service' +import { + searchMentorsByQuery, + updateAvailability +} from '../../services/mentor.service' export const mentorStatusHandler = async ( req: Request, @@ -141,15 +144,14 @@ export const searchMentors = async ( ): Promise> => { try { const user = req.user as Profile - const q: string | undefined = req.query.q as string | undefined; + const q: string | undefined = req.query.q as string | undefined if (user.type !== ProfileTypes.ADMIN) { return res.status(403).json({ message: 'Only Admins are allowed' }) } - const { statusCode, mentors, message } = - await searchMentorsByQuery(q) - return res.status(statusCode).json({ mentors, message }) + const { statusCode, mentors, message } = await searchMentorsByQuery(q) + return res.status(statusCode).json({ mentors, message }) } catch (err) { if (err instanceof Error) { console.error('Error executing query', err) diff --git a/src/routes/admin/mentor/mentor.route.test.ts b/src/routes/admin/mentor/mentor.route.test.ts index d83e5e74..6afe6c44 100644 --- a/src/routes/admin/mentor/mentor.route.test.ts +++ b/src/routes/admin/mentor/mentor.route.test.ts @@ -171,8 +171,6 @@ describe('Admin mentor routes', () => { .get(`/api/admin/mentors/search?q=john`) .expect(200) - console.log(response.body) - expect(response.body).toHaveProperty('mentors') }) diff --git a/src/services/mentor.service.ts b/src/services/mentor.service.ts index fbfa8efb..0d0c8b0f 100644 --- a/src/services/mentor.service.ts +++ b/src/services/mentor.service.ts @@ -144,7 +144,7 @@ export const getMentor = async ( } export const searchMentorsByQuery = async ( - query?: string + q?: string ): Promise<{ statusCode: number mentors?: Mentor[] | null @@ -152,13 +152,17 @@ export const searchMentorsByQuery = async ( }> => { try { const mentorRepository = dataSource.getRepository(Mentor) + const query = q ? `${q}%` : '' - const mentors: Mentor[] | null = await mentorRepository + const mentors: Mentor[] = await mentorRepository .createQueryBuilder('mentor') .innerJoinAndSelect('mentor.profile', 'profile') - .where('profile.first_name ILIKE :name OR profile.last_name ILIKE :name', { - name: `${query}%` - }) + .where( + 'profile.first_name ILIKE :name OR profile.last_name ILIKE :name', + { + name: query + } + ) .getMany() if (!mentors) { @@ -170,7 +174,7 @@ export const searchMentorsByQuery = async ( return { statusCode: 200, - mentors: mentors, + mentors, message: 'All search Mentors found' } } catch (err) { From 5c8bd81f7dfd8f9e5bbc8de7d033dfaed216d3e4 Mon Sep 17 00:00:00 2001 From: Krishnadeva Date: Mon, 23 Oct 2023 20:41:24 +0530 Subject: [PATCH 12/15] Created Email Template table --- ...troller.ts => emailTemplate.controller.ts} | 10 ++++----- src/entities/emailTemplate.entity.ts | 19 ++++++++++++++++ src/routes/admin/admin.route.ts | 4 ++-- src/routes/admin/email/email.route.ts | 13 ----------- ...te.test.ts => emailTemplate.route.test.ts} | 14 ++++++------ src/routes/admin/email/emailTemplate.route.ts | 13 +++++++++++ ...il.service.ts => emailTemplate.service.ts} | 22 ++++++++----------- 7 files changed, 55 insertions(+), 40 deletions(-) rename src/controllers/admin/{email.controller.ts => emailTemplate.controller.ts} (84%) create mode 100644 src/entities/emailTemplate.entity.ts delete mode 100644 src/routes/admin/email/email.route.ts rename src/routes/admin/email/{email.route.test.ts => emailTemplate.route.test.ts} (84%) create mode 100644 src/routes/admin/email/emailTemplate.route.ts rename src/services/admin/{email.service.ts => emailTemplate.service.ts} (71%) diff --git a/src/controllers/admin/email.controller.ts b/src/controllers/admin/emailTemplate.controller.ts similarity index 84% rename from src/controllers/admin/email.controller.ts rename to src/controllers/admin/emailTemplate.controller.ts index a2949b0e..816e30f8 100644 --- a/src/controllers/admin/email.controller.ts +++ b/src/controllers/admin/emailTemplate.controller.ts @@ -1,17 +1,17 @@ import type { Request, Response } from 'express' import type { ApiResponse } from '../../types' -import type Email from '../../entities/email.entity' import type Profile from '../../entities/profile.entity' import { ProfileTypes } from '../../enums' import { createEmailTemplate, getEmailTemplateById -} from '../../services/admin/email.service' +} from '../../services/admin/emailTemplate.service' +import type EmailTemplate from '../../entities/emailTemplate.entity' export const addEmailTemplate = async ( req: Request, res: Response -): Promise> => { +): Promise> => { try { const user = req.user as Profile const { subject, content } = req.body @@ -22,7 +22,7 @@ export const addEmailTemplate = async ( if (!subject || !content) { return res.status(400).json({ - error: 'Receipent, subject, content and state are required fields' + error: 'Subject, content and state are required fields' }) } const { emailTemplate, statusCode, message } = await createEmailTemplate( @@ -39,7 +39,7 @@ export const addEmailTemplate = async ( export const getEmailTemplate = async ( req: Request, res: Response -): Promise> => { +): Promise> => { try { const user = req.user as Profile const templateId = req.params.mentorId diff --git a/src/entities/emailTemplate.entity.ts b/src/entities/emailTemplate.entity.ts new file mode 100644 index 00000000..080344f2 --- /dev/null +++ b/src/entities/emailTemplate.entity.ts @@ -0,0 +1,19 @@ +import { Column, Entity } from 'typeorm' +import BaseEntity from './baseEntity' + +@Entity('emailTemplate') +class EmailTemplate extends BaseEntity { + @Column({ type: 'varchar', length: 255 }) + subject: string + + @Column({ type: 'varchar', length: 655 }) + content: string + + constructor(subject: string, content: string) { + super() + this.subject = subject + this.content = content + } +} + +export default EmailTemplate diff --git a/src/routes/admin/admin.route.ts b/src/routes/admin/admin.route.ts index 235cf5e7..fb1aed27 100644 --- a/src/routes/admin/admin.route.ts +++ b/src/routes/admin/admin.route.ts @@ -3,7 +3,7 @@ import userRouter from './user/user.route' import mentorRouter from './mentor/mentor.route' import categoryRouter from './category/category.route' import platformRouter from './platform/platform.route' -import emailRouter from './email/email.route' +import emailTemplateRouter from './email/emailTemplate.route' const adminRouter = express() @@ -11,6 +11,6 @@ adminRouter.use('/users', userRouter) adminRouter.use('/mentors', mentorRouter) adminRouter.use('/categories', categoryRouter) adminRouter.use('/platform', platformRouter) -adminRouter.use('/emails', emailRouter) +adminRouter.use('/emailTemplate', emailTemplateRouter) export default adminRouter diff --git a/src/routes/admin/email/email.route.ts b/src/routes/admin/email/email.route.ts deleted file mode 100644 index 85627d9b..00000000 --- a/src/routes/admin/email/email.route.ts +++ /dev/null @@ -1,13 +0,0 @@ -import express from 'express' -import { requireAuth } from '../../../controllers/auth.controller' -import { - addEmailTemplate, - getEmailTemplate -} from '../../../controllers/admin/email.controller' - -const emailRouter = express.Router() - -emailRouter.post('/templates', requireAuth, addEmailTemplate) -emailRouter.get('/templates/:templateId', requireAuth, getEmailTemplate) - -export default emailRouter diff --git a/src/routes/admin/email/email.route.test.ts b/src/routes/admin/email/emailTemplate.route.test.ts similarity index 84% rename from src/routes/admin/email/email.route.test.ts rename to src/routes/admin/email/emailTemplate.route.test.ts index 4aed5a6d..ee67cbf9 100644 --- a/src/routes/admin/email/email.route.test.ts +++ b/src/routes/admin/email/emailTemplate.route.test.ts @@ -6,14 +6,14 @@ import { emailTemplateInfo, mockAdmin, mockUser } from '../../../../mocks' import { dataSource } from '../../../configs/dbConfig' import Profile from '../../../entities/profile.entity' import { ProfileTypes } from '../../../enums' -import type Email from '../../../entities/email.entity' +import type EmailTemplate from '../../../entities/emailTemplate.entity' const port = Math.floor(Math.random() * (9999 - 3000 + 1)) + 3000 let server: Express let agent: supertest.SuperAgentTest let adminAgent: supertest.SuperAgentTest -let savedEmailTemplate: Email +let savedEmailTemplate: EmailTemplate describe('Admin email template routes', () => { beforeAll(async () => { @@ -48,8 +48,8 @@ describe('Admin email template routes', () => { it('should add a email template', async () => { const response = await adminAgent - .post('/api/admin/emails/templates') - .send({ ...emailTemplateInfo, recipient: mockUser.email }) + .post('/api/admin/emailTemplate') + .send({ ...emailTemplateInfo }) .expect(201) savedEmailTemplate = response.body.emailTemplate @@ -57,14 +57,14 @@ describe('Admin email template routes', () => { it('should only allow admins to add a email template', async () => { await agent - .post('/api/admin/emails/templates') - .send({ ...emailTemplateInfo, recipient: mockUser.email }) + .post('/api/admin/emailTemplate') + .send({ ...emailTemplateInfo }) .expect(403) }) it('should get a email template by passing template id', async () => { const response = await adminAgent - .get(`/api/admin/emails/templates/${savedEmailTemplate.uuid}`) + .get(`/api/admin/emailTemplate/${savedEmailTemplate.uuid}`) .expect(200) const emailTemplate = response.body.emailTemplate diff --git a/src/routes/admin/email/emailTemplate.route.ts b/src/routes/admin/email/emailTemplate.route.ts new file mode 100644 index 00000000..6e8e4d64 --- /dev/null +++ b/src/routes/admin/email/emailTemplate.route.ts @@ -0,0 +1,13 @@ +import express from 'express' +import { requireAuth } from '../../../controllers/auth.controller' +import { + addEmailTemplate, + getEmailTemplate +} from '../../../controllers/admin/emailTemplate.controller' + +const emailTemplateRouter = express.Router() + +emailTemplateRouter.post('/', requireAuth, addEmailTemplate) +emailTemplateRouter.get('/:templateId', requireAuth, getEmailTemplate) + +export default emailTemplateRouter diff --git a/src/services/admin/email.service.ts b/src/services/admin/emailTemplate.service.ts similarity index 71% rename from src/services/admin/email.service.ts rename to src/services/admin/emailTemplate.service.ts index c193034f..08a16ddb 100644 --- a/src/services/admin/email.service.ts +++ b/src/services/admin/emailTemplate.service.ts @@ -1,26 +1,22 @@ import { dataSource } from '../../configs/dbConfig' -import Email from '../../entities/email.entity' -import { EmailStatusTypes } from '../../enums' +import EmailTemplate from '../../entities/emailTemplate.entity' export const createEmailTemplate = async ( subject: string, content: string ): Promise<{ statusCode: number - emailTemplate?: Email | null + emailTemplate?: EmailTemplate | null message: string }> => { try { - const emailRepositroy = dataSource.getRepository(Email) + const emailTemplateRepositroy = dataSource.getRepository(EmailTemplate) - const newEmailTemplate = new Email( - '', - subject, - content, - EmailStatusTypes.SENT - ) + const newEmailTemplate = new EmailTemplate(subject, content) - const savedEmailTemplate = await emailRepositroy.save(newEmailTemplate) + const savedEmailTemplate = await emailTemplateRepositroy.save( + newEmailTemplate + ) return { statusCode: 201, @@ -37,11 +33,11 @@ export const getEmailTemplateById = async ( templateId: string ): Promise<{ statusCode: number - emailTemplate?: Email | null + emailTemplate?: EmailTemplate | null message: string }> => { try { - const emailRepositroy = dataSource.getRepository(Email) + const emailRepositroy = dataSource.getRepository(EmailTemplate) const emailTemplate = await emailRepositroy.findOne({ where: { uuid: templateId }, From 71d01c6495b27b6e37c6bbb5ded774ee2caa594e Mon Sep 17 00:00:00 2001 From: Krishnadeva Date: Tue, 24 Oct 2023 15:59:55 +0530 Subject: [PATCH 13/15] Implement Get mentors by category endpoint (Admin) #26 --- src/controllers/admin/mentor.controller.ts | 31 +++++++++++++ src/routes/admin/mentor/mentor.route.test.ts | 24 ++++++++++ src/routes/admin/mentor/mentor.route.ts | 2 + src/services/admin/mentor.service.ts | 49 ++++++++++++++++++++ 4 files changed, 106 insertions(+) diff --git a/src/controllers/admin/mentor.controller.ts b/src/controllers/admin/mentor.controller.ts index ca94bad3..8f5fbd5b 100644 --- a/src/controllers/admin/mentor.controller.ts +++ b/src/controllers/admin/mentor.controller.ts @@ -2,6 +2,7 @@ import type { Request, Response } from 'express' import { findAllMentorEmails, getAllMentors, + searchMentorsByCategory, updateMentorStatus } from '../../services/admin/mentor.service' import { ApplicationStatus, ProfileTypes } from '../../enums' @@ -163,3 +164,33 @@ export const searchMentors = async ( throw err } } + +export const getAllMentorsByCategory = async ( + req: Request, + res: Response +): Promise> => { + try { + const user = req.user as Profile + const category: string | undefined = req.query.category as + | string + | undefined + + if (user.type !== ProfileTypes.ADMIN) { + return res.status(403).json({ message: 'Only Admins are allowed' }) + } + + const { statusCode, mentors, message } = await searchMentorsByCategory( + category + ) + return res.status(statusCode).json({ mentors, message }) + } catch (err) { + if (err instanceof Error) { + console.error('Error executing query', err) + return res + .status(500) + .json({ error: 'Internal server error', message: err.message }) + } + + throw err + } +} diff --git a/src/routes/admin/mentor/mentor.route.test.ts b/src/routes/admin/mentor/mentor.route.test.ts index 6afe6c44..8d06406c 100644 --- a/src/routes/admin/mentor/mentor.route.test.ts +++ b/src/routes/admin/mentor/mentor.route.test.ts @@ -7,6 +7,8 @@ import { dataSource } from '../../../configs/dbConfig' import bcrypt from 'bcrypt' import { v4 as uuidv4 } from 'uuid' import { mentorApplicationInfo, mockAdmin, mockMentor } from '../../../../mocks' +import type Category from '../../../entities/category.entity' +import type Mentor from '../../../entities/mentor.entity' const port = Math.floor(Math.random() * (9999 - 3000 + 1)) + 3000 @@ -15,6 +17,7 @@ let mentorAgent: supertest.SuperAgentTest let adminAgent: supertest.SuperAgentTest let mentorId: string let mentorProfileId: string +let category: Category describe('Admin mentor routes', () => { beforeAll(async () => { @@ -56,6 +59,7 @@ describe('Admin mentor routes', () => { mentorId = response.body.mentor.uuid mentorProfileId = response.body.mentor.profile.uuid + category = categoryResponse.body.category }, 5000) it('should update the mentor application state', async () => { @@ -178,6 +182,26 @@ describe('Admin mentor routes', () => { await mentorAgent.get(`/api/admin/mentors/search?q=john`).expect(403) }) + it('should return mentors category starts with Computer and a success message when mentors by category are found', async () => { + const response = await adminAgent + .get(`/api/admin/mentors/category?category=${category.category}`) + .expect(200) + + const mentorDetails = response.body.mentors + + mentorDetails.forEach((mentor: Mentor) => { + expect(mentor).toHaveProperty('profile') + expect(mentor).toHaveProperty('application') + expect(mentor).toHaveProperty('category') + }) + }) + + it('should only allow admins to search mentors by category', async () => { + await mentorAgent + .get(`/api/admin/mentors/category?category=${category.category}`) + .expect(403) + }) + afterAll(async () => { await dataSource.destroy() }) diff --git a/src/routes/admin/mentor/mentor.route.ts b/src/routes/admin/mentor/mentor.route.ts index 6db479b8..926aaeb0 100644 --- a/src/routes/admin/mentor/mentor.route.ts +++ b/src/routes/admin/mentor/mentor.route.ts @@ -2,6 +2,7 @@ import express from 'express' import { requireAuth } from '../../../controllers/auth.controller' import { getAllMentorEmails, + getAllMentorsByCategory, getAllMentorsByStatus, mentorStatusHandler, searchMentors, @@ -19,5 +20,6 @@ mentorRouter.put( updateMentorAvailability ) mentorRouter.get('/search', requireAuth, searchMentors) +mentorRouter.get('/category', requireAuth, getAllMentorsByCategory) export default mentorRouter diff --git a/src/services/admin/mentor.service.ts b/src/services/admin/mentor.service.ts index b826d4a8..c598936f 100644 --- a/src/services/admin/mentor.service.ts +++ b/src/services/admin/mentor.service.ts @@ -109,3 +109,52 @@ export const findAllMentorEmails = async ( throw new Error('Error getting mentors emails') } } + +export const searchMentorsByCategory = async ( + category?: string +): Promise<{ + statusCode: number + mentors?: Mentor[] | null + message: string +}> => { + try { + const mentorRepository = dataSource.getRepository(Mentor) + + const categoryQuery = category ? `${category}%` : '' + + const mentors: Mentor[] = await mentorRepository + .createQueryBuilder('mentor') + .innerJoinAndSelect('mentor.category', 'category') + .leftJoinAndSelect('mentor.profile', 'profile') + .select([ + 'mentor.uuid', + 'category.category', + 'mentor.application', + 'profile.contact_email', + 'profile.first_name', + 'profile.last_name', + 'profile.image_url', + 'profile.linkedin_url' + ]) + .where('category.category ILIKE :name', { + name: categoryQuery + }) + .getMany() + + if (!mentors) { + return { + statusCode: 404, + message: 'Mentors not found' + } + } + + return { + statusCode: 200, + mentors, + message: 'All search Mentors by category found' + } + } catch (err) { + console.error('Error getting mentor', err) + throw new Error('Error getting mentor') + } +} From eb08bf371c456ccbeb2ef7f529b2e9525bff3213 Mon Sep 17 00:00:00 2001 From: Krishnadeva Date: Sun, 3 Dec 2023 16:19:10 +0530 Subject: [PATCH 14/15] bug fix --- src/routes/admin/mentor/mentor.route.test.ts | 1 - src/services/admin/mentor.service.ts | 10 ---------- 2 files changed, 11 deletions(-) diff --git a/src/routes/admin/mentor/mentor.route.test.ts b/src/routes/admin/mentor/mentor.route.test.ts index 8d06406c..fa33755f 100644 --- a/src/routes/admin/mentor/mentor.route.test.ts +++ b/src/routes/admin/mentor/mentor.route.test.ts @@ -191,7 +191,6 @@ describe('Admin mentor routes', () => { mentorDetails.forEach((mentor: Mentor) => { expect(mentor).toHaveProperty('profile') - expect(mentor).toHaveProperty('application') expect(mentor).toHaveProperty('category') }) }) diff --git a/src/services/admin/mentor.service.ts b/src/services/admin/mentor.service.ts index c598936f..4afdc017 100644 --- a/src/services/admin/mentor.service.ts +++ b/src/services/admin/mentor.service.ts @@ -126,16 +126,6 @@ export const searchMentorsByCategory = async ( .createQueryBuilder('mentor') .innerJoinAndSelect('mentor.category', 'category') .leftJoinAndSelect('mentor.profile', 'profile') - .select([ - 'mentor.uuid', - 'category.category', - 'mentor.application', - 'profile.contact_email', - 'profile.first_name', - 'profile.last_name', - 'profile.image_url', - 'profile.linkedin_url' - ]) .where('category.category ILIKE :name', { name: categoryQuery }) From 66ae3b00f0c673e65ee4a7c6a3b263e1f2f7cc9a Mon Sep 17 00:00:00 2001 From: Krishnadeva Date: Tue, 26 Dec 2023 13:15:17 +0530 Subject: [PATCH 15/15] Update type orm package version --- package-lock.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index b7a8099d..a3d95bf0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1722,7 +1722,8 @@ "node_modules/@types/node": { "version": "20.1.4", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.1.4.tgz", - "integrity": "sha512-At4pvmIOki8yuwLtd7BNHl3CiWNbtclUbNtScGx4OHfBd4/oWoJC8KRCIxXwkdndzhxOsPXihrsOoydxBjlE9Q==" + "integrity": "sha512-At4pvmIOki8yuwLtd7BNHl3CiWNbtclUbNtScGx4OHfBd4/oWoJC8KRCIxXwkdndzhxOsPXihrsOoydxBjlE9Q==", + "dev": true }, "node_modules/@types/passport": { "version": "1.0.12", @@ -9456,6 +9457,7 @@ "version": "5.1.6", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", + "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver"