Add search-filtered fetch method to ContactRepository
epic-contact-list-management-foundation-task-007 — Extend ContactRepository with a fetchBySearch(String query) method that uses ContactRLSQueryBuilder to compose the ILIKE predicate on top of the base role+org scope, executes the filtered Supabase query, and returns a List<Contact>. Ensure PeerMentor records are correctly typed at the mapping layer based on the role field in the response.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 4 - 323 tasks
Can start after Tier 3 completes
Implementation Notes
Add a buildSearchQuery(String query, {String? searchTerm}) method to ContactRLSQueryBuilder that conditionally appends .ilike('name', '%$searchTerm%') only when searchTerm is not null/empty. The repository's fetchBySearch calls buildSearchQuery with the searchTerm parameter. This keeps the ILIKE composition in the query builder (single responsibility).
Consider also searching the notes field with OR ILIKE — check with product owner whether notes search is in scope. Document that a GIN or pg_trgm index on the name column is required for acceptable search performance at scale — create a follow-up DB migration task if not already tracked.
Testing Requirements
Unit tests: (1) fetchBySearch with non-empty query delegates ILIKE to ContactRLSQueryBuilder, (2) fetchBySearch with empty string calls fetchAll path (no ILIKE), (3) fetchBySearch returns PeerMentor instances for matching peer mentor records, (4) fetchBySearch returns empty list for no matches, (5) fetchBySearch with special characters (%, _) does not throw. Integration test: seeded multi-org Supabase instance where fetchBySearch('Ana') as coordinator returns only contacts named 'Ana' within coordinator's org.
Existing Supabase RLS policies for the contacts and peer_mentors tables may not align with the application-level UserRole model, causing ContactRLSQueryBuilder to construct filter expressions that are redundant, conflicting, or that allow over-fetching. In a multi-chapter context (NHF), this could expose contacts belonging to other chapters.
Mitigation & Contingency
Mitigation: Audit and document the existing RLS policies against the UserRole enum before writing a single line of query builder code. Write integration tests asserting cross-organization data isolation using separate test user tokens for each role.
Contingency: If RLS policies are misaligned at runtime, add an explicit application-level organization_id equality check in ContactRepository as a secondary guard while the database policies are corrected in a coordinated migration.
The Supabase schema for contacts and peer_mentors tables may differ from the expected typed models — missing columns, renamed fields, or type mismatches — causing deserialization failures that surface only at runtime during integration testing.
Mitigation & Contingency
Mitigation: Document expected schema fields upfront and validate against the live Supabase schema at sprint start. Use freezed and json_serializable for compile-time-safe deserialization with explicit required/optional field declarations.
Contingency: Introduce nullable fields with safe defaults for any schema mismatches discovered in testing; log deserialization errors to the monitoring service so schema drift is caught before production deployment.