Architecture technique de l'immobilier¶
Frontières et données¶
PostgreSQL reste autoritaire. La migration 0037_buildings crée les agences, bâtiments, points,
copropriétaires, contrats, badges, reçus, paramètres et permissions de groupe ciblées. Elle ajoute
les rattachements optionnels aux stockages et garages sans modifier le comportement des instances
autonomes. building_badge est un item système protégé des outils génériques de suppression.
La migration publiée 0037_buildings avait omis le DEFAULT now() SQL sur les colonnes non nulles
created_at/updated_at. 0039_building_timestamps, appliquée après le merge 0038, restaure ce
contrat sur les huit tables portant TimestampMixin. Elle ne réécrit aucune ligne et son downgrade
retire seulement les defaults. Sans ce correctif, le premier INSERT d'agence échoue en 503 avant
que l'ORM puisse relire l'entité.
Le modèle Building stocke un polygone XY de 3 à 32 sommets, min_z/max_z, ses bounds indexées,
son routing bucket, une révision optimiste et un ownership XOR. Les plafonds applicatifs sont 64
portes, 8 points de gestion, 8 garages et 32 stockages par bâtiment.
La validation refuse les sommets dupliqués, surfaces nulles et auto-intersections. Les points de gestion et portes doivent rester dans le prisme. Les reçus conservent l'UUID historique sans clé étrangère vers le bâtiment afin qu'une suppression sûre n'efface ni ne bloque la piste d'audit.
api/app/services/buildings.py porte les invariants. Le routeur
api/app/routers/buildings.py expose deux surfaces :
/internal/v1/buildings/*, protégée par la clé API interne et appelée par FXServer ;/admin/buildings/*, protégée par session Discord, permission et CSRF.
Transactions et concurrence¶
L'achat verrouille l'inventaire personnel puis le bâtiment et les agrégats financiers. Le débit
bancaire réutilise debit_account_no_commit; le cash réutilise debit_item_no_commit. Le badge,
l'ownership, les permissions de groupe et le reçu sont commités dans la même transaction. Le
request_id rend un retry terminé idempotent.
Les transferts et recoveries verrouillent le bâtiment, contrôlent les garages, révoquent les accès et recréent le badge maître dans une transaction unique. Une recovery contenant des véhicules exige un mapping complet vers des garages actifs, externes et compatibles. La suppression de personnage ou groupe est bloquée tant qu'il possède un bâtiment.
Les permissions BLD_<building>_<type>_<resource> sont persistées dans
group_scoped_permissions. Elles rejoignent le catalogue effectif du groupe et suivent
l'héritage des rangs ; seul le rang owner les reçoit implicitement à leur création. Une permission
scoped désactivée ou rattachée à un ancien groupe est filtrée du calcul effectif, même si une ligne
de rang historique porte encore son code.
Sécurité des accès¶
Le client n'envoie jamais une identité ou une position faisant autorité. medusa-buildings dérive
le personnage actif, les rôles Discord, la position et le routing bucket sur FXServer. L'API
revalide la proximité de l'agence, de la porte ou du point de gestion. Une émission de badge cible
uniquement un personnage connecté dans le rayon serveur.
Les codes utilisent le hachage PIN bancaire existant. Redis limite les erreurs par personnage/porte ; si Redis est indisponible, le code échoue fermé, tandis qu'un badge peut toujours être contrôlé en base. L'ouverture recherche un badge actif réellement présent dans l'inventaire personnel courant et vérifie son scope. Les mutations administratives web et en jeu produisent un audit nominatif sans code secret.
Runtime FiveM¶
medusa-buildings dépend de medusa-core, medusa-player-utils, medusa-moderation,
medusa-interactions, medusa-inventory, medusa-groups et medusa-entity-placement. Le serveur
charge le snapshot canonique et le diffuse après commit. Le client recalcule toutes les 1,5 seconde
la cellule proche à streamDistance : seuls les Peds, blips, points, props et portes voisins sont
créés. Les changements de cellule nettoient interactions, blips, props et DoorSystem.
La diffusion monde est volontairement expurgée : elle contient la géométrie et les champs utiles au streaming, jamais l'adresse, le prix ni les owners. Le catalogue complet et la gestion ne sont renvoyés qu'après un contrôle serveur de proximité à l'agence ou au point de gestion.
medusa-entity-placement expose aussi selectEntity(options, callback). Le raycast accepte les
types configurés, active le contour uniquement lors d'un changement de cible, emploie un cylindre
pour les Peds et ne supprime jamais une entité existante à la fin de la sélection.
Le placement d'une agence depuis F10 suit un handoff de focus explicite : medusa-admin masque sa
NUI et appelle SetNuiFocus(false, false) avant de déléguer à medusa-buildings. La touche E
reste ainsi la propriété exclusive du placement. La validation produit un request_id; le serveur
valide et normalise le payload, force le routing bucket depuis la session puis acquitte le client
uniquement après le commit API. adminAgencySaveResult affiche le succès ou l'échec dans le jeu et
ne restaure World Edit qu'après cet acquittement. F8 journalise les étapes received, committed
ou failed, tandis que F9 reçoit le diagnostic API structuré sous le préfixe IMMOBILIER.
La NUI du module démarre avec sa racine hidden, garde html/body transparents et confine ses
surfaces sombres au panneau AR visible. Toute fermeture rend le focus et libère le verrou
d'interaction.
Exploitation¶
ensure medusa-buildings se trouve après ses dépendances et avant medusa-admin. Un arrêt de
ressource nettoie les entités locales. Au prochain snapshot, les échéances de relock dépassées sont
appliquées avant diffusion et les bâtiments inactifs ou sans owner forcent leurs portes verrouillées.