# 06 — Modèle de données (base `leperelion`)

Schéma cible géré par **Prisma**. C'est une **proposition initiale** : elle évoluera avec les
fonctionnalités. Aucune table n'est partagée avec les autres bases du serveur.

## Entités

### `User` — compte du site
| Champ           | Type        | Notes                                   |
|-----------------|-------------|-----------------------------------------|
| id              | uuid (PK)   |                                         |
| username        | string      | unique — **identifiant de connexion**   |
| email           | string?     | optionnel, unique (notifications/Google)|
| passwordHash    | string      | bcrypt                                  |
| role            | enum        | `super_admin` \| `admin` \| `user`      |
| displayName     | string?     |                                         |
| isActive        | boolean     | défaut true                             |
| createdAt       | datetime    |                                         |
| updatedAt       | datetime    |                                         |

### `GoogleAccount` — liaison OAuth Google (1 user → 1..n comptes Google)
| Champ            | Type      | Notes                                          |
|------------------|-----------|------------------------------------------------|
| id               | uuid (PK) |                                                |
| userId           | uuid (FK) | → User                                         |
| googleSub        | string    | identifiant Google stable                      |
| email            | string    | email du compte Google lié                     |
| refreshTokenEnc  | string    | refresh token **chiffré** (AES-GCM)            |
| scopes           | string[]  | scopes accordés                                |
| connectedAt      | datetime  |                                                |
| lastRefreshAt    | datetime? |                                                |

### `RefreshSession` — sessions JWT refresh (révocation)
| Champ        | Type      | Notes                          |
|--------------|-----------|--------------------------------|
| id           | uuid (PK) |                                |
| userId       | uuid (FK) | → User                         |
| tokenHash    | string    | hash du refresh token          |
| userAgent    | string?   |                                |
| expiresAt    | datetime  |                                |
| revokedAt    | datetime? |                                |
| createdAt    | datetime  |                                |

### `Device` — appareil suivi (téléphone)
| Champ        | Type      | Notes                                        |
|--------------|-----------|----------------------------------------------|
| id           | uuid (PK) |                                              |
| userId       | uuid (FK) | → User                                       |
| name         | string    | ex. « iPhone de X »                          |
| pairingToken | string    | jeton secret que l'appareil utilise pour POST|
| lastSeenAt   | datetime? |                                              |
| createdAt    | datetime  |                                              |

### `LocationPoint` — positions remontées par l'appareil
| Champ        | Type      | Notes                          |
|--------------|-----------|--------------------------------|
| id           | uuid (PK) |                                |
| deviceId     | uuid (FK) | → Device                       |
| lat          | float     |                                |
| lng          | float     |                                |
| accuracy     | float?    | mètres                         |
| battery      | int?      | % batterie (optionnel)         |
| recordedAt   | datetime  | horodatage côté appareil       |
| createdAt    | datetime  |                                |

### `LoginEvent` — journal de connexions (Super Admin)
| Champ      | Type      | Notes                                          |
|------------|-----------|------------------------------------------------|
| id         | uuid (PK) |                                                |
| userId     | string?   | utilisateur concerné (null si identifiant inconnu) |
| username   | string    | identifiant saisi / du compte                  |
| success    | boolean   |                                                |
| kind       | string    | `password` \| `impersonation`                  |
| actor      | string?   | qui a déclenché (impersonation : le super_admin)|
| ip         | string?   | IP réelle (via X-Forwarded-For, `trust proxy`) |
| userAgent  | string?   |                                                |
| createdAt  | datetime  | indexé                                         |

> Écrit par `lib/audit.ts` (login réussi/échoué, impersonation). Lu via
> `GET /audit/logins` (super_admin) ; page `/admin/logs`.

### `AppSetting` — réglages globaux (config Super Admin)
| Champ      | Type      | Notes                                                    |
|------------|-----------|----------------------------------------------------------|
| key        | string (PK) | ex. `google.client_id`, `google.client_secret`, `google.redirect_uri` |
| value      | string    | valeur ; **chiffrée** (AES-GCM) si `isSecret`            |
| isSecret   | boolean   | true pour les secrets (jamais renvoyés en clair par l'API)|
| updatedAt  | datetime  |                                                          |

> Édité via `/admin/config` (capacité `config:manage` = super_admin). La config Google de base
> prime sur le `.env` (cf. `backend/src/lib/config-store.ts`).

### `Geofence` — alerte de zone (1 par `Device`)
| Champ          | Type      | Notes                                                |
|----------------|-----------|------------------------------------------------------|
| id             | uuid (PK) |                                                      |
| deviceId       | uuid (FK) | → Device (unique)                                    |
| refLat/refLng  | float     | point de référence                                   |
| radiusM        | int       | rayon en mètres (défaut 50)                          |
| notifyNumber   | string    | numéro WhatsApp à alerter (format international)      |
| enabled        | boolean   |                                                      |
| lastInside     | boolean?  | état précédent (alerte au franchissement seulement)  |
| lastNotifiedAt | datetime? | anti-spam                                            |

> À chaque position OwnTracks, `lib/geofence.ts` évalue la sortie de zone → envoi WhatsApp
> (`lib/evolution.ts`). Endpoints super_admin `GET/PUT/DELETE /track/devices/:id/geofence`.

### `BatteryAlert` — alerte batterie faible (1 par `Device`)
`threshold` (% défaut 20), `notifyNumber` (cible WhatsApp), `message?` (variables `{device}`
`{battery}` `{threshold}`), `enabled`, `lastBelow`/`lastNotifiedAt` (anti-spam, alerte au passage
sous le seuil). Évalué par `lib/battery-alert.ts` à l'ingestion OwnTracks. Endpoints super_admin
`GET/PUT/DELETE /track/devices/:id/battery-alert` + `…/test`.

> Agenda et mails ne sont **pas** dupliqués en base : ils sont lus en direct via les APIs Google.
> On pourra ajouter un cache local (V2) si les performances/quotas l'exigent.

## Index & contraintes notables

- `User.email` unique ; `GoogleAccount (userId, googleSub)` unique.
- `LocationPoint (deviceId, recordedAt)` indexé pour les requêtes d'historique.
- Suppression d'un `User` → cascade sur ses `GoogleAccount`, `Device`, `LocationPoint`, sessions.

## Notes Prisma

- `datasource` PostgreSQL via `DATABASE_URL`.
- `SHADOW_DATABASE_URL` ou attribut `CREATEDB` du rôle pour les migrations en dev.
- `enum Role { super_admin admin user }`. La capacité `users:manage` (gestion des comptes) est
  réservée à `super_admin` — voir la matrice dans [01_ROLES_ET_DROITS.md](01_ROLES_ET_DROITS.md)
  et `backend/src/lib/permissions.ts`.
