high priority medium complexity backend pending backend specialist Tier 4

Acceptance Criteria

fetchBySearch(String query) method added to IContactRepository interface and ContactRepository implementation
When query is empty string or whitespace-only, fetchBySearch behaves identically to fetchAll() — no ILIKE predicate added
ILIKE predicate is applied to the name column (at minimum) and is case-insensitive; search term is percent-wrapped (%query%) by the query builder, not by the repository
The search respects the same RLS/org/role scope as fetchAll() — ILIKE is layered on top, not instead of, the base filters
PeerMentor records in filtered results are still correctly typed as PeerMentor instances
fetchBySearch with a query that matches zero records returns an empty list without error
Special characters in the query string (%, _, apostrophe) are safely passed to Supabase without SQL injection risk — Supabase client parameterizes values automatically
Method is debounce-friendly: calling fetchBySearch rapidly does not open multiple concurrent connections (debouncing is the BLoC/UI responsibility, but the method must be idempotent and cancellation-safe)

Technical Requirements

frameworks
Flutter
Dart
Riverpod
apis
Supabase PostgREST ILIKE filter
data models
Contact
PeerMentor
ContactRLSQueryBuilder
performance requirements
Search query must be server-side (PostgREST ILIKE), not client-side list filtering, to support large contact lists
Response time under 500ms for typical org-scoped search on a table with up to 10,000 contacts (requires database index on name column — flag as DB migration requirement)
security requirements
Use Supabase parameterized query API to pass search string — never string-interpolate user input into the query
Log search queries only at verbose/debug level and never in production builds

Execution Context

Execution Tier
Tier 4

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.

Component
Contact Repository
data medium
Epic Risks (2)
high impact medium prob security

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.

medium impact low prob integration

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.