critical priority medium complexity backend pending backend specialist Tier 2

Acceptance Criteria

ProxyActivityRepository is an abstract interface class with a concrete SupabaseProxyActivityRepository implementation
insertProxyActivity inserts a single ProxyActivityRecord row and returns the inserted record with server-generated id and createdAt
insertProxyActivity throws ProxyActivityPermissionException when Supabase returns a 403 (RLS violation)
insertProxyActivity throws ProxyActivityConflictException when Supabase returns a duplicate key error
bulkInsert calls the bulk_register_activities Supabase RPC with the serialised BulkRegistrationRequest payload
bulkInsert returns a List<ProxyActivityRecord> representing all successfully inserted records
bulkInsert throws BulkInsertPartialFailureException containing per-participant error details when the RPC returns partial failures
fetchByAttributedTo returns all ProxyActivityRecord rows where attributed_to = peerId, ordered by date descending
fetchByRegisteredBy returns all ProxyActivityRecord rows where registered_by = coordinatorId, ordered by date descending
All Supabase exceptions are caught and rethrown as typed domain exceptions (not raw PostgrestException)
SupabaseClient is injected via a Riverpod Provider (supabaseClientProvider)
A Riverpod Provider (proxyActivityRepositoryProvider) exposes the repository instance
Unit tests mock SupabaseClient and verify correct table/RPC calls and error mapping

Technical Requirements

frameworks
Flutter
Supabase Flutter SDK
Riverpod
apis
Supabase REST: activities table (INSERT, SELECT with eq filter)
Supabase RPC: bulk_register_activities
Supabase Auth: current user context via SupabaseClient.auth.currentUser
data models
ProxyActivityRecord
BulkRegistrationRequest
BulkParticipant
activities (Supabase table)
performance requirements
fetchByAttributedTo and fetchByRegisteredBy must use indexed columns — confirm activities table has indexes on attributed_to and registered_by
bulkInsert must use the RPC instead of individual INSERTs to minimise round-trips
Avoid select() with no column filter — specify only needed columns to reduce payload size
security requirements
Never pass the Supabase service_role key from the Flutter client — use only the anon key; RLS enforces access control
Validate that the current auth.uid() matches registered_by before calling insert — fail fast on the client to avoid unnecessary RLS rejections
Domain exceptions must not expose raw Supabase error messages to the UI layer — map to user-safe messages in the exception classes

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Define domain exception classes first: ProxyActivityPermissionException, ProxyActivityConflictException, BulkInsertPartialFailureException (with a List field). The repository should catch PostgrestException and inspect the code field: '42501' maps to permission denied, '23505' maps to duplicate/conflict. For the RPC call: `await supabaseClient.rpc('bulk_register_activities', params: request.toJson())`. The RPC response shape must be agreed with task-002 — assume it returns a JSON array of inserted activity rows on success, or an error object with per-participant failure details.

Use the abstract class pattern to enable mocking in tests: `abstract class ProxyActivityRepository { Future insertProxyActivity(...); ... }`. Register with Riverpod: `final proxyActivityRepositoryProvider = Provider((ref) => SupabaseProxyActivityRepository(ref.watch(supabaseClientProvider)));`. Ensure supabaseClientProvider is already defined in the app's core providers file.

Testing Requirements

Write flutter_test unit tests in test/features/proxy_activity/repository/proxy_activity_repository_test.dart using Mockito or mocktail to mock the SupabaseClient (and its .from().insert()/.select()/.rpc() chain). Cover: (1) insertProxyActivity returns mapped ProxyActivityRecord on success, (2) insertProxyActivity throws ProxyActivityPermissionException on 403 PostgrestException, (3) insertProxyActivity throws ProxyActivityConflictException on unique constraint violation, (4) bulkInsert calls the correct RPC name with correct payload, (5) bulkInsert returns mapped list on full success, (6) bulkInsert throws BulkInsertPartialFailureException on RPC partial failure response, (7) fetchByAttributedTo calls .eq('attributed_to', peerId) with correct ordering, (8) fetchByRegisteredBy calls .eq('registered_by', coordinatorId) with correct ordering. Minimum 8 test cases. Integration tests against a local Supabase instance are optional but recommended.

Component
Proxy Activity Repository
data medium
Epic Risks (3)
high impact medium prob technical

The activities table migration adding registered_by and attributed_to columns may conflict with existing RLS policies or FK constraints if the user profile table structure differs from assumptions, blocking all subsequent epics.

Mitigation & Contingency

Mitigation: Review existing activities table schema and RLS policies before writing the migration. Run the migration against a staging database clone first. Write rollback scripts alongside the migration.

Contingency: If migration fails in staging, isolate the conflict with a targeted schema audit, adjust FK references or RLS policy scope, and re-run before touching production.

high impact medium prob security

The RLS policy must filter proxy inserts to the coordinator's chapter scope. If the chapter-scope resolver pattern differs between organisations (multi-chapter coordinators in NHF vs single-chapter in HLF), the policy may be too broad or too restrictive.

Mitigation & Contingency

Mitigation: Design the RLS policy to accept a coordinator's full set of assigned chapter IDs (array) rather than a single chapter_id. Validate the policy against NHF multi-chapter test fixtures during the integration test phase.

Contingency: If the policy is found to be incorrect after deployment, introduce a server-side validation edge function as a safety net while the RLS policy is corrected.

medium impact low prob technical

The bulk_register_activities RPC function may time out or cause lock contention when inserting large participant batches (e.g. 40+ peer mentors in a single group session), degrading the user experience.

Mitigation & Contingency

Mitigation: Benchmark the RPC function with 50-participant batches during development. Use unnest-based bulk insert rather than row-by-row PL/pgSQL loops. Set a reasonable statement_timeout.

Contingency: If performance is insufficient, split the client-side submission into chunks of 20 participants with progress feedback, rather than a single RPC call.