Skip to content

MSPCA-17: Approve Volunteer Endpoint - #10

Open
shreeyaadhikari wants to merge 12 commits into
mainfrom
sa/mspca-17-approve-volunteer-endpoint
Open

shreeyaadhikari wants to merge 12 commits into
mainfrom
sa/mspca-17-approve-volunteer-endpoint

Conversation

@shreeyaadhikari

@shreeyaadhikari shreeyaadhikari commented Oct 6, 2026 •

Copy link
Copy Markdown

ℹ️ Issue

Closes MSPCA-17

This PR is built on top of MSPCA-7 which adds the VolunteerStatus enum this ticket depends on.

📝 Description

When a foster volunteer creates an account, it starts as Pending. This PR lets foster coordinators approve a pending volunteer. Approving sets the account to Active and emails the volunteer that they've been approved and can now log in. It also adds a reusable checkVolunteerActive helper for future endpoints that should only work for active volunteers.

Changes:

  1. New endpoint PATCH /volunteers/:id/approve (volunteers.controller.ts, volunteers.service.ts)
    • changes volunteer status from Pending to Active, then sends the approval email
    • 400 for an invalid ID or a volunteer who isn't pending
    • 404 if the volunteer doesn't exist
    • 409 if the volunteer is already active (no email is resent)
    • If the email fails to send, the approval still succeeds and the error is logged. Otherwise the coordinator would get an error, and retrying would fail with "already active."
  2. New emails folder with bodies/volunteerApproved.ts (the approval email's subject and body) and emails.utils.ts (an escapeHtml helper so a volunteer's name is shown as plain text in emails).
  3. Connected the email service to the volunteers module so volunteers can be emailed.
  4. Added a checkVolunteerActive helper that throws an error if a volunteer isn't active.
  5. Tests: service and controller tests

✔️ Verification

Ran yarn test. All service and controller tests pass

🏕️ (Optional) Future Work / Notes

@shreeyaadhikari
shreeyaadhikari marked this pull request as ready for review October 6, 2026 04:30
@dburkhart07
dburkhart07 self-requested a review October 6, 2026 06:33
@shreeyaadhikari
shreeyaadhikari requested a balanced review from Copilot October 6, 2026 12:16

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@dburkhart07 dburkhart07 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all very small things, looking really good so far!!! 🦓

* @param volunteer - The Volunteer to check.
* @throws {BadRequestException} If the Volunteer's status is not Active.
*/
export function checkVolunteerActive(volunteer: FosterVolunteer): void {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think since we are dealing with volunteers, this helper can be put in the volunteers service (along with its tests), and any other service that wants to check this can import the VolunteerService. The validation utils is for highly generalized helper functions that many controller or service functions may all need access to.

async approve(id: number): Promise<FosterVolunteer> {
const volunteer = await this.findByIdOrFail(id);

if (volunteer.status === VolunteerStatus.ACTIVE) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dont think we need this here. we only really need one check for the volunteer's status, which should be whether or not they are pending

}

if (volunteer.status !== VolunteerStatus.PENDING) {
throw new BadRequestException(`Only pending Volunteers can be approved`);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be a conflict exception. will need to update the documentation on the controller based on this and the above comment

* @param value - The raw string.
* @returns The HTML-escaped string.
*/
export function escapeHtml(value: string): string {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is really cool, i didnt know that we needed this (seems good for defense against strange edits people make to fields). do you think we could abstract this file into the utils file, and import it from there? let's also add some tests for it in there.

repo.save.mockImplementation(async (v) => v);

const result = await service.approve(1);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's check repo.FindByIdOrFail was also called with the right parameter

});
});

it('throws ConflictException and does not resend the email if already active', async () => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can be combined with the test below. i think throws ConflictException and does not send an email if the volunteer is not pending


await expect(service.approve(999)).rejects.toThrow(
new NotFoundException('Volunteer with ID 999 not found'),
);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets make sure findByIdOrFail was called here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants