critical priority high complexity backend pending backend specialist Tier 4

Acceptance Criteria

MentorLocation domain model exists with fields: mentorId (String), latitude (double), longitude (double), displayName (String), consentLevel (String), consentExpiresAt (DateTime?), updatedAt (DateTime)
MentorLocation.fromJson() factory constructor handles all fields including nullable consentExpiresAt
MentorLocationRepository class is defined with a Supabase client dependency injected via constructor
upsertMentorLocation(String mentorId, double lat, double lng, DateTime consentExpiresAt) upserts a row in mentor_locations using the Supabase client upsert() method with onConflict: 'mentor_id'
deleteMentorLocation(String mentorId) deletes the row for the given mentorId and returns void; does not throw if row does not exist
getMentorLocation(String mentorId) returns MentorLocation? (nullable) — returns null if no row found
findMentorsInBounds(LatLngBounds bounds, String orgId) calls the find_mentors_in_bounds RPC function via supabase.rpc() and returns List<MentorLocation>
All methods throw a typed MentorLocationException (not raw PostgrestException) with a human-readable message and the original error attached
A Riverpod Provider<MentorLocationRepository> is registered and injects the Supabase client from supabaseClientProvider
Repository does not hold any mutable state — it is a pure data-access object
All public methods are covered by unit tests using a mocked Supabase client

Technical Requirements

frameworks
Flutter
Riverpod
Supabase PostgreSQL 15
apis
Supabase client .rpc('find_mentors_in_bounds')
Supabase client .from('mentor_locations').upsert()
Supabase client .from('mentor_locations').delete()
Supabase client .from('mentor_locations').select().eq()
data models
assignment
contact
performance requirements
findMentorsInBounds must not block the UI thread — always awaited in an async context
Repository methods must not cache results internally — caching is the responsibility of the BLoC/Notifier layer
upsertMentorLocation should complete in under 500ms on a stable 4G connection
security requirements
consentExpiresAt must be a future DateTime — validate before calling upsert and throw ArgumentError if in the past
mentorId must match the authenticated user's ID for upsert and delete — RLS enforces this server-side, but the repository should also assert auth.currentUser?.id == mentorId before calling upsert to fail fast
Repository uses the authenticated Supabase client only — no service role key
Personal location data (lat, lng) must never be logged — use debug-only log statements guarded by kDebugMode

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

Place file at lib/features/map/data/mentor_location_repository.dart. Define MentorLocationException as: class MentorLocationException implements Exception { final String message; final Object? cause; }. For the RPC call: await supabase.rpc('find_mentors_in_bounds', params: {'min_lat': bounds.south, 'min_lng': bounds.west, 'max_lat': bounds.north, 'max_lng': bounds.east, 'p_organisation_id': orgId}).

Cast the RPC response as List and map each element to MentorLocation.fromJson(). For upsert, the payload should include updated_at: DateTime.now().toIso8601String() so the server timestamp reflects the client update time (or use a database DEFAULT NOW() trigger instead and omit the field). Avoid using .execute() deprecated API — use the modern async pattern. Register the Riverpod provider as: final mentorLocationRepositoryProvider = Provider((ref) => MentorLocationRepository(supabase: ref.watch(supabaseClientProvider))).

Testing Requirements

Unit tests (flutter_test with mockito or mocktail): (1) upsertMentorLocation calls supabase.from().upsert() with correct payload, (2) upsertMentorLocation with past consentExpiresAt throws ArgumentError before any network call, (3) deleteMentorLocation calls .delete().eq() with correct mentorId, (4) getMentorLocation returns null when Supabase returns empty list, (5) getMentorLocation returns MentorLocation when row found, (6) findMentorsInBounds calls rpc() with correct parameters derived from LatLngBounds, (7) all methods wrap PostgrestException in MentorLocationException. Integration test against local Supabase: upsert then getMentorLocation returns the same coordinates.

Component
Mentor Location Repository
data high
Epic Risks (3)
high impact medium prob integration

Supabase's hosted PostGIS extension behaviour may differ from the local emulator for spatial RPC functions, causing bounding-box queries to return incorrect results or fail in production while passing locally.

Mitigation & Contingency

Mitigation: Write integration tests against the Supabase emulator from the start and run the same test suite against a staging Supabase project before merging. Use ST_DWithin and ST_MakeEnvelope in plain SQL first, validate with psql, then wrap as RPC.

Contingency: If PostGIS RPC proves unreliable, fall back to client-side bounding box filtering on a full fetch of consented mentor locations (acceptable for up to ~200 mentors per chapter) until the spatial query is stabilised.

medium impact low prob dependency

OpenStreetMap tile usage may require attribution handling and rate limiting. Switching to Google Maps Flutter plugin mid-implementation would require significant rework of the map-provider-integration abstraction.

Mitigation & Contingency

Mitigation: Define the map-provider-integration abstraction interface before selecting the SDK so that the concrete implementation is swappable. Implement OSM first with correct attribution. Document Google Maps as the alternate with its API key setup steps.

Contingency: If OSM tiles are rejected by stakeholders or tile server limits are hit, activate the Google Maps Flutter plugin implementation behind the same interface without touching any UI or service code.

high impact low prob security

Incorrect RLS configuration could allow a coordinator to query mentor locations from a different organisation, constituting a GDPR data breach.

Mitigation & Contingency

Mitigation: Write dedicated RLS integration tests with two isolated test organisations and assert that cross-organisation queries return zero rows. Include these tests in CI. Have a second developer review all RLS policy SQL before migration is applied.

Contingency: If a cross-organisation data leak is discovered post-deployment, immediately disable the map feature via the organisation feature flag, revoke the affected Supabase RLS policy, and notify the data protection officer per the organisation's GDPR incident response procedure.