feat(flutter): admin parity slice — requests, quarantine, users, invites #33

Merged
bvandeusen merged 262 commits from dev into main 2026-05-08 22:08:00 -04:00
bvandeusen commented 2026-05-08 21:59:07 -04:00 (Migrated from git.fabledsword.com)

Mirrors the operator-actionable subset of the web admin surface to the Flutter client. Closes the admin chunk of Fable umbrella #356 for v1; Integrations + Overview tabs are an explicit carve-out (mobile-low-value, deferred).

What ships

Three new admin surfaces under /admin

  • Requests (/admin/requests) — pending Lidarr requests with Approve / Reject. Approve fires immediately; Reject confirms via dialog. Both optimistic with rollback. Joins user_idusername client-side via adminUsersProvider (server doesn't denormalize the requester username).
  • Quarantine (/admin/quarantine) — ExpansionTile per aggregated track row (collapsed: title + report-count + top reason; expanded: per-user reports). Three-dot menu offers Resolve / Delete file / Delete via Lidarr; both deletes route through a TypedConfirmSheet that requires typing "DELETE".
  • Users (/admin/users) — Users + Invites in one scroll. User rows tap into an edit sheet (admin / auto-approve toggles, admin-supplied password reset, typed-confirm delete). Invites section shows token (monospace, copy button), optional note, redeemed badge; "Generate" opens a small note-input dialog (server hardcodes 24h TTL).

Admin landing + entry points

  • AdminLandingScreen — list of three section cards with at-a-glance counts driven by adminCountsProvider (parallel fan-out to the three list endpoints).
  • Settings → Admin card — visible only to admins.
  • Kebab overflow → "Admin" — sibling entry to Settings, gated on isAdmin. Reach is 2 taps.

Home AppBar simplification

HomeScreen and the other top-level screens (Library, Search, Discover, Playlists, Settings) now adopt a shared MainAppBarActions widget:

  • Three primary icons (Home / Library / Search; current screen suppressed)
  • Kebab overflow with Playlists / Discover / Settings / [Admin if isAdmin]

This drops Home from a crowded 5-icon AppBar and gives every screen a consistent shape.

Routing

buildRouter redirect closure now refuses any /admin/* path when user.isAdmin == false (server RequireAdmin middleware remains the actual authority — this is UX).

Server contract — verified, not assumed

Spent the diagnostic time up-front against internal/api/admin_*.go to lock down JSON shapes. Notable client-affecting bits:

  • GET /admin/users and GET /admin/invites are enveloped ({"users": [...]}, {"invites": [...]}) — not flat lists.
  • PUT /admin/users/{id}/auto-approve body field is auto_approve (different from the auto_approve_requests response field).
  • POST /admin/users/{id}/reset-password takes admin-supplied {password: "..."} and returns 204 — there is no server-generated-password mode, so the UI is a typed-input form.
  • POST /admin/invites takes optional {note: "..."} — server hardcodes 24h TTL, so the UI offers a note input rather than an expiry input.
  • Quarantine rows are aggregated (report_count, reason_counts, nested reports[]) — UI is an ExpansionTile showing the rollup with per-user reports underneath.
  • Requests don't include the requester username — UI joins client-side against adminUsersProvider (free since the Users screen warms it).

Spec + plan in docs/superpowers/specs/2026-05-08-flutter-admin-parity-slice-design.md and docs/superpowers/plans/2026-05-08-flutter-admin-parity-slice.md (gitignored, local-only).

Test plan

CI runs flutter analyze + flutter test + APK build on the flutter-ci runner.

  • CI green
  • Operator: open the new APK, confirm the kebab on /home shows Admin (only when logged in as admin)
  • Operator: navigate /admin → tap each section card → verify counts and actions
  • Operator: register a user via /register, then promote them in /admin/users
  • Operator: generate an invite, copy the token, register a new user with the token

Notes for next slices

  • AdminUsersController build-only landed in slice 2 to feed the Requests-screen username join; mutations layered on in slice 3.
  • Realtime / push updates for the queues, bulk actions, and admin user-creation form remain explicit non-goals (separate tasks).
Mirrors the operator-actionable subset of the web admin surface to the Flutter client. Closes the admin chunk of Fable umbrella **#356** for v1; Integrations + Overview tabs are an explicit carve-out (mobile-low-value, deferred). ## What ships ### Three new admin surfaces under `/admin` - **Requests** (`/admin/requests`) — pending Lidarr requests with Approve / Reject. Approve fires immediately; Reject confirms via dialog. Both optimistic with rollback. Joins `user_id` → `username` client-side via `adminUsersProvider` (server doesn't denormalize the requester username). - **Quarantine** (`/admin/quarantine`) — `ExpansionTile` per aggregated track row (collapsed: title + report-count + top reason; expanded: per-user reports). Three-dot menu offers Resolve / Delete file / Delete via Lidarr; both deletes route through a `TypedConfirmSheet` that requires typing "DELETE". - **Users** (`/admin/users`) — Users + Invites in one scroll. User rows tap into an edit sheet (admin / auto-approve toggles, admin-supplied password reset, typed-confirm delete). Invites section shows token (monospace, copy button), optional note, redeemed badge; "Generate" opens a small note-input dialog (server hardcodes 24h TTL). ### Admin landing + entry points - **`AdminLandingScreen`** — list of three section cards with at-a-glance counts driven by `adminCountsProvider` (parallel fan-out to the three list endpoints). - **Settings → Admin card** — visible only to admins. - **Kebab overflow → "Admin"** — sibling entry to Settings, gated on `isAdmin`. Reach is 2 taps. ### Home AppBar simplification `HomeScreen` and the other top-level screens (Library, Search, Discover, Playlists, Settings) now adopt a shared **`MainAppBarActions`** widget: - Three primary icons (Home / Library / Search; current screen suppressed) - Kebab overflow with Playlists / Discover / Settings / `[Admin if isAdmin]` This drops Home from a crowded 5-icon AppBar and gives every screen a consistent shape. ### Routing `buildRouter` redirect closure now refuses any `/admin/*` path when `user.isAdmin == false` (server `RequireAdmin` middleware remains the actual authority — this is UX). ## Server contract — verified, not assumed Spent the diagnostic time up-front against `internal/api/admin_*.go` to lock down JSON shapes. Notable client-affecting bits: - `GET /admin/users` and `GET /admin/invites` are **enveloped** (`{"users": [...]}`, `{"invites": [...]}`) — not flat lists. - `PUT /admin/users/{id}/auto-approve` body field is `auto_approve` (different from the `auto_approve_requests` response field). - `POST /admin/users/{id}/reset-password` takes admin-supplied `{password: "..."}` and returns 204 — there is no server-generated-password mode, so the UI is a typed-input form. - `POST /admin/invites` takes optional `{note: "..."}` — server hardcodes 24h TTL, so the UI offers a note input rather than an expiry input. - Quarantine rows are aggregated (`report_count`, `reason_counts`, nested `reports[]`) — UI is an `ExpansionTile` showing the rollup with per-user reports underneath. - Requests don't include the requester username — UI joins client-side against `adminUsersProvider` (free since the Users screen warms it). Spec + plan in `docs/superpowers/specs/2026-05-08-flutter-admin-parity-slice-design.md` and `docs/superpowers/plans/2026-05-08-flutter-admin-parity-slice.md` (gitignored, local-only). ## Test plan CI runs `flutter analyze` + `flutter test` + APK build on the `flutter-ci` runner. - [ ] CI green - [ ] Operator: open the new APK, confirm the kebab on `/home` shows Admin (only when logged in as admin) - [ ] Operator: navigate `/admin` → tap each section card → verify counts and actions - [ ] Operator: register a user via `/register`, then promote them in `/admin/users` - [ ] Operator: generate an invite, copy the token, register a new user with the token ## Notes for next slices - `AdminUsersController` build-only landed in slice 2 to feed the Requests-screen username join; mutations layered on in slice 3. - Realtime / push updates for the queues, bulk actions, and admin user-creation form remain explicit non-goals (separate tasks).
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: bvandeusen/minstrel#33