high priority low complexity infrastructure pending backend specialist Tier 2

Acceptance Criteria

LocationPrivacyConfig Dart class exists with fields: consentExpiryDays (int), retentionDays (int), privacyPolicyUrl (String), locationPrecision (LocationPrecision enum with values approximate and precise)
LocationPrecision enum is defined with approximate and precise values
LocationPrivacyConfig.fromJson() factory constructor correctly deserialises all fields from the Supabase JSONB column
LocationPrivacyConfig.toJson() serialises back to the expected JSONB structure
A Riverpod AsyncNotifierProvider<LocationPrivacyConfig> (or FutureProvider) is defined and fetches config for the current user's organisation_id from Supabase on first access
Provider caches the result and does not re-fetch on every widget rebuild
Provider returns a sensible default LocationPrivacyConfig (consentExpiryDays: 365, retentionDays: 730, locationPrecision: approximate) when the database row has no location_privacy_settings column or the column is null
Provider exposes a refresh method to force re-fetch (e.g. after admin config update)
All fields are immutable (final) and the class supports copyWith
No sensitive data (API keys, credentials) is embedded in the Dart model

Technical Requirements

frameworks
Flutter
Riverpod
apis
Supabase PostgreSQL 15 (organisation_settings or organisations table select)
data models
accessibility_preferences
performance requirements
Config is fetched once per app session and cached in Riverpod state — no repeated network calls per widget build
Fallback to default values completes synchronously (no async delay) when database returns null
security requirements
privacy_policy_url must be validated as a well-formed HTTPS URL before use in WebView or external link — reject non-https URLs
Organisation config is read-only for peer mentors and coordinators — only org admins may write (enforced by RLS on the database side)
Config fetch uses the authenticated Supabase client with the user's JWT — no service role key on client

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Place the file at lib/features/map/data/location_privacy_config.dart (or lib/core/privacy/location_privacy_config.dart if shared across features). Use freezed or manual immutable class with copyWith — freezed is preferred if already in the project. The Riverpod provider should use ref.watch(supabaseClientProvider) to access the Supabase client and ref.watch(currentOrganisationIdProvider) to scope the query. Fetch query: supabase.from('organisations').select('location_privacy_settings').eq('id', orgId).single().

Parse the JSONB field as Map. Guard against the column not existing yet (pre-migration) by catching PostgrestException and returning defaults — this allows the Dart code to be deployed before the migration.

Testing Requirements

Unit tests (flutter_test): (1) LocationPrivacyConfig.fromJson() with full valid JSON, (2) fromJson() with missing optional fields falls back to defaults, (3) fromJson() with null input returns default config, (4) toJson() round-trips correctly, (5) LocationPrecision enum parsing from string. Widget/integration test: mock Supabase client returning a config row and assert the Riverpod provider resolves to the correct LocationPrivacyConfig. Test the default fallback by mocking a null response. Verify provider does not trigger unnecessary rebuilds using ProviderObserver.

Component
Location Privacy Configuration
infrastructure low
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.