critical priority high complexity backend pending backend specialist Tier 2

Acceptance Criteria

UserManagementService.getUsers(nodeId, {RoleFilter?, StatusFilter?}) returns a paginated List<AdminUserView> scoped to the org subtree
getUserById(userId) returns the full AdminUserView with role history and current certification status
createUser(CreateUserRequest) validates all fields, creates the user in Supabase Auth and the users table atomically, returns the created AdminUserView
updateUser(userId, UpdateUserRequest) applies partial updates, re-validates role eligibility on any role change, returns updated AdminUserView
deleteUser(userId) performs a soft delete (sets status=deleted, anonymises PII fields) — hard delete is not permitted
Role transition peer_mentor → coordinator requires: active status, at least 1 completed activity in the last 12 months, and no pending reimbursements
Role transition coordinator → org_admin requires explicit approval flag in the request (no auto-promotion)
Attempting an invalid role transition returns a RoleTransitionException with a List<String> reasons explaining each failed condition
A user cannot hold status=active and status=paused simultaneously (enforced at service layer, not just DB constraint)
A paused peer mentor cannot be assigned new contacts — service returns UserPausedException if this is attempted
All mutating operations are scoped to the admin's own org subtree — attempting to modify a user outside the admin's scope throws InsufficientScopeException
All validation errors are typed (not generic Exception) and include a user-facing message in English suitable for display in the admin UI
Unit tests cover: all CRUD paths, each role transition rule (pass and fail), soft delete, scope enforcement, paused-mentor guard

Technical Requirements

frameworks
Flutter
Riverpod
supabase_flutter
apis
Supabase Auth Admin API: createUser, deleteUser (via Edge Function — client cannot call Admin API directly)
Supabase PostgREST: users table (CRUD)
Supabase PostgREST: activities table (read — for role transition eligibility check)
Supabase PostgREST: reimbursements table (read — for pending reimbursement check)
Supabase Edge Function: create-admin-user (wraps Supabase Auth Admin API server-side)
data models
AdminUserView (id, email, fullName, role, status, orgId, certificationStatus, createdAt, lastActivityAt)
CreateUserRequest (email, fullName, role, orgId)
UpdateUserRequest (fullName?, role?, status?, orgId?)
RoleTransitionException (targetRole, reasons: List<String>)
InsufficientScopeException (requestedOrgId, adminScopeNodeId)
UserPausedException (userId, pausedSince)
performance requirements
getUsers() for 1000-user subtree with pagination (page size 50): < 2s
Role transition eligibility check (3 Supabase queries): < 1.5s
security requirements
User creation must go through a Supabase Edge Function — the Supabase Auth Admin API key must never be present in the Flutter client bundle
Scope enforcement (InsufficientScopeException) must be validated server-side via RLS as defence-in-depth, not only client-side
PII fields (email, fullName) must be anonymised on soft delete: set to 'deleted_<userId>@deleted.invalid' and 'Deleted User'
Role escalation to org_admin requires the explicit approval flag to prevent accidental promotion via API misuse
ui components
UserManagementBlocProvider (Riverpod provider wrapping UserManagementService)
RoleTransitionDialog (displays validation error reasons from RoleTransitionException)

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

User creation flow: call the Supabase Edge Function (create-admin-user) which calls supabaseAdmin.auth.admin.createUser() server-side, then inserts the users table row in the same Edge Function to ensure atomicity. Use Supabase's .upsert() with onConflict for idempotent retries. Role transition eligibility check: run all 3 condition queries in parallel (Future.wait), collect failures, throw RoleTransitionException if any fail — do not short-circuit on first failure so the admin sees all reasons at once. Soft delete: use a Supabase RPC function (soft_delete_user) to atomically anonymise PII and set status in a single DB round-trip, avoiding race conditions.

Pagination: use Supabase's .range(from, to) with a consistent .order('full_name') to ensure stable pagination. Expose the service via a Riverpod StateNotifierProvider that holds pagination state (current page, total count, filter state) so the UI can implement infinite scroll.

Testing Requirements

Unit tests (flutter_test) with mocked Supabase client and mocked Edge Function caller. Test each role transition rule independently with a factory that constructs users in specific states (active/paused, with/without recent activities, with/without pending reimbursements). Test soft delete: verify PII fields are overwritten and status is set to deleted. Test scope enforcement: mock OrgHierarchyService to return a subtree that excludes the target user's org, assert InsufficientScopeException is thrown.

Integration tests: full create→read→update→soft-delete cycle against a Supabase test project. Test the Edge Function integration with a real test deployment.

Component
User Management Service
service high
Epic Risks (4)
medium impact high prob technical

OrgHierarchyNavigator rendering NHF's full 1,400-chapter tree in a single widget may cause Flutter frame-rate drops below 60 fps on mid-range devices, making the navigator unusable for NHF national admins.

Mitigation & Contingency

Mitigation: Implement lazy expansion: only load immediate children on node expand rather than the full tree upfront. Use virtual scrolling for long sibling lists. Test with a synthetic 1,400-node dataset on a low-end Android device during development.

Contingency: If lazy expansion is insufficient, replace the tree widget with a paginated drill-down navigator (select level → select child) that avoids rendering more than 50 nodes at a time.

medium impact medium prob dependency

Bufdir may update their required export column structure or file format during or after development. If the AdminExportService hardcodes the current Bufdir schema, any format change requires a code release rather than a config update.

Mitigation & Contingency

Mitigation: Drive the Bufdir column mapping from a configuration repository rather than hardcoded constants. Abstract column definitions into a named schema config so that format changes require only a config update and re-deployment without service logic changes.

Contingency: If Bufdir format changes post-launch, release a config update within one sprint. If the change is structural (new required sections), scope a targeted service update and communicate timeline to partner organisations.

high impact medium prob integration

Role transition side-effects in UserManagementService (e.g., certification expiry removing mentor from chapter listing, pause triggering coordinator notification) may interact with external services like HLF's website sync. Incomplete side-effect handling could leave the system in an inconsistent state.

Mitigation & Contingency

Mitigation: Model side-effects as explicit domain events published after the primary state change is persisted. Implement event handlers as idempotent operations so re-processing is safe. Write integration tests that assert all side-effects fire correctly for each role transition type.

Contingency: If a side-effect fails after the primary change is persisted, log the failure with full context and trigger a manual reconciliation alert to the on-call team. Provide an admin-accessible re-trigger action for failed side-effects.

medium impact medium prob scope

If AdminStatisticsService cache TTL is set too long, org_admin may see significantly stale KPI values (e.g., a mentor newly paused an hour ago still appears as active), undermining trust in the dashboard.

Mitigation & Contingency

Mitigation: Default cache TTL to 5 minutes with a manual refresh action on the dashboard. Implement cache invalidation triggered by UserManagementService write operations that affect counted entities.

Contingency: If staleness causes org admin complaints post-launch, reduce TTL to 60 seconds and introduce a real-time Supabase subscription for high-impact counters (paused mentors, expiring certifications).