Build Unit Assignment Repository CRUD
epic-organizational-hierarchy-management-assignment-aggregation-task-002 — Implement the Supabase-backed repository for the junction table linking users to organizational units. Provide create, read, update, delete operations for user-unit assignments, including queries for fetching all units a user belongs to and all users in a given unit. Enforce uniqueness constraints at the repository level.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 1 - 540 tasks
Can start after Tier 0 completes
Implementation Notes
Model the junction table as `user_unit_assignments(id UUID, user_id UUID, unit_id UUID, is_primary BOOL, assigned_at TIMESTAMPTZ)` with a unique constraint on `(user_id, unit_id)`. The primary-unit swap (unset old primary, set new primary) must be atomic — implement this as a Supabase RPC function (PostgreSQL function) rather than two sequential REST calls to avoid race conditions. Map the Supabase `PostgrestException` with code `23505` (unique violation) to `DuplicateAssignmentException` in the repository. Use a `Result
Inject the `HierarchyCacheRepository` into this repository via constructor so that after a write operation, the relevant cache entries can be invalidated without tight coupling.
Testing Requirements
Write unit tests using mocktail to mock the Supabase client. Test each CRUD method: (1) happy path with valid inputs; (2) duplicate assignment returns DuplicateAssignmentException; (3) out-of-scope assignment returns ScopeViolationException; (4) Supabase network error is wrapped in a domain-level error type; (5) primary-unit swap atomically unsets the old primary. Write one integration test against a Supabase test project (or local Supabase via Docker) that verifies the full create→read→update→delete lifecycle. Use setUp/tearDown to clean test data.
Verify that getUnitsForUser returns results in the correct order (primary first).
Recursive aggregation queries across four hierarchy levels (national → region → local) with 1,400 leaf nodes may be too slow for real-time dashboard requests, exceeding the 200ms target and causing spinner timeouts.
Mitigation & Contingency
Mitigation: Implement aggregation as a Supabase RPC using a single recursive CTE rather than multiple round-trip queries. Pre-compute aggregations nightly via a scheduled Edge Function and cache results. For real-time needs, aggregate only the immediate subtree on demand.
Contingency: Surface a 'Refreshing...' indicator and serve stale cached aggregations immediately. Queue an async recalculation and push updated data via Supabase Realtime when ready, avoiding blocking the admin dashboard.
The 5-chapter limit and primary-assignment constraint are NHF-specific. Applying these rules globally may break HLF and Blindeforbundet configurations where different limits apply, requiring per-organization configuration that was not initially scoped.
Mitigation & Contingency
Mitigation: Make the maximum assignment count a configurable value stored in the organization's feature-flag or settings table rather than a hardcoded constant. Design the assignment service to read this limit at runtime per organization.
Contingency: Default the limit to a high value (e.g., 100) for organizations other than NHF, effectively making it non-restrictive, while keeping the enforcement logic intact for when per-org configuration is fully implemented.
The searchable parent dropdown in HierarchyNodeEditor must search across up to 1,400 units efficiently. Client-side filtering of the full hierarchy may be slow; server-side search adds complexity and latency.
Mitigation & Contingency
Mitigation: Use the in-memory hierarchy cache as the search corpus — since the cache already holds the flat unit list, client-side filtering with a debounced input is sufficient and avoids extra Supabase calls. Pre-build a search index on cache load.
Contingency: Cap the dropdown to showing the 50 most recently accessed units by default, with a 'search all' option that triggers a server-side full-text query. This keeps the common case fast while supporting edge cases.