Skip to content

feat: scoped user-management list view for admins and domain managers #85

Description

@martinydeAI

Resume

Description

Build the admin user-management surface decided in ADR 006: one
controller serving a list view that's scoped by role — ROLE_ADMIN
sees every user, ROLE_DOMAIN_MANAGER sees only users whose email
domain matches their own. The approval queue (#64) is one filter on
top of this view; this issue covers the broader listing and the
controller / service plumbing that #64 plugs into.

Tasks

  • Route /admin/users (Twig list) — IsGranted('ROLE_DOMAIN_MANAGER')
    on the controller, then the voter / repository scoping handles
    the per-domain visibility.
  • App\Controller\Admin\UserController::list() — thin per
    project conventions:
    • Inject UserManager (or similar) service.
    • Call userManager->listVisibleTo($currentUser, $statusFilter).
    • Render templates/admin/user/list.html.twig.
  • App\Service\UserManager (the persistent home for user-mutation
    logic — extend if it exists, else create), fully documented per
    project conventions:
    • listVisibleTo(User $actor, ?UserStatus $statusFilter = null): array.
    • Internally calls a scoped repository method.
  • UserRepository gains a scoped finder:
    • findVisibleTo(User $actor, ?UserStatus $statusFilter = null): array.
    • Admin → no email-domain filter.
    • Domain manager → WHERE email LIKE '%@' || :domain
      (parameterised properly, no string interpolation).
    • Optional status filter for the approval-queue view in feat: approval queue for domain managers #64.
  • Twig template: list with name, email, status badge, and action
    links (Approve / Block) that point at the existing feat: approval queue for domain managers #64 actions.
    Re-use existing Twig components (templates/components/Layout/,
    Form/, etc.) — do not invent new card / badge primitives if
    one is already in templates/components/.
  • Tests:
    • Functional: admin can see users across multiple email domains.
    • Functional: domain manager sees only same-domain users.
    • Functional: a regular user (no ROLE_DOMAIN_MANAGER) gets 403.
    • Unit: UserRepository::findVisibleTo scopes correctly for both
      actor types and respects the optional status filter.
  • Translations for any new strings under
    translations/messages.da.yaml.
  • CHANGELOG.md under ## [Unreleased] / Added.

Details - AI specificities

  • Why: ADR 006 specifies one controller for user management with
    role-scoped queries — admin sees everyone, domain manager sees own
    domain. The ADR's "Domain manager — a role, not a flag" section is
    the spec.
  • Relationship to feat: approval queue for domain managers #64: feat: approval queue for domain managers #64 is the approval-queue subset
    (/admin/users/pending). Two reasonable shapes:
    1. (Preferred) This issue ships /admin/users with an optional
      ?status=pending query and the action buttons. feat: approval queue for domain managers #64's
      /admin/users/pending becomes a redirect / pre-filtered view of
      the same controller. Land this first.
    2. Ship /admin/users after feat: approval queue for domain managers #64 lands and refactor feat: approval queue for domain managers #64's
      controller to delegate to this one's service.
      Pick whichever sequencing is easier when the work is picked up —
      both end up at the same shape.
  • Authorisation: the voter from the sibling roles issue
    (ROLE_DOMAIN_MANAGER + same-domain check) is what powers the
    per-row "may I act on this user" decisions. The list query uses
    the same domain-scoping logic but at the repository level so the
    page doesn't have to render rows the voter would just deny.
  • Out of scope:
    • Bulk actions, sortable / searchable lists, pagination beyond the
      framework default. Track separately if the list grows.
    • Editing a user's role(s) from this screen. ROLE_DOMAIN_MANAGER
      promotion is a separate UX decision.
    • Self-service profile editing — that's User profile and account management #13.
  • Coordinate with feat: approval queue for domain managers #64 sequencing. The status transitions (Approve
    / Block) live in feat: approval queue for domain managers #64's UserApproval service (or whatever it
    becomes). This issue's UserManager::listVisibleTo() is read-only
    scoping; mutations stay where they are.
  • Files touched:
    • src/Controller/Admin/UserController.php (new or extended)
    • src/Service/UserManager.php (new or extended)
    • src/Repository/UserRepository.php
    • config/routes.yaml (if attribute routing isn't already in use
      for admin)
    • templates/admin/user/list.html.twig (new)
    • translations/messages.da.yaml
    • tests/Functional/Admin/UserListTest.php (new)
    • tests/Unit/Repository/UserRepositoryTest.php (extended) — note:
      a domain-scoping query is repository-level integration territory,
      so the actual test belongs under tests/Integration/Repository/.
    • CHANGELOG.md
  • Acceptance verification: task coding-standards-check and
    task test-coverage (100% gate) both green; manual smoke test
    by logging in as the domainmanager@aarhus.dk fixture user
    (whenever the fixture lands) and verifying the visible list.

Related

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request
No fields configured for Feature.

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions