Implement PostSessionReportRepository CRUD
epic-structured-post-session-report-foundation-task-007 — Implement the PostSessionReportRepository Dart class with full Supabase CRUD: createReport, getReportById, getReportsByActivityId, getReportsByPeerMentor, updateReport, deleteReport, submitReport. Map JSONB field_values to/from typed Map<String, dynamic>. Enforce activity_id linkage on create. Handle RLS-driven permission errors with typed exceptions. Support partial updates for draft saving.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 2 - 518 tasks
Can start after Tier 1 completes
Implementation Notes
For submitReport atomicity, use a conditional Supabase update: .update({'status': 'submitted', 'submitted_at': DateTime.now().toIso8601String()}).eq('report_id', reportId).eq('status', 'draft').select().maybeSingle() — if the result is null, the report was already submitted (or does not exist), throw the appropriate typed exception. The fieldValues JSONB field should use Supabase's built-in JSONB handling — pass the Dart Map directly in the update payload and let the Supabase Dart client serialise it; do not manually call jsonEncode on the map. For partial updates in updateReport, build the update Map from only the provided keys — never merge with existing server data client-side. Align the exception hierarchy with other repositories in the codebase to avoid proliferating inconsistent exception types.
The submitted_at column must exist in the post_session_reports table from task-001 — verify and request a schema addition if absent.
Testing Requirements
Write unit tests using flutter_test with mocked SupabaseClient (mocktail). Cover: (1) createReport returns mapped PostSessionReport with draft status, (2) getReportById returns report on success and null when not found, (3) getReportsByActivityId returns correctly filtered and ordered list, (4) getReportsByPeerMentor returns correctly filtered list, (5) updateReport sends only specified fields in payload (verify mock call arguments), (6) updateReport returns updated model, (7) deleteReport succeeds for draft report, (8) deleteReport throws ReportNotDeletableException for submitted report, (9) submitReport sets status to submitted and throws ReportAlreadySubmittedException on second call, (10) each method throws PostSessionReportRepositoryException on PostgrestException, (11) fromJson/toJson round-trip for PostSessionReport including null fieldValues entries. Write a separate integration test against local Supabase for submitReport atomicity.
Supabase RLS policies for multi-org report access may be more complex than anticipated — coordinators need cross-peer-mentor access within their org but not across orgs, and draft reports should be invisible to coordinators until submitted. Misconfigured RLS could expose sensitive health data or block legitimate access.
Mitigation & Contingency
Mitigation: Define and test RLS policies in isolation before writing repository code. Create a dedicated SQL migration file with policy definitions and an automated integration test suite that verifies each role's access boundaries using real Supabase auth tokens.
Contingency: If RLS proves too complex to express declaratively, implement application-level access control in the repository layer with explicit org and role checks, and add a security audit task before the feature goes to production.
The org field config JSON stored in Supabase may lack a stable, versioned schema contract. If different organisations have drifted to different field-definition formats, org-field-config-loader will fail silently or crash, breaking form rendering for those orgs.
Mitigation & Contingency
Mitigation: Define a canonical JSON Schema for field config and validate all existing org configs against it before implementation begins. Store a schema version field in every config record and handle version migrations explicitly in the loader.
Contingency: If existing configs are too heterogeneous, implement a config normalisation pass in org-field-config-loader that coerces known variants to the canonical format, logging warnings for fields that cannot be normalised so operations can fix them in the admin console.
TTL-based schema cache invalidation may cause peer mentors to use stale field definitions for up to the TTL window after an admin updates the org config, potentially collecting data against outdated field structures.
Mitigation & Contingency
Mitigation: Set a conservative TTL (e.g. 15 minutes) and expose a manual cache-bust mechanism triggered on app foreground-resume. Document the maximum staleness window in the admin console so org admins know to plan config changes outside active reporting windows.
Contingency: If stale schema causes a data quality incident, add a Supabase Realtime subscription to the org config table that invalidates the cache immediately on any config update.