BlockLucky Documentation officielle GitHub

Dictionnaire de données

Cette page décrit chaque table, chaque colonne et chaque énumération de la base PostgreSQL du backend. Les tableaux ne sont pas écrits à la main : ils sont générés à chaque construction de la documentation à partir de backend/prisma/schema.prisma et des fichiers SQL de backend/prisma/migrations/. Une colonne ajoutée au schéma apparaît donc ici sans intervention ; seule sa description doit être rédigée.

La vue d'ensemble des relations, avec le diagramme entité-association, est sur la page Modèle de données. La justification de chaque index et de chaque contrainte est sur la page Index et contraintes.

Mode de lecture

Chaque modèle Prisma fait l'objet d'une section. Le nom du modèle est aussi le nom de la table PostgreSQL, entre guillemets doubles dans le SQL car il contient des majuscules : SELECT * FROM "User";. De même pour les colonnes : "walletAddress".

Les colonnes du tableau de chaque modèle se lisent ainsi :

Colonne Signification
Champ Nom du champ dans le schéma Prisma, identique au nom de la colonne SQL
Type Type Prisma : String (texte), Int (entier 32 bits), Float (flottant double précision), Boolean, DateTime, Json (JSONB), un nom d'énumération, ou relation vers … pour un champ de relation. Le suffixe [] désigne un tableau PostgreSQL
Obligatoire oui si la colonne est NOT NULL ; non pour un champ facultatif (? dans le schéma), un tableau ou une relation multiple
Défaut Valeur par défaut du schéma : uuid(7) (UUID version 7 généré par Prisma), uuid() (UUID version 4), now() (date d'insertion), une constante ou une valeur d'énumération
Contraintes Clé primaire, unicité, mise à jour automatique (@updatedAt), type PostgreSQL précis (Timestamptz(3)), clé étrangère avec son comportement à la suppression
Rôle Description fonctionnelle

Champs de relation

Les lignes de type relation vers … ne correspondent à aucune colonne en base. Ce sont les champs de navigation du client Prisma : ticket.user, user.tickets. La colonne réelle est la clé étrangère qui les accompagne (userId, lotteryId…), signalée dans la colonne Contraintes par « clé étrangère » et le comportement ON DELETE quand il est précisé dans le schéma. Quand il ne l'est pas, Prisma applique Restrict pour une relation obligatoire et SetNull pour une relation facultative.

Index et contraintes de la table

Sous chaque tableau, une liste reprend les attributs de bloc du schéma (@@index, @@unique) et les contraintes CHECK trouvées dans les migrations. Les CHECK ne sont pas exprimables dans le schéma Prisma : elles n'existent que dans le SQL, et la génération va les y chercher.

Exemple de lecture

Dans la section Ticket, la ligne ticketIndex indique un Int non obligatoire. Avec la ligne d'unicité @@unique([lotteryId, ticketIndex]), cela se lit : en mode onchain, un numéro de ticket ne peut apparaître qu'une fois par tour ; en mode simulé, la colonne reste vide, et PostgreSQL considère deux valeurs NULL comme distinctes, si bien que les tickets simulés ne se gênent pas entre eux.

Conventions transverses

  • Identifiants : tous en String. Les UUID v7 (uuid(7)) sont triables par date de création. User.referralCode utilise un UUID v4 (uuid()), non prévisible. ChainCursor.id est fixé par le code.
  • Dates : DateTime est stocké en TIMESTAMPTZ(3), au fuseau UTC, avec une précision à la milliseconde.
  • Montants : les montants en wei sont en String pour rester exacts ; prizePool est en ETH flottant, pour l'affichage.
  • Adresses : stockées en minuscules.
  • Données sensibles : walletEncryptedKey et twoFactorSecret sont chiffrées par l'application (AES-256-GCM) ; Token.token et twoFactorBackupCodes sont des empreintes SHA-256. Aucune colonne ne contient de secret en clair. Voir Cryptographie et secrets.
  • Suppression : les comptes ne sont jamais supprimés physiquement par l'application ; ils sont anonymisés et deletedAt est renseigné.

Mise à jour de cette page

Les descriptions viennent du fichier documentation-officielle/content/_data/modele-donnees.json, organisé ainsi :

JSON
{
  "modeles": { "User": { "description": "…", "champs": { "id": "…" } } },
  "enumerations": { "Role": { "description": "…", "valeurs": { "USER": "…" } } }
}

Après une modification de schema.prisma, il faut ajouter dans ce fichier la description de chaque nouveau champ, modèle, énumération ou valeur. La construction de la documentation signale chaque description manquante, et le job CI documentation échoue tant qu'il en reste.

Tables et colonnes

User

Compte d'un citoyen, d'un commerçant partenaire ou d'un agent de la Mairie. Porte le portefeuille Ethereum intégré, le mot de passe facultatif, le statut de vérification d'âge, le solde d'EtherPoints, la double authentification et le parrainage. Un compte peut aussi être créé sans email par l'indexeur pour une adresse inconnue qui a acheté sur le contrat. La suppression RGPD anonymise la ligne au lieu de la supprimer.

Champ Type Obligatoire Défaut Contraintes Rôle
id String oui uuid(7) clé primaire Identifiant interne UUID v7, ordonné dans le temps.
walletAddress String oui - unique Adresse Ethereum du compte, en minuscules. Adresse du portefeuille intégré pour un compte custodial ; valeur 0xdeleted_gdpr_… après effacement RGPD ; adresses non valides pour les comptes du seed.
walletEncryptedKey String non - - Clé privée du portefeuille intégré chiffrée en AES-256-GCM au format v2:iv:étiquette:données. NULL pour un compte sans portefeuille intégré ou effacé.
welcomeGrantTxHash String non - - Hachage de la transaction de dotation de bienvenue en ethers fictifs. NULL tant que la dotation n'est pas versée ; sert de garde contre une seconde dotation.
email String non - unique Adresse email en minuscules, identifiant de connexion. NULL pour un compte créé par l'indexeur ou effacé.
passwordHash String non - - Empreinte bcrypt (coût 12) du mot de passe. NULL pour un compte sans mot de passe (connexion par code email, Google ou Apple) et après effacement RGPD.
firstName String non - - Prénom saisi par le citoyen dans l'espace Paramètres : 50 caractères au plus, lettres (accents compris), espaces, traits d'union et apostrophes. NULL tant qu'il n'est pas renseigné, après effacement par une chaîne vide et après effacement RGPD.
lastName String non - - Nom de famille saisi par le citoyen dans l'espace Paramètres, avec les mêmes règles que firstName. NULL tant qu'il n'est pas renseigné, après effacement par une chaîne vide et après effacement RGPD.
role énumération Role oui USER - Rôle d'autorisation : citoyen, commerçant partenaire ou administrateur de la Mairie.
isTwoFactorEnabled Boolean oui false - Vrai quand la double authentification est activée et vérifiée.
twoFactorMethod énumération TwoFactorMethod non - - Méthode de double authentification choisie : application TOTP ou code par email.
twoFactorSecret String non - - Secret TOTP chiffré par encryptText. NULL sans méthode TOTP.
twoFactorBackupCodes String[] non [] - Empreintes SHA-256 des codes de secours de la double authentification. Un code utilisé est retiré du tableau.
isEmailVerified Boolean oui false - Vrai quand l'adresse email a été confirmée par un code reçu.
ageVerificationStatus énumération AgeVerificationStatus oui NONE - Statut de la vérification d'âge par la Mairie. Seul VERIFIED ouvre l'achat et le cadeau de tickets et l'échange de récompenses quand AGE_VERIFICATION_REQUIRED est actif.
ageVerifiedAt DateTime non - PostgreSQL Timestamptz(3) Date de la validation de la pièce d'identité. NULL tant que l'âge n'est pas vérifié, et remis à NULL à chaque nouvel envoi ou refus.
ageVerificationNote String non - - Motif du dernier refus, envoyé au citoyen par email. NULL sans refus, après une validation et après effacement RGPD.
pointsBalance Int oui 0 - Solde courant d'EtherPoints, tenu à jour avec chaque ligne de PointTransaction. Ne peut pas devenir négatif (contrainte CHECK).
referralCode String oui uuid() unique Code de parrainage unique du compte, partagé dans le lien d'invitation.
referredById String non - - Identifiant du parrain, NULL sans parrain.
referredBy relation vers User non - clé étrangère referredById Relation vers le compte parrain.
referrals relation vers User (plusieurs) non - - Comptes parrainés par ce compte (relation inverse de referredBy).
tickets relation vers Ticket (plusieurs) non - - Tickets dont ce compte est le bénéficiaire.
pointLedger relation vers PointTransaction (plusieurs) non - - Mouvements d'EtherPoints du compte.
claimedRewards relation vers ClaimedReward (plusieurs) non - - Récompenses obtenues chez les partenaires.
tokens relation vers Token (plusieurs) non - - Jetons à usage unique émis pour ce compte (suppression de compte).
analytics relation vers Analytics (plusieurs) non - - Événements d'analytique et entrées du journal d'audit de ce compte.
ownedPartners relation vers Partner (plusieurs) non - - Commerces partenaires gérés par ce compte.
identityDocument relation vers IdentityDocument non - - Pièce d'identité envoyée par ce compte (une au plus, relation IdentityOwner).
reviewedIdentities relation vers IdentityDocument (plusieurs) non - - Pièces d'identité sur lesquelles ce compte, agent de la Mairie, a statué (relation IdentityReviewer).
createdAt DateTime oui now() PostgreSQL Timestamptz(3) Date de création du compte.
updatedAt DateTime oui - mis à jour automatiquement ; PostgreSQL Timestamptz(3) Date de dernière modification, mise à jour par Prisma.
deletedAt DateTime non - PostgreSQL Timestamptz(3) Date d'anonymisation RGPD. NULL pour un compte actif ; les statistiques ne comptent que les comptes où elle est NULL.

Index et contraintes de la table :

  • Index : @@index([referredById])
  • Contrainte CHECK RewardCatalog_remainingStock_non_negative : "remainingStock" >= 0

IdentityDocument

Pièce d'identité envoyée par un citoyen pour la vérification d'âge. Une seule ligne par compte : un nouvel envoi remplace le précédent. Le recto et le verso sont chiffrés en AES-256-GCM et effacés dès que la Mairie statue ; la ligne est supprimée avec l'anonymisation RGPD du compte.

Champ Type Obligatoire Défaut Contraintes Rôle
id String oui uuid(7) clé primaire Identifiant interne UUID v7 de la demande.
userId String oui - unique Compte qui a envoyé la pièce. Unique : un compte n'a qu'une demande.
user relation vers User oui - clé étrangère userId, suppression Cascade Relation vers le compte ; la demande est supprimée avec lui (ON DELETE CASCADE).
status énumération AgeVerificationStatus oui PENDING - État de la demande : PENDING à l'envoi, puis VERIFIED ou REJECTED après la décision. La valeur NONE de l'énumération n'est pas utilisée ici.
frontEncrypted Bytes non - - Recto chiffré par encryptBuffer : vecteur d'initialisation de 12 octets, étiquette de 16 octets puis données. NULL après la décision.
backEncrypted Bytes non - - Verso chiffré de la même façon. NULL après la décision.
mimeType String oui - - Type de l'image détecté à partir des premiers octets du recto (image/jpeg ou image/png).
submittedAt DateTime oui now() PostgreSQL Timestamptz(3) Date du dernier envoi ; la file de la Mairie est triée par cette date, la plus ancienne en premier.
reviewedAt DateTime non - PostgreSQL Timestamptz(3) Date de la décision. NULL tant que la demande est en attente.
reviewerId String non - - Agent de la Mairie qui a statué. NULL en attente, ou si le compte de l'agent est supprimé (SET NULL).
reviewer relation vers User non - clé étrangère reviewerId, suppression SetNull Relation vers l'agent qui a statué.

Index et contraintes de la table :

  • Index : @@index([status, submittedAt])
  • Index : @@index([reviewerId])

Token

Jeton à usage unique et à durée limitée envoyé par email. Seule l'empreinte SHA-256 du jeton est stockée. Utilisé aujourd'hui pour confirmer une suppression de compte (validité 24 h).

Champ Type Obligatoire Défaut Contraintes Rôle
id String oui uuid(7) clé primaire Identifiant interne UUID v7.
token String oui - unique Empreinte SHA-256 du jeton transmis à l'utilisateur, jamais le jeton en clair.
type énumération TokenType oui - - Usage du jeton.
userId String oui - - Compte auquel le jeton est rattaché.
user relation vers User oui - clé étrangère userId, suppression Cascade Relation vers le compte ; les jetons sont supprimés avec le compte.
expiresAt DateTime oui - PostgreSQL Timestamptz(3) Date au-delà de laquelle le jeton est refusé.
createdAt DateTime oui now() PostgreSQL Timestamptz(3) Date d'émission.

Index et contraintes de la table :

  • Index : @@index([userId])

LotteryRound

Tour de loterie. En mode onchain, projection d'un tour du contrat BlockLucky, identifié par contractRoundId et alimenté par l'indexeur et la lecture du contrat. En mode simulé, tour créé et piloté par le backend.

Champ Type Obligatoire Défaut Contraintes Rôle
id String oui uuid(7) clé primaire Identifiant interne UUID v7.
contractRoundId Int oui - unique Numéro du tour dans le contrat (roundId), unique. Clé de rapprochement entre la base et la chaîne.
status énumération Status oui OPEN - Statut du tour, aligné sur l'énumération Solidity RoundStatus.
prizePool Float oui 0.0 - Total collecté en ETH (nombre flottant), somme des achats indexés. Valeur d'affichage : le montant exact est sur la chaîne.
winnerWallet String non - - Adresse du gagnant en minuscules, renseignée au tirage.
chainlinkRequestId String non - - Identifiant décimal de la requête Chainlink VRF ; identifiant fictif vrf-req-… en mode simulé.
ticketPriceWei String non - - Prix d'un ticket en wei, en chaîne décimale pour éviter toute perte de précision.
endsAt DateTime non - PostgreSQL Timestamptz(3) Date de fin du tour (endTime du contrat).
drawRequestedAt DateTime non - PostgreSQL Timestamptz(3) Date du bloc où le tirage a été demandé (DrawRequested).
winningTicket Int non - - Numéro du ticket gagnant dans le tour (winningTicket de WinnerPicked).
prizeWei String non - - Cagnotte nette versée au gagnant, après commission de la Mairie, en wei sous forme de chaîne.
prizeClaimedAt DateTime non - PostgreSQL Timestamptz(3) Date du bloc où la cagnotte a été versée (PrizeClaimed).
startedAt DateTime oui now() PostgreSQL Timestamptz(3) Date d'ouverture du tour : heure du bloc RoundOpened ou startTime du contrat.
drawnAt DateTime non - PostgreSQL Timestamptz(3) Date du bloc où le gagnant a été désigné.
tickets relation vers Ticket (plusieurs) non - - Tickets vendus dans ce tour.

Index et contraintes de la table :

  • Index : @@index([status])

Ticket

Ticket de loterie. En mode onchain, une ligne par ticket émis par l'événement TicketsPurchased, identifiée par le couple tour et numéro de ticket. Un achat de plusieurs tickets produit plusieurs lignes partageant le même hachage de transaction.

Champ Type Obligatoire Défaut Contraintes Rôle
id String oui uuid(7) clé primaire Identifiant interne UUID v7.
transactionHash String oui - - Hachage de la transaction d'achat. Non unique : une transaction peut émettre plusieurs tickets. Valeur aléatoire en mode simulé.
blockNumber Int oui - - Numéro du bloc de la transaction d'achat. Valeur aléatoire en mode simulé.
ticketIndex Int non - - Numéro du ticket dans le tour, à partir de 0, tel que stocké par le contrat. NULL en mode simulé.
payerWallet String non - - Adresse qui a payé le ticket (payer), différente du bénéficiaire pour un ticket offert. Sert à réclamer les remboursements. NULL en mode simulé.
userId String oui - - Compte bénéficiaire du ticket (player).
user relation vers User oui - clé étrangère userId Relation vers le compte bénéficiaire.
lotteryId String oui - - Tour auquel appartient le ticket.
lottery relation vers LotteryRound oui - clé étrangère lotteryId Relation vers le tour.
purchasedAt DateTime oui now() PostgreSQL Timestamptz(3) Date d'achat : heure du bloc en mode onchain.

Index et contraintes de la table :

  • Unicité : @@unique([lotteryId, ticketIndex])
  • Index : @@index([userId, purchasedAt])
  • Index : @@index([lotteryId])
  • Index : @@index([transactionHash])

ChainCursor

Curseur de l'indexeur blockchain. Une seule ligne, d'identifiant BlockLucky, contient le dernier bloc entièrement indexé. La remettre à zéro ou la supprimer fait relire la chaîne depuis CHAIN_START_BLOCK.

Champ Type Obligatoire Défaut Contraintes Rôle
id String oui - clé primaire Nom du flux indexé ; seule la valeur BlockLucky est utilisée.
lastBlock Int oui - - Dernier bloc traité ; la passe suivante commence à lastBlock + 1.
updatedAt DateTime oui - mis à jour automatiquement ; PostgreSQL Timestamptz(3) Date de la dernière avance du curseur.

PointTransaction

Journal des mouvements d'EtherPoints. Chaque crédit ou débit écrit une ligne et met à jour User.pointsBalance dans la même transaction. Les montants sont positifs pour un crédit et négatifs pour un débit.

Champ Type Obligatoire Défaut Contraintes Rôle
id String oui uuid(7) clé primaire Identifiant interne UUID v7.
userId String oui - - Compte concerné.
user relation vers User oui - clé étrangère userId Relation vers le compte.
amount Int oui - - Nombre de points, positif pour un crédit, négatif pour un débit.
actionType String oui - - Cause du mouvement : TICKET_PURCHASE, GIFT_TICKET, REFERRAL, SOCIAL_QUEST, REWARD_CLAIM ou ADMIN_BONUS (type LedgerActionType du code, texte libre en base).
description String non - - Libellé lisible du mouvement, affiché dans l'historique.
createdAt DateTime oui now() PostgreSQL Timestamptz(3) Date du mouvement.

Index et contraintes de la table :

  • Index : @@index([userId, createdAt])

Partner

Commerce partenaire d'EtherBay qui propose des récompenses en échange d'EtherPoints.

Champ Type Obligatoire Défaut Contraintes Rôle
id String oui uuid(7) clé primaire Identifiant interne UUID v7.
name String oui - - Nom du commerce affiché dans le catalogue.
logoUrl String oui - - URL du logo du commerce.
isActive Boolean oui true - Faux pour retirer le commerce et toutes ses offres du catalogue.
ownerId String non - - Compte de rôle PARTNER qui gère ce commerce. NULL si aucun ou si le compte est supprimé.
owner relation vers User non - clé étrangère ownerId, suppression SetNull Relation vers le compte gestionnaire.
rewards relation vers RewardCatalog (plusieurs) non - - Offres de récompenses du commerce.
createdAt DateTime oui now() PostgreSQL Timestamptz(3) Date de création.

Index et contraintes de la table :

  • Index : @@index([ownerId])

RewardCatalog

Offre de récompense d'un partenaire, échangeable contre des EtherPoints, avec un stock limité.

Champ Type Obligatoire Défaut Contraintes Rôle
id String oui uuid(7) clé primaire Identifiant interne UUID v7.
partnerId String oui - - Commerce qui propose l'offre.
partner relation vers Partner oui - clé étrangère partnerId Relation vers le commerce.
title String oui - - Intitulé de l'offre.
costInPoints Int oui - - Prix en EtherPoints, strictement positif (contrainte CHECK).
totalStock Int oui - - Stock initial de l'offre, positif ou nul (contrainte CHECK).
remainingStock Int oui - - Stock restant, décrémenté à chaque échange ; ne peut pas devenir négatif (contrainte CHECK).
claimedRewards relation vers ClaimedReward (plusieurs) non - - Échanges réalisés sur cette offre.
isActive Boolean oui true - Faux pour retirer l'offre du catalogue.
createdAt DateTime oui now() PostgreSQL Timestamptz(3) Date de création.

Index et contraintes de la table :

  • Index : @@index([partnerId])
  • Contrainte CHECK RewardCatalog_totalStock_non_negative : "totalStock" >= 0
  • Contrainte CHECK RewardCatalog_costInPoints_positive : "costInPoints" > 0
  • Contrainte CHECK LotteryRound_prizePool_non_negative : "prizePool" >= 0

ClaimedReward

Récompense obtenue par un citoyen : un code promo unique à présenter chez le partenaire, qui le valide une seule fois.

Champ Type Obligatoire Défaut Contraintes Rôle
id String oui uuid(7) clé primaire Identifiant interne UUID v7.
userId String oui - - Citoyen qui a échangé ses points.
user relation vers User oui - clé étrangère userId Relation vers le citoyen.
rewardId String oui - - Offre échangée.
rewardCatalog relation vers RewardCatalog oui - clé étrangère rewardId Relation vers l'offre.
uniqueCode String oui - unique Code promo unique, tiré du lot importé par le partenaire ou généré par le backend.
isUsed Boolean oui false - Vrai une fois le code validé en magasin.
claimedAt DateTime oui now() PostgreSQL Timestamptz(3) Date de l'échange.

Index et contraintes de la table :

  • Index : @@index([userId, claimedAt])
  • Index : @@index([rewardId])

Analytics

Événement d'analytique (parcours, sources UTM) ou entrée du journal d'audit d'administration. Les entrées d'audit ont un eventType préfixé ADMIN_AUDIT:.

Champ Type Obligatoire Défaut Contraintes Rôle
id String oui uuid(7) clé primaire Identifiant interne UUID v7.
userId String non - - Compte à l'origine de l'événement, NULL pour un visiteur anonyme ou un compte supprimé.
user relation vers User non - clé étrangère userId Relation vers le compte.
eventType String oui - - Type d'événement, par exemple un événement de suivi ou ADMIN_AUDIT:TREASURY_WITHDRAWAL_MULTISIG.
utmSource String non - - Source de trafic UTM, ou admin-dashboard pour l'audit.
metadata Json non - - Données complémentaires au format JSON.
timestamp DateTime oui now() PostgreSQL Timestamptz(3) Date de l'événement.

Index et contraintes de la table :

  • Index : @@index([eventType, timestamp])
  • Index : @@index([userId])
  • Contrainte CHECK User_pointsBalance_non_negative : "pointsBalance" >= 0

Énumérations

Role

Rôle d'autorisation d'un compte, vérifié par le middleware RBAC.

Valeur Signification
USER Citoyen : achète des tickets, gagne et échange des EtherPoints.
PARTNER Commerçant partenaire : gère ses offres et valide les codes promo en magasin.
ADMIN Agent de la Mairie : tableau de bord, tirage, annulation, disjoncteur, trésorerie.

TwoFactorMethod

Méthode de double authentification d'un compte.

Valeur Signification
APP Code TOTP produit par une application d'authentification.
EMAIL Code à usage unique envoyé par email.

AgeVerificationStatus

Statut de la vérification d'âge, porté par User.ageVerificationStatus et IdentityDocument.status.

Valeur Signification
NONE Aucune pièce envoyée : valeur par défaut d'un compte.
PENDING Pièce envoyée, en attente de la décision de la Mairie.
VERIFIED Majorité vérifiée par un agent de la Mairie.
REJECTED Pièce refusée avec un motif ; le citoyen peut en envoyer une nouvelle.

TokenType

Usage d'un jeton à usage unique de la table Token.

Valeur Signification
EMAIL_VERIFICATION Vérification d'adresse email. Déclaré mais non utilisé par le code actuel : la vérification passe par un code stocké dans Redis.
ACCOUNT_DELETION Confirmation d'une demande de suppression de compte RGPD, valable 24 h.
PASSWORD_RESET Réinitialisation de mot de passe. Déclaré mais non utilisé : le code de réinitialisation est un code à 6 chiffres stocké dans Redis.

Status

Statut d'un tour de loterie, aligné sur l'énumération Solidity RoundStatus du contrat.

Valeur Signification
OPEN Tour ouvert aux achats (Open).
LOCKED Tirage demandé, en attente de la réponse de Chainlink VRF (Drawing) ; plus aucun achat possible.
COMPLETED Gagnant désigné, cagnotte disponible pour le gagnant (Completed).
CANCELLED Tour annulé ; chaque payeur peut récupérer sa mise (Cancelled).