Repository navigation
feat(admin): gérer les utilisateurs et leurs rôles (A-06) - #499
Open
Nascimento95 wants to merge 12 commits into
Open
Nascimento95 wants to merge 12 commits into
Nascimento95 wants to merge 12 commits into
Conversation
…d route unlock (A-06) Add users.plan_source and route_unlocks.source (store or gift). A gift has no store transaction, so route_unlocks.store_transaction_id becomes nullable, required for the store. Additive migration 0017. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…do to an account (A-06) Schemas of the list, the card and the actions, and allowedUserActions, the one place of the rule: the admin changes roles, offers Premium and deletes; a moderator suspends a user. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
GET /admin/users and GET /admin/users/:id, for moderators and admins: search by part of an e-mail or first name or by whole identifier, newest first, with the plan and its origin, the routes unlocked and created, the reports made and received, and the support messages. The ratings are empty until route_ratings exists (#64). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
PATCH /admin/users/:id/role (admin only) and POST /admin/users/:id/suspend and /reactivate with a reason. The rule is allowedUserActions, checked on the locked row: nobody acts on their own account, a moderator suspends a user only. Each write is journaled in the transaction of its change. A suspended account keeps its published routes but its public profile is masked. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
POST /admin/users/:id/gift, admin only: Premium until a future date (plan_source gift, which ends by itself through canAccessRoute) or one published Premium route (route_unlocks, source gift). Refused with a reason in details when the account has a running store plan, already has the route, is its author, or the route is free. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… delete it (A-06) POST /admin/users/:id/certification (the team's choice wins over the nightly computation), GET /admin/users/:id/export (the file of the app, rate limited, journaled as it reads the data of someone else) and DELETE /admin/users/:id with the rule of the app (softDeleteUser). Admin only; an account of the team is demoted to user before it is deleted. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The panel grew with the page, so scrolling a long card scrolled the list away. It now sticks to the top and scrolls inside its own height (max-h-detail-panel, to move to tokens.json). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…status (A-06) The table of Utilisateurs, its badges and texts, how an account is named and its plan read, the search address of the list, and the id of the member of the session. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…(A-06) Utilisateurs for moderators and admins: a search by e-mail, first name or identifier, and the card of the account in the detail panel (plan and its origin, certification, routes unlocked and created, reports, support messages). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… suspension, toasts (A-06) The buttons of the card (only what allowedUserActions gives the role), the window that asks a reason to suspend or reactivate, the texts of every action and of each refusal, and the hook that sends an action and tells its result in a toast. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…d delete from the card (A-06) The windows of the other actions and the container that opens them from the buttons of the card: a role, Premium until a date or one route, the Créateur certifié choice, the export as a JSON file, and the deletion with its confirmation. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Nascimento95
requested review from
IlanAMG and
PaulThiberville
as code owners
October 16, 2026 05:06
This was referenced Oct 16, 2026
Open
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Résultat et problème
L'équipe ne pouvait ni retrouver un compte, ni le suspendre, ni changer un rôle, ni offrir Premium sans passer par la base. A-06 « Utilisateurs » le fait depuis l'admin, avec le droit de chaque rôle vérifié par l'API et chaque écriture dans
admin_actions.account_suspendedpartout, profil public masqué, parcours publiés gardés.allowedUserActions, une seule règle partagée par l'API et l'admin) : admin = tout ; moderator = suspendre un compteuser; personne n'agit sur son propre compte ; un compte d'équipe est rétrogradé enuseravant d'être supprimé.POST /admin/users/:id/certification, citée « existe déjà » par le wiki, n'existait pas : elle est écrite ici.DetailPanelreste en vue quand la liste défile (commitfix(ui)).Limites
route_ratings#64, signalements de lieux #49). La fiche affiche des listes vides, à remplacer à un seul endroit (readUserDetail,apps/api/src/admin/users-read.ts).plan_source = gifttranquille.userseulement ; un éditeur, un modérateur ou un admin se suspend par un admin.Fixes #341
Écrans : A-06
Dépendance : PR empilée sur #479 (#54), elle-même sur #398, #365 et #363 (base
issue/54-reports-and-ratings).Comment vérifier
pnpm install,docker-compose up -d,pnpm --filter @widoo/api db:migrate && pnpm --filter @widoo/api db:seed,pnpm dev.user.user, aucune action sur un éditeur, un modérateur ou un admin.Tests : 16 tests d'API (
admin-users.test.ts) couvrent la matrice des rôles, l'arrêt du Premium offert à sa date, la suspension (403account_suspended, profil masqué), le journal, l'export et la suppression ;allowedUserActionsetGiftBodycôtéshared; affichage et adresses côté admin.pnpm lint && pnpm typecheck && pnpm testverts en local (chaque commit passe lint et types)Formatage mécanique : non
Risque
Niveau : 3 — changement de rôle, suspension, suppression de compte, droit Premium donné à la main, migration.
Taille : tous les commits sous 400 lignes utiles (le plus gros : 360). Le snapshot de la migration (
meta/0017_snapshot.json, 3 278 lignes) est généré par drizzle-kit.Revue OWASP (édition en cours)
usersfermée à l'éditeur ;allowedUserActionsrejoué sur la ligne verrouillée (FOR UPDATE) de chaque écriture, pas seulement dans l'interface ; personne n'agit sur son propre compte ; un moderator ne touche pas à un compte d'équipe. Testé (403 pour chaque couple acteur/cible).admin_actions, dans la transaction du changement.%et_de la recherche échappés ; corps validés (plainText,strictObject, 422).cache-control: no-store, rien d'écrit sur disque.account_suspended). La nouvelle saisie du TOTP avant une action sensible n'existe pas dans le socle (voir plus haut).Retour arrière
Revert de la PR. Migration
0017additive (deux colonnesplan_source/sourceavec défautstore, unDROP NOT NULL) : un retour arrière du code laisse les colonnes sans effet. L'annuler complètement demande de supprimer d'abord les cadeaux (route_unlocksoùsource = 'gift', sans transaction) pour rétablir leNOT NULL. Les rôles, suspensions et cadeaux déjà accordés sont conservés. Une suppression de compte est définitive.🤖 Generated with Claude Code