6.3 KiB
6.3 KiB
Modèle de Données — Budget Tracker
Principes directeurs
- Tous les montants sont stockés en centimes (INTEGER) — principe II de la Constitution
- Les suppressions sont des soft-deletes via
deleted_at— principe II (tracabilité) - Les identifiants sont des UUID v4 — évite l'exposition de séquences prédictibles
- Les timestamps sont en UTC sans timezone stockée
Entités
User
| Champ | Type | Contraintes |
|---|---|---|
| id | UUID | PK, auto (gen_random_uuid()) |
| VARCHAR(255) | UNIQUE, NOT NULL | |
| hashed_password | VARCHAR(255) | NOT NULL |
| full_name | VARCHAR(100) | NOT NULL |
| is_active | BOOLEAN | DEFAULT true, NOT NULL |
| created_at | TIMESTAMP | NOT NULL, DEFAULT now() |
| updated_at | TIMESTAMP | NOT NULL, DEFAULT now() |
Notes :
emailindexé (recherche lors de l'authentification)hashed_password: bcrypt, jamais exposé en APIis_active: permet de désactiver un compte sans le supprimer
Category
| Champ | Type | Contraintes |
|---|---|---|
| id | UUID | PK, auto |
| user_id | UUID | FK → User.id, NOT NULL |
| name | VARCHAR(50) | NOT NULL |
| type | ENUM(income, expense) | NOT NULL |
| color | VARCHAR(7) | NULL, format hex (#RRGGBB) |
| icon | VARCHAR(50) | NULL, nom d'icône (ex: "shopping-cart") |
| is_default | BOOLEAN | DEFAULT false, NOT NULL |
| deleted_at | TIMESTAMP | NULL = actif |
| created_at | TIMESTAMP | NOT NULL, DEFAULT now() |
Notes :
is_default = true: catégories système créées au premier login (Alimentation, Transport, Logement, Loisirs, Santé, Revenus)- Les catégories par défaut ont
user_idde l'utilisateur propriétaire — pas de catégories globales partagées (simplicité) - Index :
(user_id, deleted_at)pour les requêtes de listing - Contrainte : impossible de supprimer si transactions actives liées (contrôlé en service, pas en DB)
Transaction
| Champ | Type | Contraintes |
|---|---|---|
| id | UUID | PK, auto |
| user_id | UUID | FK → User.id, NOT NULL |
| category_id | UUID | FK → Category.id, NOT NULL |
| amount_cents | INTEGER | NOT NULL, CHECK (amount_cents > 0) |
| type | ENUM(income, expense) | NOT NULL |
| description | VARCHAR(255) | NULL autorisé |
| transaction_date | DATE | NOT NULL |
| deleted_at | TIMESTAMP | NULL = actif |
| created_at | TIMESTAMP | NOT NULL, DEFAULT now() |
| updated_at | TIMESTAMP | NOT NULL, DEFAULT now() |
Notes :
amount_cents > 0: le signe est porté partype, jamais par le montanttransaction_date: date métier saisie par l'utilisateur (≠created_attechnique)- Index :
(user_id, transaction_date, deleted_at)pour les requêtes par période - Index :
(user_id, category_id, deleted_at)pour les agrégats par catégorie - Le soft-delete (
deleted_at IS NOT NULL) masque la transaction de l'UI sans la détruire
Budget
| Champ | Type | Contraintes |
|---|---|---|
| id | UUID | PK, auto |
| user_id | UUID | FK → User.id, NOT NULL |
| category_id | UUID | FK → Category.id, NOT NULL |
| month | CHAR(7) | NOT NULL, format YYYY-MM |
| limit_cents | INTEGER | NOT NULL, CHECK (limit_cents > 0) |
| created_at | TIMESTAMP | NOT NULL, DEFAULT now() |
| updated_at | TIMESTAMP | NOT NULL, DEFAULT now() |
| UNIQUE | (user_id, category_id, month) |
Notes :
monthen CHAR(7) format ISOYYYY-MM: simple, triable lexicographiquement, pas de confusion timezone- La contrainte UNIQUE garantit un seul budget par catégorie par mois par utilisateur
- Index implicite sur la contrainte UNIQUE
- Pas de soft-delete : un budget supprimé est réellement supprimé (pas de données financières dans l'entité elle-même)
RefreshToken
| Champ | Type | Contraintes |
|---|---|---|
| id | UUID | PK, auto |
| user_id | UUID | FK → User.id, NOT NULL |
| token_hash | VARCHAR(255) | UNIQUE, NOT NULL |
| expires_at | TIMESTAMP | NOT NULL |
| revoked_at | TIMESTAMP | NULL = actif |
| created_at | TIMESTAMP | NOT NULL, DEFAULT now() |
Notes :
- Seul le hash du token est stocké (SHA-256), jamais le token brut
- Permet la révocation explicite (logout) et l'invalidation par rotation
- Nettoyage périodique des tokens expirés recommandé (tâche cron ou au login)
Relations
User 1 ──────────────────────────── N Category
User 1 ──────────────────────────── N Transaction
User 1 ──────────────────────────── N Budget
User 1 ──────────────────────────── N RefreshToken
Category 1 ──────────────────────── N Transaction
Category 1 ──────────────────────── N Budget
Requêtes critiques et leurs index
Solde courant d'un utilisateur
SELECT
SUM(CASE WHEN type = 'income' THEN amount_cents ELSE -amount_cents END)
FROM transactions
WHERE user_id = :uid AND deleted_at IS NULL;
→ Index : (user_id, deleted_at)
Résumé mensuel (revenus/dépenses d'un mois)
SELECT type, SUM(amount_cents)
FROM transactions
WHERE user_id = :uid
AND transaction_date >= :month_start
AND transaction_date < :month_end
AND deleted_at IS NULL
GROUP BY type;
→ Index : (user_id, transaction_date, deleted_at)
Consommation d'un budget (dépenses catégorie × mois)
SELECT SUM(t.amount_cents)
FROM transactions t
JOIN budgets b ON b.category_id = t.category_id
WHERE t.user_id = :uid
AND t.category_id = :cat_id
AND t.type = 'expense'
AND t.transaction_date >= :month_start
AND t.transaction_date < :month_end
AND t.deleted_at IS NULL;
→ Index : (user_id, category_id, deleted_at)
Valeurs par défaut — Catégories système
| Nom | Type | Couleur | Icône |
|---|---|---|---|
| Alimentation | expense | #22c55e | utensils |
| Transport | expense | #3b82f6 | car |
| Logement | expense | #f59e0b | home |
| Loisirs | expense | #a855f7 | gamepad-2 |
| Santé | expense | #ef4444 | heart-pulse |
| Revenus | income | #10b981 | trending-up |
Créées automatiquement lors du premier login utilisateur (dans la logique de service, pas en seed DB globale).