critical priority high complexity backend pending backend specialist Tier 4

Acceptance Criteria

AdminRepository class exists with all four methods: getUsersInScope(), getActivitiesInScope(), getReimbursementsInScope(), getOrganisationsInScope()
Each method calls adminRlsGuard.guardedClient before executing any Supabase query
Each method returns a typed List<T> using domain models defined in task-001 (e.g., List<AdminUser>, List<Activity>)
Each method returns an empty list — not null — when no records exist in scope
Each method propagates Supabase errors as typed AdminRepositoryException with the originating method name and error detail
getUsersInScope() supports optional filtering by role (org_admin, coordinator, peer_mentor)
getActivitiesInScope() supports optional date range filter (startDate, endDate)
getReimbursementsInScope() supports optional status filter (pending, approved, rejected)
getOrganisationsInScope() returns the full subtree of organisations visible to the current admin
All methods are tested against a local Supabase instance and confirmed to respect RLS boundaries
Repository is provided via Riverpod and injectable into BLoC/Cubit layers

Technical Requirements

frameworks
Flutter
Riverpod
BLoC
Supabase
apis
Supabase .from().select().eq().gte().lte() query builder
AdminRlsGuard.guardedClient
Supabase RPC for complex filtered queries if needed
data models
AdminUser
Activity
Reimbursement
Organisation
performance requirements
Each list query must return in under 500ms for up to 1,000 records in scope
Use .select() with explicit column lists — never SELECT * — to minimise payload
Pagination support (limit/offset or cursor) must be designed into method signatures even if not fully implemented in this task
security requirements
No query must bypass AdminRlsGuard — direct supabase client usage without the guard is forbidden in this class
Column-level data (PII such as personnummer) must not be returned unless the admin role explicitly requires it
Repository must not expose raw Supabase error messages containing schema details to UI layers

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

Structure AdminRepository as a plain Dart class (not a Riverpod StateNotifier) provided via Provider. Inject AdminRlsGuard via constructor. Each method should follow the pattern: final client = await _guard.guardedClient; final response = await client.from('table').select('col1,col2,...').execute(); return response.data.map((row) => DomainModel.fromJson(row)).toList(). Define AdminRepositoryException as a typed exception carrying operationName, message, and optional statusCode.

For pagination, add optional AdminQueryPage page parameter (with offset and limit) to each method signature now — even if the UI only uses the first page initially — to avoid breaking changes later. Align column names precisely with the Supabase schema established in task-003.

Testing Requirements

Unit tests (flutter_test with mocked AdminRlsGuard and mocked SupabaseClient): (1) Each method calls guardedClient exactly once per invocation. (2) Each method correctly maps raw Supabase JSON response to typed domain models. (3) Empty database response returns empty list. (4) Supabase error is wrapped in AdminRepositoryException with correct method name.

(5) Optional filters are applied correctly (verify query builder receives correct .eq()/.gte() calls). Integration tests against local Supabase: (6) org_admin user only sees users/activities within their org subtree. (7) super_admin sees cross-org data. (8) Date range filter on getActivitiesInScope returns only records in range.

Component
Admin Data Repository
data high
Epic Risks (3)
high impact medium prob security

Missing RLS policies on one or more tables (e.g., a newly added join table or a Supabase view) could expose cross-org data to org_admin queries, creating a GDPR-reportable data breach.

Mitigation & Contingency

Mitigation: Enumerate all tables and views accessed by admin queries before writing any policy. Create an automated test that attempts a cross-org query for each table from an org_admin JWT and asserts an empty result set.

Contingency: If a gap is discovered post-deployment, immediately disable the affected query surface and deploy a hotfix policy before re-enabling. Log the incident and notify DPO if any cross-org data was returned.

high impact medium prob technical

The recursive CTE for NHF's deeply nested org tree (up to 5 levels, 1,400 local chapters) may exceed the 2-second dashboard load target when resolving large subtrees on every request.

Mitigation & Contingency

Mitigation: Benchmark the recursive CTE against a synthetic NHF-scale dataset during development. Introduce a short-TTL server-side cache for subtree resolution results. Index the parent_id column on the organisations table.

Contingency: If CTE performance remains insufficient, materialise the org subtree as a precomputed closure table updated on org structure changes, and switch the RLS guard to query the closure table instead.

high impact low prob security

Incorrect JWT claim injection in AdminRlsGuard (e.g., wrong claim key name or missing refresh on org switch) could silently apply the wrong org scope, causing org_admin to see a different organisation's data without an explicit error.

Mitigation & Contingency

Mitigation: Write unit tests for the guard that verify the injected claim value against the authenticated user's org_id for every admin route. Add a server-side assertion that the claim matches the user's database record before executing any query.

Contingency: Roll back the guard to a deny-all fallback, invalidate active admin sessions, and re-issue corrected JWTs. Audit query logs to identify any sessions that received incorrect scope.