From 20befe46036e4cd55b09689490e4359f0c062ecd Mon Sep 17 00:00:00 2001 From: shrutiii Date: Tue, 5 Aug 2025 02:02:14 +0530 Subject: [PATCH 1/6] Updated update member route to accept password field --- src/controllers/member.controller.ts | 3 ++- src/services/member.service.ts | 26 ++++++++++++++------------ src/types/members.d.ts | 3 +-- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/controllers/member.controller.ts b/src/controllers/member.controller.ts index ca5d5ca..bf922c8 100644 --- a/src/controllers/member.controller.ts +++ b/src/controllers/member.controller.ts @@ -3,6 +3,7 @@ import * as memberService from "../services/member.service"; import { ApiError } from "../utils/apiError"; import { uploadImage } from "../utils/imageUtils"; import { SupabaseClient } from "@supabase/supabase-js"; +import { password } from "bun"; // List all approved members export const listAllApprovedMembers = async (req: Request, res: Response) => { @@ -46,7 +47,7 @@ export const createAMember = (supabase: SupabaseClient) => async (req: Request, res: Response) => { const {email, name, password, passoutYear, provider} = req.body; - if (!email || !name || !password || !passoutYear) { + if (!email || !name || !password || !passoutYear || !provider) { throw new ApiError("Required fields absent", 400); } diff --git a/src/services/member.service.ts b/src/services/member.service.ts index f473080..d5fe44f 100644 --- a/src/services/member.service.ts +++ b/src/services/member.service.ts @@ -66,26 +66,28 @@ export const createMember = async ( export const updateMember = async ( id: string, - payload: UpdateMemberPayload, + payload: UpdateMemberPayload ) => { - const { name, ...rest } = payload; + const { name, password, ...rest } = payload; - const member = await prisma.member.findUnique({ - where: { - id: id, - }, - }); + if (password) { + const account = await prisma.account.findFirst({ + where: { memberId: id }, + }); + + if (!account) throw new ApiError("Associated account not found", 404); - if (!member) { - throw new ApiError("Member not found", 404); + return await prisma.account.update({ + where: { id: account.id }, + data: { password }, + }); } const dataToUpdate = Object.fromEntries( - Object.entries(rest).filter(([_, v]) => v !== undefined), + Object.entries({ name, ...rest }).filter(([_, v]) => v !== undefined) ); - if (JSON.stringify(dataToUpdate) === "{}") - throw new ApiError("No fields passed", 400); + if (JSON.stringify(dataToUpdate) === "{}" && !password) throw new ApiError("No fields passed", 400); return await prisma.member.update({ where: { id }, diff --git a/src/types/members.d.ts b/src/types/members.d.ts index 17a24a2..ebd2e9e 100644 --- a/src/types/members.d.ts +++ b/src/types/members.d.ts @@ -15,7 +15,6 @@ declare global { leetcode?: string; codechef?: string; codeforces?: string; - isApproved?: boolean; - approvedBy?: string; + password?: string } } \ No newline at end of file From 6860584f25fa2aceac340548a98a5c1fe875a231 Mon Sep 17 00:00:00 2001 From: shrutiii Date: Tue, 5 Aug 2025 02:04:53 +0530 Subject: [PATCH 2/6] removed password import --- src/controllers/member.controller.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/controllers/member.controller.ts b/src/controllers/member.controller.ts index bf922c8..1e80cbd 100644 --- a/src/controllers/member.controller.ts +++ b/src/controllers/member.controller.ts @@ -3,7 +3,6 @@ import * as memberService from "../services/member.service"; import { ApiError } from "../utils/apiError"; import { uploadImage } from "../utils/imageUtils"; import { SupabaseClient } from "@supabase/supabase-js"; -import { password } from "bun"; // List all approved members export const listAllApprovedMembers = async (req: Request, res: Response) => { From 4c3d7f58a93a0464e0af18c9e79b9c1bfda2e30b Mon Sep 17 00:00:00 2001 From: shrutiii Date: Tue, 5 Aug 2025 02:36:13 +0530 Subject: [PATCH 3/6] Updated tests" --- tests/Member.test.ts | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/tests/Member.test.ts b/tests/Member.test.ts index fb40180..9db573b 100644 --- a/tests/Member.test.ts +++ b/tests/Member.test.ts @@ -13,10 +13,15 @@ jest.mock('../src/db/client', () => ({ create: jest.fn(), delete: jest.fn(), }, + account: { + findFirst: jest.fn(), + update: jest.fn(), + }, }, })); jest.mock('../src/utils/imageUtils'); + const mockSupabase = {} as SupabaseClient; const mockResponse = (): Response => { @@ -31,13 +36,14 @@ describe('Member Controller, createAMember', () => { jest.clearAllMocks(); }); - it('should respond with 201 and created member', async () => { + it('should respond with 201 and created member', async () => { const req = { body: { email: 'shruti@example.com', name: 'Shruti', password: 'password123', passoutYear: '2026', + provider: 'credentials' }, file: undefined, } as unknown as Request; @@ -50,7 +56,7 @@ describe('Member Controller, createAMember', () => { const handler = createAMember(mockSupabase); await handler(req, res); - expect(res.status).toHaveBeenCalledWith(201); + expect(res.status).toHaveBeenCalledWith(201); expect(res.json).toHaveBeenCalledWith({ success: true, user: mockUser }); }); @@ -71,7 +77,7 @@ describe('Member Controller - updateAMember', () => { it('should update member and return updated data (no image)', async () => { const req = { params: { memberId: 'abc-123' }, - body: { memberData: JSON.stringify({ github: 'https://github.com/shrutii' }) }, + body: { memberData: JSON.stringify({ github: 'https://github.com/shrutii' }) }, file: undefined, } as unknown as Request; @@ -81,10 +87,10 @@ describe('Member Controller - updateAMember', () => { id: '123', name: 'Test User', email: 'test@example.com', + github: 'https://github.com/shrutii', + profilePhoto: null, phone: null, bio: null, - profilePhoto: null, - github: 'https://github.com/shrutii', linkedin: null, twitter: null, leetcode: null, @@ -100,14 +106,14 @@ describe('Member Controller - updateAMember', () => { updatedAt: new Date(), }; - const spyUpdate = jest.spyOn(memberService, 'updateMember').mockResolvedValue(updatedMember); - const spyGet = jest.spyOn(memberService, 'getDetails').mockResolvedValue(updatedMember); + jest.spyOn(memberService, 'updateMember').mockResolvedValue(updatedMember); + jest.spyOn(memberService, 'getDetails').mockResolvedValue(updatedMember); const handler = updateAMember(mockSupabase); await handler(req, res); - expect(spyUpdate).toHaveBeenCalledWith('abc-123', { github: 'https://github.com/shrutii' }); - expect(spyGet).toHaveBeenCalledTimes(1); + expect(memberService.updateMember).toHaveBeenCalledWith('abc-123', { github: 'https://github.com/shrutii' }); + expect(memberService.getDetails).toHaveBeenCalledTimes(1); expect(res.status).toHaveBeenCalledWith(200); expect(res.json).toHaveBeenCalledWith({ success: true, @@ -152,15 +158,13 @@ describe('Member Controller - updateAMember', () => { profilePhoto: 'https://new.url/image.png', }; - (uploadImage as jest.Mock) - .mockResolvedValueOnce('https://new.url/image.png'); + (uploadImage as jest.Mock).mockResolvedValueOnce('https://new.url/image.png'); jest.spyOn(memberService, 'getDetails') - .mockResolvedValueOnce(oldMember) - .mockResolvedValueOnce(updatedMember); + .mockResolvedValueOnce(oldMember) + .mockResolvedValueOnce(updatedMember); - const spyUpdate = jest - .spyOn(memberService, 'updateMember') + jest.spyOn(memberService, 'updateMember') .mockResolvedValue(updatedMember); const handler = updateAMember(mockSupabase); @@ -173,9 +177,7 @@ describe('Member Controller - updateAMember', () => { 'https://old.url/image.png' ); - - expect(spyUpdate).toHaveBeenCalledWith('abc-123', { - + expect(memberService.updateMember).toHaveBeenCalledWith('abc-123', { }); expect(res.status).toHaveBeenCalledWith(200); From f90dd5bc90d1bbd7be73bc53ba81758d20f6dfa4 Mon Sep 17 00:00:00 2001 From: shrutiii Date: Mon, 11 Aug 2025 14:54:29 +0530 Subject: [PATCH 4/6] Updated annotations according to changes --- src/routes/members.ts | 164 +++++++++++++++++++++++++++--------------- 1 file changed, 107 insertions(+), 57 deletions(-) diff --git a/src/routes/members.ts b/src/routes/members.ts index b2000b2..98c235a 100644 --- a/src/routes/members.ts +++ b/src/routes/members.ts @@ -15,82 +15,118 @@ export default function membersRouter( * @apiGroup Member * * @apiSuccess {Object[]} unapprovedMembers List of unapproved members. + * + * @apiExample {curl} Example usage: + * curl -X GET http://localhost:3000/members/unapproved */ router.get("/unapproved", memberCtrl.getUnapprovedMembers); /** - * @api {get} /members/:memberId Get a member's details - * @apiName GetUserDetails - * @apiGroup Member - * - * @apiParam (URL Params) {String} memberId Member's unique ID. - * - * @apiSuccess {Object} user Member object. - * @apiError (Error 400) BadRequest No memberId provided. - */ + * @api {get} /members/:memberId Get a member's details + * @apiName GetUserDetails + * @apiGroup Member + * + * @apiParam (URL Params) {String} memberId Member's unique ID. + * + * @apiSuccess {Object} user Member object. + * @apiError (Error 400) BadRequest No memberId provided. + * + * @apiExample {curl} Example usage: + * curl -X GET http://localhost:3000/members/123 + */ router.get("/:memberId", memberCtrl.getUserDetails); - - /** + +/** * @api {get} /members List all approved members or get member by email * @apiName ListAllApprovedMembers * @apiGroup Member * - * @apiDescription + * @apiDescription * - Returns a list of all approved members if no email query parameter is provided. * - If `email` query parameter is provided, returns the member associated with that email. * - * @apiQuery {String} [email] Optional email to fetch a specific member. + * @apiQuery {String} [email] Optional email to fetch a specific member. * * @apiSuccess {Object} user Single user object when email provided. -+ * @apiSuccess {Object[]} user Array of approved members when no email provided. + * @apiSuccess {Object[]} user Array of approved members when no email provided. * @apiSuccess {String} [message] Message in case of full list fetch. * * @apiError (400) IncorrectEmail The provided email does not match any user. + * + * @apiExample {curl} Example usage (list all): + * curl -X GET http://localhost:3000/members + * + * @apiExample {curl} Example usage (get by email): + * curl -X GET "http://localhost:3000/members?email=john@example.com" */ router.get("/", memberCtrl.listAllApprovedMembers); /** - * @api {post} /members Create a new member - * @apiName CreateAMember - * @apiGroup Member - * - * @apiBody {String} email Email of the member. - * @apiBody {String} name Full name of the member. - * @apiBody {String} password Member's password. - * @apiBody {String} passoutYear Graduation year. - * @apiBody {String} imageUrl profile photo of the member. - * - * @apiSuccess {Object} user Created member object. - * @apiError (Error 402) ValidationError Required fields missing. - */ + * @api {post} /members Create a new member + * @apiName CreateAMember + * @apiGroup Member + * + * @apiBody {String} email Email of the member. (Required) + * @apiBody {String} name Full name of the member. (Required) + * @apiBody {String} password Member's password. (Required) + * @apiBody {String} passoutYear Graduation year (Required, e.g., "2026"). + * @apiBody {String} provider Authentication provider (Required, e.g., "local", "google"). + * @apiBody {File} [file] Profile photo file (field name: "file"). + * + * @apiSuccess {Boolean} success Request status. + * @apiSuccess {Object} user Created member object. + * + * @apiError (Error 400) ApiError Required fields absent. + * @apiError (Error 500) ServerError Error creating user. + * + * @apiExample {curl} Example usage: + * curl -X POST -F "file=@profile.jpg" \ + * -F "email=john@example.com" \ + * -F "name=John Doe" \ + * -F "password=securePass123" \ + * -F "passoutYear=2026" \ + * -F "provider=local" \ + * http://localhost:3000/members + */ + router.post("/", upload.single("file"), memberCtrl.createAMember(supabase)); /** - * @api {patch} /members/:memberId Update a member - * @apiName UpdateAMember - * @apiGroup Member - * - * @apiParam {String} memberId Member's unique ID. - * - * @apiBody {File} [file] Profile photo file (field name: "file"). - * @apiBody {String} [name] Full name of the member. - * @apiBody {String} [email] Email address. - * @apiBody {String} [phone] Phone number. - * @apiBody {String} [bio] Short bio. - * @apiBody {String} [github] GitHub handle. - * @apiBody {String} [linkedin] LinkedIn handle. - * @apiBody {String} [twitter] Twitter handle. - * @apiBody {String} [geeksforgeeks] GeeksforGeeks username. - * @apiBody {String} [leetcode] LeetCode username. - * @apiBody {String} [codechef] CodeChef username. - * @apiBody {String} [codeforces] Codeforces username. - * @apiBody {Date} [passoutYear] Graduation year (ISO string format). - * - * @apiSuccess {Object} member Updated member object. - * @apiError (Error 404) NotFound Member not found. - * @apiError (Error 400) ValidationError Invalid or missing fields. - * @apiError (Error 500) ServerError Unexpected error occurred during update. - */ + * @api {patch} /members/:memberId Update a member + * @apiName UpdateAMember + * @apiGroup Member + * + * @apiParam {String} memberId Member's unique ID. + * + * @apiBody {String} memberData JSON string containing the member's updated details. + * @apiBody {File} [file] Profile photo file (field name: "file"). + * + * @apiBody (memberData fields) {String} [name] Full name of the member. + * @apiBody (memberData fields) {String} [email] Email address. + * @apiBody (memberData fields) {String} [phone] Phone number. + * @apiBody (memberData fields) {String} [bio] Short bio. + * @apiBody (memberData fields) {String} [github] GitHub handle. + * @apiBody (memberData fields) {String} [linkedin] LinkedIn handle. + * @apiBody (memberData fields) {String} [twitter] Twitter handle. + * @apiBody (memberData fields) {String} [geeksforgeeks] GeeksforGeeks username. + * @apiBody (memberData fields) {String} [leetcode] LeetCode username. + * @apiBody (memberData fields) {String} [codechef] CodeChef username. + * @apiBody (memberData fields) {String} [codeforces] Codeforces username. + * @apiBody (memberData fields) {Date} [passoutYear] Graduation year (ISO string format). + * @apiBody (memberData fields) {String} [profilePhoto] (Auto-assigned if a new file is uploaded). + * + * @apiSuccess {Boolean} success Request status. + * @apiSuccess {Object} user Updated member object. + * + * @apiError (Error 400) ApiError No memberId provided or invalid request data. + * @apiError (Error 404) NotFound Member not found. + * @apiError (Error 500) ServerError Unexpected error occurred during update. + * + * @apiExample {curl} Example usage: + * curl -X PATCH -F "file=@profile.jpg" \ + * -F 'memberData={"name":"John Doe","email":"john@example.com"}' \ + * http://localhost:3000/members/123 + */ router.patch( "/:memberId", upload.single("file"), @@ -98,16 +134,21 @@ export default function membersRouter( ); /** - * @api {patch} /members/approve/:memberId Approve/reject a member + * @api {patch} /members/approve/:memberId Approve a member * @apiName UpdateApprovalRequest * @apiGroup Member * * @apiParam (URL Params) {String} memberId Member ID. - * @apiBody {Boolean} isApproved Approval status. - * @apiBody {String} adminId Admin who approved. + * @apiBody {Boolean} isApproved Approval status (true = approved, false = rejected). + * @apiBody {String} adminId ID of the admin who approved. * - * @apiSuccess {Object} update Approval status updated. + * @apiSuccess {Object} update Approval status update result. * @apiError (Error 400) BadRequest Missing required fields. + * + * @apiExample {curl} Example usage: + * curl -X PATCH http://localhost:3000/members/approve/123 \ + * -H "Content-Type: application/json" \ + * -d '{"isApproved": true, "adminId": "admin123"}' */ router.patch("/approve/:memberId", memberCtrl.updateRequest); @@ -119,17 +160,23 @@ export default function membersRouter( * @apiParam (URL Params) {String} memberId Member ID. * * @apiSuccess {Object[]} achievements List of achievements. + * + * @apiExample {curl} Example usage: + * curl -X GET http://localhost:3000/members/123/achievements */ router.get("/:memberId/achievements", memberCtrl.getUserAchievements); /** - * @api {get} /api/members/:memberId/projects Get member's projects + * @api {get} /members/:memberId/projects Get member's projects * @apiName GetUserProjects * @apiGroup Member * * @apiParam (URL Params) {String} memberId Member ID. * * @apiSuccess {Object[]} projects List of projects. + * + * @apiExample {curl} Example usage: + * curl -X GET http://localhost:3000/members/123/projects */ router.get("/:memberId/projects", memberCtrl.getUserProjects); @@ -141,6 +188,9 @@ export default function membersRouter( * @apiParam (URL Params) {String} memberId Member ID. * * @apiSuccess {Object[]} interviews List of interviews. + * + * @apiExample {curl} Example usage: + * curl -X GET http://localhost:3000/members/123/interviews */ router.get("/:memberId/interviews", memberCtrl.getUserInterviews); From f5e2a6deeccd926b9b8a52e4f1f00eef24b98970 Mon Sep 17 00:00:00 2001 From: shrutiii Date: Mon, 11 Aug 2025 16:00:43 +0530 Subject: [PATCH 5/6] Refactored update controller to handle multiple services --- src/controllers/member.controller.ts | 3 +- src/routes/members.ts | 181 ++++++++++++++------------- src/services/member.service.ts | 30 ++--- 3 files changed, 108 insertions(+), 106 deletions(-) diff --git a/src/controllers/member.controller.ts b/src/controllers/member.controller.ts index 1e80cbd..c89ff64 100644 --- a/src/controllers/member.controller.ts +++ b/src/controllers/member.controller.ts @@ -89,7 +89,8 @@ export const updateAMember = } if (imageUrl) parsedBody.profilePhoto = imageUrl; - await memberService.updateMember(memberId, parsedBody); + if(parsedBody.password) await memberService.updatePassword(memberId, parsedBody.password); + else await memberService.updateMember(memberId, parsedBody); const updatedData = await memberService.getDetails(memberId); res diff --git a/src/routes/members.ts b/src/routes/members.ts index 98c235a..87d9962 100644 --- a/src/routes/members.ts +++ b/src/routes/members.ts @@ -22,21 +22,21 @@ export default function membersRouter( router.get("/unapproved", memberCtrl.getUnapprovedMembers); /** - * @api {get} /members/:memberId Get a member's details - * @apiName GetUserDetails - * @apiGroup Member - * - * @apiParam (URL Params) {String} memberId Member's unique ID. - * - * @apiSuccess {Object} user Member object. - * @apiError (Error 400) BadRequest No memberId provided. - * - * @apiExample {curl} Example usage: - * curl -X GET http://localhost:3000/members/123 - */ + * @api {get} /members/:memberId Get a member's details + * @apiName GetUserDetails + * @apiGroup Member + * + * @apiParam (URL Params) {String} memberId Member's unique ID. + * + * @apiSuccess {Object} user Member object. + * @apiError (Error 400) BadRequest No memberId provided. + * + * @apiExample {curl} Example usage: + * curl -X GET http://localhost:3000/members/123 + */ router.get("/:memberId", memberCtrl.getUserDetails); - -/** + + /** * @api {get} /members List all approved members or get member by email * @apiName ListAllApprovedMembers * @apiGroup Member @@ -62,85 +62,85 @@ export default function membersRouter( router.get("/", memberCtrl.listAllApprovedMembers); /** - * @api {post} /members Create a new member - * @apiName CreateAMember - * @apiGroup Member - * - * @apiBody {String} email Email of the member. (Required) - * @apiBody {String} name Full name of the member. (Required) - * @apiBody {String} password Member's password. (Required) - * @apiBody {String} passoutYear Graduation year (Required, e.g., "2026"). - * @apiBody {String} provider Authentication provider (Required, e.g., "local", "google"). - * @apiBody {File} [file] Profile photo file (field name: "file"). - * - * @apiSuccess {Boolean} success Request status. - * @apiSuccess {Object} user Created member object. - * - * @apiError (Error 400) ApiError Required fields absent. - * @apiError (Error 500) ServerError Error creating user. - * - * @apiExample {curl} Example usage: - * curl -X POST -F "file=@profile.jpg" \ - * -F "email=john@example.com" \ - * -F "name=John Doe" \ - * -F "password=securePass123" \ - * -F "passoutYear=2026" \ - * -F "provider=local" \ - * http://localhost:3000/members - */ - + * @api {post} /members Create a new member + * @apiName CreateAMember + * @apiGroup Member + * + * @apiBody {String} email Email of the member. (Required) + * @apiBody {String} name Full name of the member. (Required) + * @apiBody {String} password Member's password. (Required) + * @apiBody {String} passoutYear Graduation year (Required, e.g., "2026"). + * @apiBody {String} provider Authentication provider (Required, e.g., "local", "google"). + * @apiBody {File} [file] Profile photo file (field name: "file"). + * + * @apiSuccess {Boolean} success Request status. + * @apiSuccess {Object} user Created member object. + * + * @apiError (Error 400) ApiError Required fields absent. + * @apiError (Error 500) ServerError Error creating user. + * + * @apiExample {curl} Example usage: + * curl -X POST -F "file=@profile.jpg" \ + * -F "email=john@example.com" \ + * -F "name=John Doe" \ + * -F "password=securePass123" \ + * -F "passoutYear=2026" \ + * -F "provider=local" \ + * http://localhost:3000/members + */ router.post("/", upload.single("file"), memberCtrl.createAMember(supabase)); /** - * @api {patch} /members/:memberId Update a member - * @apiName UpdateAMember - * @apiGroup Member - * - * @apiParam {String} memberId Member's unique ID. - * - * @apiBody {String} memberData JSON string containing the member's updated details. - * @apiBody {File} [file] Profile photo file (field name: "file"). - * - * @apiBody (memberData fields) {String} [name] Full name of the member. - * @apiBody (memberData fields) {String} [email] Email address. - * @apiBody (memberData fields) {String} [phone] Phone number. - * @apiBody (memberData fields) {String} [bio] Short bio. - * @apiBody (memberData fields) {String} [github] GitHub handle. - * @apiBody (memberData fields) {String} [linkedin] LinkedIn handle. - * @apiBody (memberData fields) {String} [twitter] Twitter handle. - * @apiBody (memberData fields) {String} [geeksforgeeks] GeeksforGeeks username. - * @apiBody (memberData fields) {String} [leetcode] LeetCode username. - * @apiBody (memberData fields) {String} [codechef] CodeChef username. - * @apiBody (memberData fields) {String} [codeforces] Codeforces username. - * @apiBody (memberData fields) {Date} [passoutYear] Graduation year (ISO string format). - * @apiBody (memberData fields) {String} [profilePhoto] (Auto-assigned if a new file is uploaded). - * - * @apiSuccess {Boolean} success Request status. - * @apiSuccess {Object} user Updated member object. - * - * @apiError (Error 400) ApiError No memberId provided or invalid request data. - * @apiError (Error 404) NotFound Member not found. - * @apiError (Error 500) ServerError Unexpected error occurred during update. - * - * @apiExample {curl} Example usage: - * curl -X PATCH -F "file=@profile.jpg" \ - * -F 'memberData={"name":"John Doe","email":"john@example.com"}' \ - * http://localhost:3000/members/123 - */ + * @api {patch} /members/:memberId Update a member + * @apiName UpdateAMember + * @apiGroup Member + * + * @apiParam {String} memberId Member's unique ID. + * + * @apiBody {String} memberData JSON string containing the member's updated details. + * @apiBody {File} [file] Profile photo file (field name: "file"). + * + * @apiBody (memberData fields) {String} [name] Full name of the member. + * @apiBody (memberData fields) {String} [email] Email address. + * @apiBody (memberData fields) {String} [phone] Phone number. + * @apiBody (memberData fields) {String} [bio] Short bio. + * @apiBody (memberData fields) {String} [github] GitHub handle. + * @apiBody (memberData fields) {String} [linkedin] LinkedIn handle. + * @apiBody (memberData fields) {String} [twitter] Twitter handle. + * @apiBody (memberData fields) {String} [geeksforgeeks] GeeksforGeeks username. + * @apiBody (memberData fields) {String} [leetcode] LeetCode username. + * @apiBody (memberData fields) {String} [codechef] CodeChef username. + * @apiBody (memberData fields) {String} [codeforces] Codeforces username. + * @apiBody (memberData fields) {Date} [passoutYear] Graduation year (ISO string format). + * @apiBody (memberData fields) {String} [profilePhoto] (Auto-assigned if a new file is uploaded). + * + * @apiSuccess {Boolean} success Request status. + * @apiSuccess {Object} user Updated member object. + * + * @apiError (Error 400) ApiError No memberId provided or invalid request data. + * @apiError (Error 404) NotFound Member not found. + * @apiError (Error 500) ServerError Unexpected error occurred during update. + * + * @apiExample {curl} Example usage: + * curl -X PATCH -F "file=@profile.jpg" \ + * -F 'memberData={"name":"John Doe","email":"john@example.com"}' \ + * http://localhost:3000/members/123 + */ router.patch( "/:memberId", upload.single("file"), memberCtrl.updateAMember(supabase), ); - /** + +/** * @api {patch} /members/approve/:memberId Approve a member * @apiName UpdateApprovalRequest * @apiGroup Member * * @apiParam (URL Params) {String} memberId Member ID. * @apiBody {Boolean} isApproved Approval status (true = approved, false = rejected). - * @apiBody {String} adminId ID of the admin who approved. + * @apiBody {String} adminId ID of the admin who approved/rejected. * * @apiSuccess {Object} update Approval status update result. * @apiError (Error 400) BadRequest Missing required fields. @@ -152,18 +152,19 @@ export default function membersRouter( */ router.patch("/approve/:memberId", memberCtrl.updateRequest); - /** - * @api {get} /members/:memberId/achievements Get member's achievements - * @apiName GetUserAchievements - * @apiGroup Member - * - * @apiParam (URL Params) {String} memberId Member ID. - * - * @apiSuccess {Object[]} achievements List of achievements. - * - * @apiExample {curl} Example usage: - * curl -X GET http://localhost:3000/members/123/achievements - */ + +/** + * @api {get} /members/:memberId/achievements Get member's achievements + * @apiName GetUserAchievements + * @apiGroup Member + * + * @apiParam (URL Params) {String} memberId Member ID. + * + * @apiSuccess {Object[]} achievements List of achievements. + * + * @apiExample {curl} Example usage: + * curl -X GET http://localhost:3000/members/123/achievements + */ router.get("/:memberId/achievements", memberCtrl.getUserAchievements); /** diff --git a/src/services/member.service.ts b/src/services/member.service.ts index d5fe44f..6f0e96b 100644 --- a/src/services/member.service.ts +++ b/src/services/member.service.ts @@ -68,26 +68,13 @@ export const updateMember = async ( id: string, payload: UpdateMemberPayload ) => { - const { name, password, ...rest } = payload; - - if (password) { - const account = await prisma.account.findFirst({ - where: { memberId: id }, - }); - - if (!account) throw new ApiError("Associated account not found", 404); - - return await prisma.account.update({ - where: { id: account.id }, - data: { password }, - }); - } + const { name, ...rest } = payload; const dataToUpdate = Object.fromEntries( Object.entries({ name, ...rest }).filter(([_, v]) => v !== undefined) ); - if (JSON.stringify(dataToUpdate) === "{}" && !password) throw new ApiError("No fields passed", 400); + if (JSON.stringify(dataToUpdate) === "{}") throw new ApiError("No fields passed", 400); return await prisma.member.update({ where: { id }, @@ -95,6 +82,19 @@ export const updateMember = async ( }); }; +export const updatePassword = async(id: string, password: string) => { + const account = await prisma.account.findFirst({ + where: { memberId: id }, + }); + + if (!account) throw new ApiError("Associated account not found", 404); + + return await prisma.account.update({ + where: { id: account.id }, + data: { password }, + }); +} + export const unapprovedMembers = async () => { return await prisma.member.findMany({ where: { isApproved: false }, From 75ab289773c4620a219a9d83128045e089b43072 Mon Sep 17 00:00:00 2001 From: shrutiii Date: Tue, 12 Aug 2025 19:40:45 +0530 Subject: [PATCH 6/6] Added updatePassword service test --- tests/Member.test.ts | 48 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/tests/Member.test.ts b/tests/Member.test.ts index 9db573b..b82e244 100644 --- a/tests/Member.test.ts +++ b/tests/Member.test.ts @@ -186,4 +186,52 @@ describe('Member Controller - updateAMember', () => { user: updatedMember, }); }); + + it('should update password when password is provided in memberData', async () => { + const req = { + params: { memberId: 'abc-123' }, + body: { memberData: JSON.stringify({ password: 'newSecurePass123' }) }, + file: undefined, + } as unknown as Request; + + const res = mockResponse(); + + const updatedMember = { + id: '123', + name: 'Updated User', + email: 'updated@example.com', + profilePhoto: null, + github: null, + phone: null, + bio: null, + linkedin: null, + twitter: null, + leetcode: null, + codeforces: null, + codechef: null, + gfg: null, + geeksforgeeks: null, + passoutYear: new Date('2025-05-31'), + isManager: false, + isApproved: false, + approvedById: null, + createdAt: new Date(), + updatedAt: new Date(), + }; + + // Mock updatePassword and getDetails + jest.spyOn(memberService, 'updatePassword').mockResolvedValue({} as any); + jest.spyOn(memberService, 'getDetails').mockResolvedValue(updatedMember); + + const handler = updateAMember(mockSupabase); + await handler(req, res); + + expect(memberService.updatePassword).toHaveBeenCalledWith('abc-123', 'newSecurePass123'); + expect(memberService.getDetails).toHaveBeenCalledTimes(1); + expect(res.status).toHaveBeenCalledWith(200); + expect(res.json).toHaveBeenCalledWith({ + success: true, + user: updatedMember, + }); + }); });