Aller au contenu principal

Gestion admin (contrat gelé)

Deux lanes à fichiers disjoints : admin-server (DB + API + realtime) et admin-ui (panel client). Les messages protocole (AdminKick/Kicked) sont déjà dans shared/src/protocol.ts — ne pas les modifier.

Rôles & ban

  • Migration server/migrations/003_admin.sql : ALTER TABLE accounts ADD COLUMN is_admin boolean NOT NULL DEFAULT false; ALTER TABLE accounts ADD COLUMN banned_at timestamptz;
  • role = 'admin' si accounts.is_admin ou username ∈ PLAZA_ADMINS (bootstrap env conservé). Recalculé à register/login/authenticate.
  • Ban : login d'un compte banni → DbError 403 'account banned' (ajouter 403 aux statuts de DbError) ; authenticatenull (coupe les sessions actives) ; le join Colyseus avec token d'un banni est rejeté.

Db (ajouts à l'interface Db)

listAccounts(search: string, limit: number, offset: number): Promise<AdminAccount[]>;
setBanned(accountId: number, banned: boolean): Promise<void>;
setAdmin(accountId: number, isAdmin: boolean): Promise<void>;
grantPixels(characterId: number, delta: number): Promise<{ pixels: number }>; // solde jamais < 0
listAllRooms(): Promise<AdminRoom[]>;
createSystemRoom(slug: string, name: string, size: number): Promise<StoredRoom>; // slug [a-z0-9-]{3,24}, size 6..24
adminDeleteRoom(roomId: string): Promise<void>; // 'lobby' interdit (400)
interface AdminAccount {
id: number;
username: string;
isAdmin: boolean;
banned: boolean;
characters: { id: number; name: string; pixels: number }[];
}
interface AdminRoom {
id: string; // slug système ou 'r<id>'
name: string;
isSystem: boolean;
owner: string | null; // username du propriétaire
}

Les system rooms créées via createSystemRoom : owner_account_id NULL, is_system true, définition = template plancher vide (réutiliser templateDefinition), id ligne DB mais adressée par slug (nouvelle colonne déjà existante slug — vérifier le schéma 001 et s'y conformer).

API /api/admin/*

Middleware : requireAuth + account.role === 'admin' sinon 403.

MéthodeRouteCorpsRéponse
GET/api/admin/players?search=&limit=50&offset=0{ players: AdminAccount[] }
POST/api/admin/players/:id/ban{ banned: boolean }204
POST/api/admin/players/:id/role{ admin: boolean }204
POST/api/admin/characters/:id/pixels{ delta: number }{ pixels }
GET/api/admin/rooms{ rooms: AdminRoom[] }
POST/api/admin/rooms{ slug, name, size }StoredRoom
DELETE/api/admin/rooms/:id204

Un admin ne peut ni se bannir ni se retirer son propre rôle (400).

Realtime (PlazaRoom)

  • MESSAGE.AdminKick { sessionId } : émetteur admin sinon ignoré ; la cible reçoit MESSAGE.Kicked { reason: 'kicked by admin' } puis force-leave.
  • Ban au join : token authentifié mais compte banni → reject.

Client (lane admin-ui)

  • client/src/ui/admin.ts + adminStyles.ts : panel 🛡️ (visible seulement si role === 'admin' via /api/me), deux onglets :
    • Joueurs : recherche, liste (username, persos + pixels, badge banni), actions ban/déban, +/- pixels par perso, promouvoir/rétrograder, kick des joueurs présents dans la room courante.
    • Mondes : liste système + joueurs (owner), créer un monde système (slug/nom/taille), supprimer une room, « Y aller » (join → éditer in-game).
  • client/src/network.ts : fetchs admin + sendKick(sessionId) + onKicked (toast + retour lobby).
  • Bouton HUD dans hud.ts, même style que les panels existants (PanelDeck).