03-comment-je-travaille/bdd/schema.md

Schéma BDD — Telaria

Source de vérité : entités Doctrine PHP de chaque bundle. Ce document est dérivé du code — en cas de divergence, le code fait foi. MySQL 8.4 · Auto-mapping Doctrine · Zéro migration au bootstrap (entités auto-mappées).


Vue d'ensemble

L'écosystème utilise deux moteurs BDD :

Moteur Usage Emplacement
MySQL 8.4 Données applicatives (toutes les tables ci-dessous) 127.0.0.1:3306
SQLite + sqlite-vec Index vectoriel RAG (kNN cosinus) /var/www/telaria/var/rag/

Les tables MySQL sont réparties entre 4 bundles — chacun déploie ses entités de manière autonome via auto_mapping. L'application principale (telaria-app) ne maintient que ses propres entités (user, reset_password_request, cms_content_seo).


Périmètre par bundle

telaria-app         → user, reset_password_request, cms_content_seo
tlr-symfony         → site, cms_content, cms_tag, cms_image, cms_content_tag (pivot)
tlr-codexia         → veille_source, veille_item, veille_attempt, veille_read
                       app_setting, chat_config
                       metrics_usage, metrics_daily, metrics_alert, metrics_api_key
tlr-mcp             → mcp_tenant, mcp_api_client, mcp_project, mcp_tool_audit_log

telaria-app

user

Table centrale d'authentification. Implémente Security, 2FA email/TOTP, et trusted devices.

Colonne Type Nullable Notes
id INT AUTO_INCREMENT non PK
email VARCHAR(255) UNIQUE non identifiant de connexion
password VARCHAR non bcrypt
roles JSON non ["ROLE_ADMIN"] — ROLE_USER ajouté dynamiquement
is_verified BOOL non vérification e-mail initiale
api_token VARCHAR(128) oui token opaque applicatif (≠ token MCP)
is2fa_enabled BOOL non 2FA par e-mail activé
auth_code VARCHAR(255) oui code OTP courant (email 2FA)
totp_secret VARCHAR(255) oui secret TOTP
trusted_version INT non version du cookie trusted device (incrémenté à la révocation)

Interfaces : UserInterface, TwoFactorInterface, TrustedDeviceInterface, CmsUserInterface, VeilleReaderInterface.

reset_password_request

Géré par SymfonyCasts ResetPassword. Stocke les tokens de réinitialisation hachés.

Colonne Type Notes
id INT AUTO_INCREMENT PK
user_id INT FK → user.id
selector VARCHAR partie publique du token
hashed_token VARCHAR partie privée hachée
requested_at DATETIME
expires_at DATETIME TTL ~1h

cms_content_seo

Extension SEO d'une page CMS. Pattern « companion entity » : la table cms_content appartient au bundle tlr-symfony et ne peut pas être modifiée directement.

Colonne Type Nullable Notes
id INT AUTO_INCREMENT non PK
content_id INT FK UNIQUE non OneToOne → cms_content.id CASCADE DELETE
canonical_url VARCHAR(255) oui URL canonique custom ; null = calculée depuis la requête

tlr-symfony

site

Un domaine servi par l'application. Résolution par host à chaque requête (SiteResolver). Porte le thème, la marque, les adresses mail propres au domaine.

Colonne Type Nullable Notes
id INT AUTO_INCREMENT non PK
host VARCHAR(255) UNIQUE non host canonique, sans schéma (ex. telaria.dev)
aliases JSON oui hosts supplémentaires (ex. ["www.telaria.dev", "localhost"])
slug VARCHAR(64) UNIQUE non id stable (ex. codexia) — /^[a-z0-9-]+$/
label VARCHAR(128) non nom affiché
locale_default VARCHAR(8) non défaut fr
layout_template VARCHAR(128) non template Twig de layout — défaut cms/index.html.twig
theme VARCHAR(64) oui clé CSS du thème
brand_domain VARCHAR(64) oui
brand_tld VARCHAR(16) oui
contact_email VARCHAR(255) oui destinataire formulaire de contact
sender_email VARCHAR(255) oui expéditeur mails sortants
home_slug VARCHAR(255) oui slug de la page CMS racine (/)
og_image_default VARCHAR(512) oui image Open Graph par défaut
enabled BOOL non défaut true
is_primary BOOL non site de repli si aucun host ne matche — un seul attendu
created_at DATETIME_IMMUTABLE non PrePersist
updated_at DATETIME_IMMUTABLE oui PreUpdate

cms_content

Page ou contenu éditorial. Slug unique par site (contrainte composite (site_id, slug)).

Colonne Type Nullable Notes
id INT AUTO_INCREMENT non PK
site_id INT FK non → site.id
author_id INT FK non → user.id (via CmsUserInterface)
title VARCHAR(255) non
slug VARCHAR(255) non /^[a-z0-9-]+$/ ; auto-slugifié depuis le titre à la création
excerpt TEXT oui
markdown TEXT non contenu source
status VARCHAR(20) non draft | published | archived
published_at DATETIME oui posé au premier publish()
updated_at DATETIME non PrePersist/PreUpdate
visibility VARCHAR(20) non public | protected | private
type VARCHAR(50) non page (défaut) | block (fragments chrome non indexés)
noindex BOOL non meta robots noindex + exclusion sitemap
og_image VARCHAR(512) oui surcharge l'OG image du site

Contrainte unique : UNIQUE(site_id, slug).

cms_tag

Étiquette transverse (vocabulaire partagé entre sites).

Colonne Type Notes
id INT AUTO_INCREMENT PK
name VARCHAR(255)
slug VARCHAR(255) UNIQUE auto-slugifié depuis name

cms_content_tag (pivot ManyToMany)

Colonne Type Notes
cms_content_id INT FK → cms_content.id
cms_tag_id INT FK → cms_tag.id

cms_image

Image média. Upload sécurisé via FileUploader (validation MIME réelle par finfo).

Colonne Type Nullable Notes
id INT AUTO_INCREMENT non PK
filename VARCHAR(255) non nom de fichier stocké
mime VARCHAR(100) non type MIME validé
size INT non octets
width INT oui
height INT oui
checksum VARCHAR(64) non déduplication
created_at DATETIME non
owner_id INT FK non → user.id
site_id INT FK oui → site.id SET NULL si site supprimé
content_id INT FK oui → cms_content.id (rattachement optionnel)

tlr-codexia

veille_source

Source de veille (RSS/Atom/HTML). La pipeline de collecte est pilotée par scheduler Symfony.

Colonne Type Nullable Notes
id INT AUTO_INCREMENT non PK
slug VARCHAR(64) UNIQUE non /^[a-z0-9-]+$/
name VARCHAR(128) non
url VARCHAR(512) non URL du flux
type VARCHAR(16) non rss | atom | html
schedule VARCHAR(32) non interval ISO 8601 (ex. PT1H)
default_theme VARCHAR(64) non thème par défaut des items — défaut ia
is_active BOOL non interrupteur humain
standby BOOL non pause automatique après N échecs consécutifs (distinct de is_active)
standby_since DATETIME_IMMUTABLE oui
standby_reason VARCHAR(255) oui
consecutive_failures INT non compteur d'échecs consécutifs — remis à 0 au succès ou au lever de standby
editorial_description TEXT oui
created_at DATETIME_IMMUTABLE non
updated_at DATETIME_IMMUTABLE oui

veille_item

Item collecté depuis une source. Journal de déduplication + cycle de vie de la proposition.

Colonne Type Nullable Notes
id INT AUTO_INCREMENT non PK
source_id INT FK non → veille_source.id CASCADE DELETE
url VARCHAR(768) non INDEX
content_hash VARCHAR(64) non SHA-256 du contenu canonisé (dédup si URL change) — INDEX
title VARCHAR(512) oui
published_at DATETIME_IMMUTABLE oui pubDate du flux
created_at DATETIME_IMMUTABLE non moment d'ingestion — INDEX
processed_at DATETIME_IMMUTABLE oui moment de traitement LLM — INDEX
theme VARCHAR(64) non
status VARCHAR(16) non pending | proposed | accepted | rejected | failed
raw_content TEXT oui contenu brut nettoyé du HTML
summary TEXT oui résumé Claude
title_fr VARCHAR(512) oui titre français extrait du résumé
relevance FLOAT oui score 0..1 (confiance classification)
model_used VARCHAR(64) oui modèle Claude utilisé (ex. claude-haiku-4-5)
proposal_path VARCHAR(512) oui chemin relatif du fichier proposition écrit
error_message TEXT oui diagnostic si status=failed

veille_attempt

Tentative de traitement d'un item. Conserve le détail complet fetch + LLM pour diagnostic.

Colonne Type Nullable Notes
id INT AUTO_INCREMENT non PK
item_id INT FK non → veille_item.id CASCADE DELETE — INDEX
attempted_at DATETIME_IMMUTABLE non INDEX
outcome VARCHAR(16) non success | failed
stage_reached VARCHAR(16) non fetch | classify | summarize | write | done
duration_ms INT oui durée totale
fetch_attempted BOOL non
fetch_outcome VARCHAR(32) oui
fetch_http_status INT oui
fetch_bytes INT oui
fetch_extracted_chars INT oui
fetch_error TEXT oui
llm_model VARCHAR(64) oui
llm_request TEXT oui prompt envoyé
llm_response_raw TEXT oui réponse brute
llm_http_status INT oui
tokens_input INT oui
tokens_output INT oui
error_class VARCHAR(255) oui classe PHP de l'exception
error_message TEXT oui

veille_read

État de lecture par utilisateur (absence de ligne = non lu).

Colonne Type Notes
id INT AUTO_INCREMENT PK
user_id INT FK → user.id CASCADE DELETE — INDEX
item_id INT FK → veille_item.id CASCADE DELETE
read_at DATETIME_IMMUTABLE

Contrainte : UNIQUE(user_id, item_id).

app_setting

Store clé/valeur générique (singleton par clé).

Colonne Type Notes
name VARCHAR(128) PK (pas d'id auto)
value TEXT nullable

chat_config

Ligne unique (singleton) — paramétrage du chat public administré en BO.

Colonne Type Notes
id INT AUTO_INCREMENT PK
model VARCHAR(64) modèle Claude (ex. claude-haiku-4-5)
top_k INT nb de documents RAG récupérés (1–20)
score_threshold FLOAT seuil de pertinence cosinus (0–1)
temperature FLOAT température LLM (0–1)
max_tokens INT limite tokens sortants (256–4096)
history_turns INT tours de conversation mémorisés (0–20)

metrics_usage

Usage de tokens dimensionnel par (jour, modèle, clé API, service tier). Alimenté depuis l'Admin API Anthropic.

Colonne Type Notes
id INT AUTO_INCREMENT PK
day DATE_IMMUTABLE INDEX
model VARCHAR(128)
api_key_id VARCHAR(128)
service_tier VARCHAR(32)
uncached_input_tokens BIGINT nullable
cache_read_tokens BIGINT nullable
cache_creation_tokens BIGINT nullable
output_tokens BIGINT nullable

Contrainte : UNIQUE(day, model, api_key_id, service_tier).

metrics_daily

Snapshot quotidien org-wide (tokens + coûts), agrégé par label (= modèle ou type de coût).

Colonne Type Notes
id INT AUTO_INCREMENT PK
day DATE_IMMUTABLE INDEX
label VARCHAR(128) modèle ou type (ex. web_search)
uncached_input_tokens BIGINT nullable
cache_read_tokens BIGINT nullable
cache_creation_tokens BIGINT nullable
output_tokens BIGINT nullable
web_search_requests BIGINT nullable
cost_cents DECIMAL(14,5) nullable — cents USD

Contrainte : UNIQUE(day, label).

metrics_alert

Alertes de coût configurables (seuil en USD, période jour/mois, anti-spam par période).

Colonne Type Nullable Notes
id INT AUTO_INCREMENT non PK
label VARCHAR(128) non
amount_usd DECIMAL(10,2) non seuil en dollars
period VARCHAR(16) non daily | monthly
email VARCHAR(255) non destinataire de l'alerte
enabled BOOL non
last_notified_period VARCHAR(16) oui ex. 2026-06 (anti-spam : 1 mail/franchissement/période)
last_notified_at DATETIME_IMMUTABLE oui

metrics_api_key

Cache local des clés API de l'org Anthropic (id → nom lisible).

Colonne Type Notes
id VARCHAR(64) PK (id Anthropic, pas d'auto-increment)
name VARCHAR(255)
workspace_id VARCHAR(64) nullable

tlr-mcp

mcp_tenant

Tenant du serveur MCP (organisation cliente).

Colonne Type Notes
id INT AUTO_INCREMENT PK
name VARCHAR(100) UNIQUE
status VARCHAR(20) active | ...

mcp_api_client

Client API d'un tenant. Le token brut n'est jamais stocké — seulement son hash SHA-256.

Colonne Type Nullable Notes
id INT AUTO_INCREMENT non PK
tenant_id INT FK non → mcp_tenant.id CASCADE DELETE
token_hash VARCHAR(64) UNIQUE non SHA-256 du token opaque
scopes JSON non ex. ["tool:search_docs", "project:codexia"] ; wildcards tool:* / project:* supportés
rate_limit_per_minute INT non défaut 60
revoked BOOL non
expires_at DATETIME_IMMUTABLE oui null = pas d'expiration

mcp_project

Projet RAG accessible via MCP (pointe vers un source_root sur le disque).

Colonne Type Notes
id INT AUTO_INCREMENT PK
tenant_id INT FK → mcp_tenant.id CASCADE DELETE
slug VARCHAR(100) identifiant du projet
root_path VARCHAR(512) chemin absolu vers le corpus RAG
status VARCHAR(20) active | ...

mcp_tool_audit_log

Journal d'audit des appels d'outils MCP.

Colonne Type Nullable Notes
id INT AUTO_INCREMENT non PK
tenant_id INT non dénormalisé (pas de FK — conservé si tenant supprimé)
project_id INT oui dénormalisé
api_client_id INT oui dénormalisé
tool_name VARCHAR(100) non list_docs | read_doc | search_docs
status VARCHAR(20) non success | refused | error
error_code VARCHAR(50) oui code JSON-RPC si erreur
timestamp DATETIME_IMMUTABLE non

Index vectoriel SQLite (RAG)

En dehors de MySQL, le moteur RAG utilise un fichier SQLite + l'extension sqlite-vec pour la recherche kNN.

Fichier Emplacement Notes
rag.sqlite /var/www/telaria/var/rag/ index vectoriel + métadonnées
vec0.so /usr/local/lib/sqlite-vec/ extension SQLite chargée par PHP 8.5

Structure SQLite (gérée par tlr-rag, pas par Doctrine) :

  • Table des chunks : (id, source_path, chunk_index, content, embedding BLOB)
  • Recherche kNN par cosinus via vec_search() de sqlite-vec

Patterns Doctrine notables

Auto-mapping — Toutes les entités des bundles utilisent l'auto-mapping. Zéro fichier XML/YAML de mapping. Zéro migration au bootstrap d'un bundle.

resolve_target_entities — Les bundles ne dépendent pas de App\Entity\User. Ils déclarent leurs propres interfaces (CmsUserInterface, VeilleReaderInterface) ; l'app résout l'entité cible dans doctrine.yaml. Couplage découplé = testabilité et réutilisabilité des bundles.

Lifecycle callbacks — #[ORM\HasLifecycleCallbacks] + #[PrePersist] / #[PreUpdate] pour created_at / updated_at. Pas de listeners Doctrine globaux.

Dénormalisation d'audit — mcp_tool_audit_log stocke les IDs en colonnes scalaires plutôt qu'en FK — choix délibéré pour conserver l'historique d'audit même si tenant/client est supprimé.

Singleton par PK naturelle — app_setting utilise name comme PK (VARCHAR 128, pas d'auto-increment). metrics_api_key idem avec l'ID Anthropic.


Voir aussi

Assistant documentaire

Posez une question sur la documentation. Les réponses citent leurs sources — un clic ouvre le document à gauche.

Loading…
Loading the web debug toolbar…
Attempt #