Files

6.3 KiB
Raw Permalink Blame History

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())
email 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 :

  • email indexé (recherche lors de l'authentification)
  • hashed_password : bcrypt, jamais exposé en API
  • is_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_id de 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é par type, jamais par le montant
  • transaction_date : date métier saisie par l'utilisateur (≠ created_at technique)
  • 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 :

  • month en CHAR(7) format ISO YYYY-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).