feat(admin): dashboard gestione ruoli /admin + endpoint /me (#235 Fase 4) - #242
Conversation
…Fase 4)
Backend:
- GET /me → { subject, email, role } (require_user), così la UI conosce il ruolo
per governare il link admin e la pagina. resolve_role come require_role (dev→admin).
Frontend:
- app/admin/page.tsx: dashboard admin-only (dietro DashboardGate) — elenco utenti
da GET /admin/users, cambio ruolo via PATCH con select (admin/regione/comune/
cittadino), gestione 403 (accesso riservato) e messaggi errore (self-lockout ecc).
- lib/auth.tsx: useAuth espone (fetch best-effort /me; in dev→admin).
- SiteHeader/UserMenu: voce «Amministrazione» solo se role==admin.
Backend 505 passed; tsc + next build (28 pagine, incl. /admin) + lint OK.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cb6361c517
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const res = await apiFetch("/me", { token }); | ||
| if (res.ok) resolvedRole = ((await res.json())?.role as string) ?? null; |
There was a problem hiding this comment.
Avoid blocking auth readiness on /me
When a signed-in user's backend is slow or temporarily unreachable, this awaited role lookup keeps setState({ isLoaded: true, ... }) from running, so every <SignedIn>/<SignedOut> gate (including DashboardGate and the header) stays in its unloaded state until /me settles. Since the role fetch is described as best-effort, load the OIDC profile first and resolve role separately or with a timeout so a degraded admin-role check does not blank authenticated routes.
Useful? React with 👍 / 👎.
| setError(null); | ||
| try { | ||
| const token = await getToken(); | ||
| const res = await apiFetch("/admin/users", { token }); |
There was a problem hiding this comment.
Add pagination for the admin user list
In deployments with more than 100 users, this call only retrieves the backend's first default page (limit defaults to 100 in opendata_backend/routers/admin.py and supports offset), but the new dashboard presents the result as the complete user list with no way to advance pages. Older users therefore cannot be found or have their roles changed from this UI; pass explicit paging params and expose next/previous controls or fetch all pages intentionally.
Useful? React with 👍 / 👎.
Fase 4: la dashboard admin che consuma l'API della Fase 2.
Backend
/me→{ subject, email, role }(gatedrequire_user): la UI conosce il ruolo dell'utente corrente per governare il link e la pagina admin, senza indovinare. Risolve/sincronizza il ruolo comerequire_role(dev-bypass → admin).Frontend
/admin(dietroDashboardGate): elenco utenti daGET /admin/users, cambio ruolo viaPATCH /admin/users/{id}/rolecon select (admin/regione/comune/cittadino). Gestisce 403 (accesso riservato) e i messaggi d'errore del backend (self-lockout, 404, 422).useAuth().role:lib/auth.tsxfa un fetch best-effort di/me(in dev → admin); mai bloccante.UserMenu: voce «🛡️ Amministrazione» mostrata solo serole === "admin".Verifica
Backend 505 passed (+1 test
/me);tscpulito;next buildstatic export 28 pagine (incl./admin);next lintsenza errori.Il backend resta la fonte di verità dell'autorizzazione: la pagina è gated client-side e ogni endpoint
/admin/*richiederequire_admin.Stato #235
✅ Fasi 1, 2, 3, 6, 4. Resta la Fase 5 (schermate registrazione SPID + email-OTP, configurazione Keycloak).
Parte di #235.
🤖 Generated with Claude Code