Implement SupabaseContactChapterAdapter core CRUD
epic-multi-chapter-membership-handling-foundation-task-004 — Build the SupabaseContactChapterAdapter class with methods for inserting a new contact-chapter membership, deleting a membership, fetching all chapters for a given contact ID, and fetching all contacts for a given chapter ID. Use the Supabase Dart client's PostgREST interface. Handle error cases (network failure, constraint violations) and wrap responses using the domain mapper from task-003.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 2 - 518 tasks
Can start after Tier 1 completes
Implementation Notes
Follow the PostgREST Dart client pattern: `supabase.from('contact_chapters').insert(payload).select().single()` for insert-and-return. For delete: `supabase.from('contact_chapters').delete().eq('contact_id', contactId).eq('chapter_id', chapterId)`. Wrap all PostgREST calls in try/catch for `PostgrestException` — inspect `error.code` to distinguish constraint violations (domain errors) from infrastructure errors. Define a private `_handlePostgrestException(PostgrestException e)` method to centralise this logic and keep individual method bodies clean.
For the abstract interface, keep it minimal — only the four public methods — so that test mocks remain easy to implement. Document the 5-membership limit in a code comment referencing the business rule source (NHF requirement from the workshop).
Testing Requirements
Unit tests using a mock `SupabaseClient` (via `mocktail` or `mockito`): (1) `insertMembership` — mock returns a valid JSON row, assert returned `ContactChapter` matches expected values; (2) `insertMembership` — mock throws PostgrestException with code `23514`, assert `ChapterMembershipLimitExceededException` is thrown; (3) `deleteMembership` — mock returns empty response, assert method completes without error; (4) `fetchChaptersForContact` — mock returns list of 3 JSON rows, assert list of 3 `ContactChapter` objects returned; (5) network failure path — mock throws `SocketException`, assert `NetworkException` thrown. Integration tests against local Supabase: seed test data, execute each method, assert correct DB state after each operation, clean up. All tests in `test/data/adapters/supabase_contact_chapter_adapter_test.dart`.
Supabase RLS policies for a junction table that spans organisations are non-trivial. An incorrectly scoped policy could expose chapter affiliations from other organisations to coordinators, constituting a data breach.
Mitigation & Contingency
Mitigation: Draft RLS policies in a staging environment and run an explicit cross-organisation isolation test suite before merging. Use Supabase policy testing tools and peer review all policy definitions.
Contingency: If a policy error reaches review, roll back the migration and apply a corrective patch. Ensure no production data has been exposed by auditing Supabase logs for cross-organisation query results.
The contact_chapters table migration may conflict with existing foreign key structures or require a backfill for contacts already assigned to a single chapter, causing migration failures in production.
Mitigation & Contingency
Mitigation: Write the migration as an additive, non-destructive operation. Backfill existing single-chapter assignments by deriving them from the existing contact records. Test the full migration on a production-sized dataset clone before release.
Contingency: Provide a rollback migration script that removes the new table without touching existing contact records. Coordinate with operations for a maintenance window if a re-run is needed.