-
Notifications
You must be signed in to change notification settings - Fork 0
Implement filter_by_role API function #49
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
…kend/filter-by-role-api
…teers by role" This reverts commit 6bc18e1.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this 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
getVolunteersByRolesthat queries the VolunteerRoles junction table to filter volunteers by role names - Route handler at
/volunteers/filter_by_rolesupporting 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) { |
Copilot
AI
Nov 23, 2025
There was a problem hiding this comment.
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))) {| if (volunteer.roleNames.size == filters.length) { | |
| if (filters.every(filter => volunteer.roleNames.has(filter))) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
| 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
AI
Nov 23, 2025
There was a problem hiding this comment.
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.
| 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", | |
| }; | |
| } |
| "name": "trcc", | ||
| "version": "0.1.0", | ||
| "private": true, | ||
| "type": "module", |
Copilot
AI
Nov 23, 2025
There was a problem hiding this comment.
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.
| "type": "module", |
There was a problem hiding this comment.
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") { |
Copilot
AI
Nov 23, 2025
There was a problem hiding this comment.
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.
| if (operator.toUpperCase() == "OR") { | |
| if (operator.toUpperCase() === "OR") { |
| isAllStrings, | ||
| isValidOperator, |
Copilot
AI
Nov 23, 2025
There was a problem hiding this comment.
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.
| isAllStrings, | |
| isValidOperator, |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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; | ||
| }; |
There was a problem hiding this comment.
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[] |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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", |
There was a problem hiding this comment.
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[] |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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
| isAllStrings, | ||
| isValidOperator, |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
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:

VolunteerRoles Table

Roles Table

Test 1: Filter for Role 1 with operator

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

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