Skip to content

Conversation

@1michhu1
Copy link

@1michhu1 1michhu1 commented Nov 23, 2025

Implemented the function handler for filtering volunteers by role(s).

Notes:
-Currently, the response object contains the column values in the Volunteers table for each filtered volunteers, as well as an additional field named filtered_rows representing the roles that that volunteer was filtered on. So for instance, if volunteer1 has roles 1 and 2 and I filter for volunteers with role 1, then filtered_rows contains only role 1 for volunteer 1. This can be changed if necessary
-Due to the limited filtering options with the Supabase JS client, I currently filter out non-matching rows in the function itself. I was thinking if this isn't acceptable for performance, I could try paginating it instead with range or adding a Postgres function?
-Added a non-exhaustive set of simple unit tests

Test Cases:

Volunteers Table:
image

VolunteerRoles Table
image

Roles Table
image

Test 1: Filter for Role 1 with operator
image

Test 2: Filter for Role 1 and Role 2 with OR operator
image

Test 3: Filter for Role 1 and Role 2 with AND operator
image

@vercel
Copy link

vercel bot commented Nov 23, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
trcc Error Error Nov 23, 2025 8:50pm

@1michhu1 1michhu1 linked an issue Nov 23, 2025 that may be closed by this pull request
@1michhu1 1michhu1 added the backend this ticket is part of the backend label Nov 23, 2025
@1michhu1 1michhu1 self-assigned this Nov 23, 2025
@1zhaohel 1zhaohel requested a review from Copilot November 23, 2025 20:49
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR implements a new API endpoint for filtering volunteers by role(s) with support for AND/OR operators. The implementation includes the API function, route handler, and basic unit tests. However, the PR contains several issues including a critical bug in the AND operator logic, code duplication across two nearly identical files, and unrelated package changes that need clarification.

Key Changes:

  • New API function getVolunteersByRoles that queries the VolunteerRoles junction table to filter volunteers by role names
  • Route handler at /volunteers/filter_by_role supporting query parameters for operator (AND/OR) and roles
  • Unit tests covering basic scenarios including validation errors and OR/AND filtering

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/lib/api/getVolunteersByRoles.ts Main API function implementing role-based volunteer filtering with validation and map-based aggregation
src/lib/api/getRolesByFilter.ts Duplicate implementation with minor variations (should be removed)
src/app/volunteers/filter_by_role/route.ts GET endpoint handler for the filter API with request parameter parsing
tests/lib/api/getVolunteersByRoles.test.ts Unit tests for validation and basic OR/AND filtering scenarios
src/lib/api/index.ts Export additions for new API function and helper functions
package.json Added "type": "module" field (unrelated to feature)
package-lock.json Supabase CLI upgrade and license field cleanup (unrelated to feature)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

}
} else {
for (const volunteer of volunteerRoleMap.values()) {
if (volunteer.roleNames.size == filters.length) {
Copy link

Copilot AI Nov 23, 2025

Choose a reason for hiding this comment

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

The AND operator logic is incorrect. This checks if the number of matched roles equals the filter count, but doesn't verify that ALL requested roles are present. For example, if filters are ["Role 1", "Role 2"] and a volunteer has ["Role 1", "Role 1"] (counted once in the Set), they would incorrectly match since size is 1 but filters.length is 2 and they don't match. More critically, if a volunteer has ["Role 1", "Role 2", "Role 3"] and you filter for ["Role 1", "Role 2"], they would match (size 3 ≠ filters.length 2) incorrectly.

The correct logic should verify that all filter roles are present in the volunteer's roles:

if (filters.every(filter => volunteer.roleNames.has(filter))) {
Suggested change
if (volunteer.roleNames.size == filters.length) {
if (filters.every(filter => volunteer.roleNames.has(filter))) {

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Choose a reason for hiding this comment

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

I agree with Copilot on this, you are only checking the count, not the actual content itself.

Copy link
Author

@1michhu1 1michhu1 Nov 29, 2025

Choose a reason for hiding this comment

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

Right now, I have the select from the db set up so that I join Roles to Volunteers, and I filter for rows such that the role name is in the filters.

More critically, if a volunteer has ["Role 1", "Role 2", "Role 3"] and you filter for ["Role 1", "Role 2"], they would match (size 3 ≠ filters.length 2) incorrectly.

I don't think a case like this would occur since the rows I would get from the db would consist of rows where the role name is in the filters (ie. either Role 1 or Role 2).

For example, if filters are ["Role 1", "Role 2"] and a volunteer has ["Role 1", "Role 1"] (counted once in the Set), they would incorrectly match since size is 1 but filters.length is 2 and they don't match

And for this case, if the operator was OR, then the volunteer would be matched. If the operator was AND, the set of role names would only contain Role 1 and the length of the set would be 1, so that volunteer would not be matched.

I might be wrong about this though and I can add logic to check for the contents of the roles. Also, if the logic for the filtering is too confusing, I can edit the code to make it clearer and add comments?

Copy link
Member

Choose a reason for hiding this comment

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

I think you're right. Let's create tests for these cases to be sure

Comment on lines +34 to +44
if (!isValidOperator(operator)) {
return { status: 400, error: "Operator is not AND or OR" };
}

if (!isAllStrings(filters)) {
return {
status: 400,
error: "Roles to filter by are not all strings",
};
}

Copy link

Copilot AI Nov 23, 2025

Choose a reason for hiding this comment

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

This validation is redundant. The route handler already validates the operator and filters before calling getVolunteersByRoles. Since the function signature requires operator: Operator (which is "OR" | "AND"), and filters: string[], these checks are unnecessary in the API function. Consider removing them or removing the validation from the route handler to avoid duplication.

Suggested change
if (!isValidOperator(operator)) {
return { status: 400, error: "Operator is not AND or OR" };
}
if (!isAllStrings(filters)) {
return {
status: 400,
error: "Roles to filter by are not all strings",
};
}

Copilot uses AI. Check for mistakes.
"name": "trcc",
"version": "0.1.0",
"private": true,
"type": "module",
Copy link

Copilot AI Nov 23, 2025

Choose a reason for hiding this comment

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

Adding "type": "module" to package.json was not mentioned in the PR description and doesn't appear to be related to the filter_by_role feature. This change affects how Node.js interprets JavaScript files throughout the entire project. Please clarify why this change is necessary for this PR, or if it should be removed or submitted as a separate PR.

Suggested change
"type": "module",

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

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

I agree with Copilot here. Is this necessary?

}

const filteredVolunteers = [];
if (operator.toUpperCase() == "OR") {
Copy link

Copilot AI Nov 23, 2025

Choose a reason for hiding this comment

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

Using loose equality (==) instead of strict equality (===). JavaScript best practices recommend using === to avoid type coercion issues.

Suggested change
if (operator.toUpperCase() == "OR") {
if (operator.toUpperCase() === "OR") {

Copilot uses AI. Check for mistakes.
Comment on lines +6 to +7
isAllStrings,
isValidOperator,
Copy link

Copilot AI Nov 23, 2025

Choose a reason for hiding this comment

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

Exporting internal validation helper functions (isAllStrings, isValidOperator) in the public API index is unusual. These are implementation details that consumers of the API module shouldn't need. Consider keeping them as internal utilities or moving them to a separate utilities file if they need to be shared.

Suggested change
isAllStrings,
isValidOperator,

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

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

Agree with Copilot here. We don't need to export isAllStrings and isValidOperator here

Copy link
Contributor

Choose a reason for hiding this comment

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

No need to implement route.ts file.

position: string | null;
pronouns: string | null;
pseudonym: string | null;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the Volunteer type has already been defined in the types.ts, so you can just import it instead of creating a new type:

type Volunteer = Database["public"]["Tables"]["Volunteers"]["Row"];


export async function getVolunteersByRoles(
operator: Operator,
filters: string[]
Copy link
Contributor

Choose a reason for hiding this comment

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

You should also check for the case that filters is an empty array.

Copy link
Author

Choose a reason for hiding this comment

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

In the case that filters is empty, should it return an error or an empty array? Right now, when an empty array is passed, Supabase returns an empty list of rows since there are no filters to match rows by

Copy link
Member

Choose a reason for hiding this comment

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

Returning an empty array should be good

"name": "trcc",
"version": "0.1.0",
"private": true,
"type": "module",
Copy link
Member

Choose a reason for hiding this comment

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

I agree with Copilot here. Is this necessary?


export async function getVolunteersByRoles(
operator: Operator,
filters: string[]
Copy link
Member

Choose a reason for hiding this comment

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

Returning an empty array should be good

}
} else {
for (const volunteer of volunteerRoleMap.values()) {
if (volunteer.roleNames.size == filters.length) {
Copy link
Member

Choose a reason for hiding this comment

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

I think you're right. Let's create tests for these cases to be sure

Comment on lines +6 to +7
isAllStrings,
isValidOperator,
Copy link
Member

Choose a reason for hiding this comment

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

Agree with Copilot here. We don't need to export isAllStrings and isValidOperator here

Copy link
Member

Choose a reason for hiding this comment

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

Good job doing the tests ahead of time! We will be running tests against an instance of the official database, so the test syntax will likely need to change a bit.

This was referenced Dec 4, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend this ticket is part of the backend

Projects

None yet

Development

Successfully merging this pull request may close these issues.

BE 1.5 - Implement filter_by_role API function

4 participants