Gestion des utilisateurs (admin)
Notes internes, tags de segmentation, bulk actions, export RGPD admin-initié.
La fiche utilisateur dans /admin/users (clic sur une row) ouvre un
sheet à 5 onglets : Info, Notes, Emails, Historique,
Actions. Cette doc couvre les features admin qui s'y rattachent.
Notes internes
Champ libre admin par user, invisible côté user. Idéal pour le contexte support ("a réclamé un refund le 12/03", "VIP partenaire", etc.).
model UserNote {
id String @id @default(cuid())
userId String
authorId String?
content String
createdAt DateTime @default(now())
}Routes :
GET /api/admin/users/[id]/notes
POST /api/admin/users/[id]/notes { content: string }
DELETE /api/admin/users/[id]/notes/[noteId]Auteur conservé via FK (SetNull si l'admin est supprimé — la note reste
pour la trace). Toute action audit-loggée (user.note_added /
user.note_deleted).
Modération d'avatar
Un modérateur ou admin peut supprimer l'avatar d'un utilisateur directement depuis le sheet de la fiche user (icône ✕ sur la photo de profil).
DELETE /api/admin/users/[id]/avatar- Guard :
requireApiAuth({ moderator: true })— accessible aux MODERATORs et ADMINs - Met
user.image = nullen DB - Audit
user.avatar_removed - Invalide le cache TanStack Query
["users", id]côté client
L'avatar est physiquement supprimé de R2 uniquement si la feature de stockage R2 est activée et que l'image provient du bucket interne.
Timeline utilisateur
La fiche user (onglet Historique) affiche une timeline unifiée qui merge :
- Connexions (
LoginHistory) — device, OS, navigateur, pays/ville - Événements d'audit (
AuditLog) — toutes les actions sensibles - Souscriptions — démarrages et résiliations
- Coupons — redemptions
Comportement
- Acteur : pour les événements d'audit déclenchés par un admin (ex. changement de rôle), le nom de l'admin est affiché dans l'accordéon sous "Par" avec un mini-popup UserEntityCell.
- Auto-exclusion : les entités
Usercorrespondant au profil en cours de consultation sont masquées (ex. "2FA désactivée" sur son propre profil n'affiche pas un accordéon vide pointant sur lui-même). - Pas d'IP : l'adresse IP n'est jamais affichée dans la timeline (ni dans le hint ni dans l'accordéon) pour ne pas surcharger l'UI.
Tags
Tableau de strings (User.tags) éditable depuis la fiche user.
Format : ^[a-z0-9][a-z0-9_-]{0,30}$ (lowercase, max 31 chars).
Stocké trié + dédupé.
Cas d'usage :
- Audience broadcasts (
#beta-testers,#early-access) - Triage support (
#vip,#high-volume) - Segmentation marketing
PATCH /api/admin/users/[id]/tags { tags: string[] }Bulk actions
API POST /api/admin/users/bulk (max 500 users / appel) :
| action | effet |
|---|---|
block | bloque + bump tokenVersion |
unblock | déblocage |
revoke_sessions | bump tokenVersion + delete UserSession |
add_tag | push tag dans tags[] (set-like) |
remove_tag | retire tag |
Garde-fous :
- Self-exclusion (un admin ne peut s'auto-bloquer)
block / unblock / revoke_sessions→where: { role: { not: "ADMIN" } }→ un admin compromis ne peut pas retirer les pouvoirs des autres admins
L'UI multi-select dans la table n'est pas encore wirée — l'API est prête, à brancher quand tu en auras l'usage.
Export RGPD
Bouton Export RGPD (JSON) dans l'onglet Actions de la fiche user.
GET /api/admin/users/[id]/exportRenvoie un JSON avec toutes les données rattachées à l'user :
- Profil (sans password hash)
- Accounts OAuth (sans tokens)
- Sessions, login history, audit logs
- Notifications
- API keys (sans hash)
- Subscription + redemptions de coupons
- Memberships d'organisations
- Notes admin
- Email logs
- Filleuls (referrals)
Limites : MAX_PER_RELATION = 5000 rows par relation pour éviter le
DoS sur des users très actifs. Rate-limit 10/h par admin.
Le filename est sanitisé via regex (pas d'injection header). L'admin qui
a déclenché l'export est loggé en audit (user.gdpr_export).
L'export user-initié (par l'utilisateur lui-même) est dans
/api/user/export — même payload, sans certains champs admin (notes).
Allez plus loin
- Activity log — équivalent user-initié
- Audit log
- Sessions globales — vue admin all-users