schema¶
⬇️ Télécharger cette page en Markdown
Table incidents — colonne email_notification_sent¶
Ajoutée en v3.0.1. Indique si la notification email a été envoyée à la mairie pour cet incident.
ALTER TABLE incidents
ADD COLUMN email_notification_sent TINYINT(1) NOT NULL DEFAULT 0
AFTER statut;
| Valeur | Signification |
|---|---|
0 |
Email non encore envoyé (état initial) |
1 |
Email envoyé avec succès |
Rôle dans submit_incident.php : avant d'envoyer l'email, le code vérifie email_notification_sent = 0. En cas de retry WorkManager (l'incident est déjà en base suite à un crash), l'email ne part qu'une seule fois même si la requête est reçue plusieurs fois.
-- Chemin normal : marquer après envoi réussi
UPDATE incidents SET email_notification_sent = 1 WHERE id = ?;
-- Chemin doublon : envoyer si pas encore envoyé
SELECT id, email_notification_sent FROM incidents
WHERE citoyen_id = ? AND type_id = ? AND description = ?
AND ABS(latitude - ?) < 0.0005 AND ABS(longitude - ?) < 0.0005
AND created_at > DATE_SUB(NOW(), INTERVAL 6 HOUR)
LIMIT 1;
Table webauthn_credentials (v3.1, 2026-08-25)¶
Clés de sécurité (WebAuthn/FIDO2) pour la connexion admin en 2 facteurs. Voir Conformité ANSSI § 15 pour le flux complet.
CREATE TABLE webauthn_credentials (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
credential_id VARCHAR(512) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
public_key TEXT NOT NULL,
sign_counter INT UNSIGNED NOT NULL DEFAULT 0,
nom VARCHAR(100) NULL,
rp_id VARCHAR(255) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
last_used_at TIMESTAMP NULL,
CONSTRAINT fk_webauthn_credentials_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
UNIQUE KEY uq_webauthn_credential_id (credential_id),
KEY idx_webauthn_credentials_user_rp (user_id, rp_id)
);
rp_id = domaine d'enregistrement (urbafix.fr/monquartier.fr distincts) ; une ligne = une clé physique, gérée en self-service depuis securite.php.
types et services sont des VUES, pas des tables (v3.1, 2026-08-25)¶
Découvert en implémentant les alertes SLA. types et services — utilisées partout dans le code applicatif (types.php, services.php, incidents.php, incident_detail.php, Auth.php...) — ne sont pas des tables de base mais des vues :
SHOW CREATE VIEW types; -- VIEW ... AS SELECT ... FROM types_incident (ordre exposé aussi sous l'alias `priorite`)
SHOW CREATE VIEW services; -- VIEW ... AS SELECT ... FROM services_mairie
Les vraies tables sont types_incident et services_mairie. Les deux vues sont de simples projections column-à-column (pas d'agrégation), donc updatable — INSERT/UPDATE/DELETE via types/services fonctionnent normalement et écrivent directement dans les tables de base (testé et vérifié). Toute ALTER TABLE doit cibler types_incident/services_mairie, jamais types/services — et si une vue doit exposer une nouvelle colonne, il faut la recréer (CREATE OR REPLACE VIEW, en conservant son DEFINER d'origine).
Pas de dérive de données malgré l'apparence trompeuse de duplication : contrairement à photos/photos_incident ou historique_incident/incident_historique (autres paires de noms similaires dans ce schéma), il n'y a ici qu'une seule source de vérité.
Colonnes SLA — types_incident.sla_delai_heures / services_mairie.sla_delai_heures (v3.1, 2026-08-25)¶
Alertes de délai configurables, avec repli du service vers le type si l'incident n'est attribué à aucun service :
ALTER TABLE services_mairie ADD COLUMN sla_delai_heures INT NULL AFTER telephone;
ALTER TABLE types_incident ADD COLUMN sla_delai_heures INT NULL AFTER ordre;
-- + recréation des vues `services` et `types` pour exposer la colonne (voir section ci-dessus)
NULL = pas de délai configuré = pas d'alerte (comportement par défaut inchangé tant que rien n'est configuré). Logique de calcul (incidents.php, incident_detail.php, statistiques.php) :
COALESCE(sv.sla_delai_heures, t.sla_delai_heures) as sla_delai_heures,
(
i.statut NOT IN ('resolu', 'ferme')
AND COALESCE(sv.sla_delai_heures, t.sla_delai_heures) IS NOT NULL
AND TIMESTAMPDIFF(HOUR, i.created_at, NOW()) > COALESCE(sv.sla_delai_heures, t.sla_delai_heures)
) as sla_depasse
FROM incidents i
JOIN types_incident t ON i.type_id = t.id
LEFT JOIN services sv ON i.service_id = sv.id
Un incident resolu ou ferme n'est jamais considéré en retard. Configuration via types.php et services.php (champ « Délai d'alerte SLA (heures) »). Voir Interface Administration → Alertes SLA.
Colonne users.incidents_view_pref (v3.1, 2026-08-25)¶
Préférence liste/kanban sur incidents.php, mémorisée par utilisateur :
ALTER TABLE users
ADD COLUMN incidents_view_pref ENUM('liste','kanban') NOT NULL DEFAULT 'liste' AFTER role;
Table saved_filters (v3.1, 2026-08-25)¶
Vues sauvegardées / filtres favoris sur incidents.php, par utilisateur :
CREATE TABLE IF NOT EXISTS saved_filters (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
page VARCHAR(50) NOT NULL DEFAULT 'incidents',
nom VARCHAR(100) NOT NULL,
query_string VARCHAR(500) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_saved_filters_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
KEY idx_saved_filters_user_page (user_id, page)
);
query_string stocke la query string GET telle quelle (statut=nouveau&priorite=urgente&vue=kanban) — pas de JSON, réappliquée directement en href.
Table app_settings¶
Table clé-valeur pour la configuration runtime de l'application (sans redémarrage Docker).
CREATE TABLE IF NOT EXISTS app_settings (
`key` VARCHAR(100) NOT NULL PRIMARY KEY,
`value` TEXT NOT NULL,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
ON UPDATE CURRENT_TIMESTAMP
);
-- Valeur initiale
INSERT INTO app_settings (`key`, `value`) VALUES ('email_test_mode', '1');
Entrées actuelles¶
| Clé | Valeur initiale | Description |
|---|---|---|
email_test_mode |
'1' |
'1' = emails vers l'adresse de test (test) · '0' = emails vers mairie réelle (prod) |
Accès PHP¶
$row = Database::getInstance()->fetchOne(
"SELECT `value` FROM app_settings WHERE `key` = ?",
['email_test_mode']
);
$isTestMode = $row ? (bool)(int)$row['value'] : true; // fallback TEST si erreur DB
Modification¶
Via le endpoint POST /api/admin/email_test_mode.php (depuis l'app Android en DEBUG) ou directement en SQL :
UPDATE app_settings SET `value` = '0' WHERE `key` = 'email_test_mode'; -- passer en PROD
UPDATE app_settings SET `value` = '1' WHERE `key` = 'email_test_mode'; -- repasser en TEST
Table registration_tokens¶
Stocke les tokens de validation générés lors des demandes d'inscription mairie.
CREATE TABLE registration_tokens (
id INT(11) NOT NULL AUTO_INCREMENT,
token VARCHAR(64) NOT NULL,
mairie_id INT(11) NOT NULL,
expires_at DATETIME NOT NULL,
used_at DATETIME DEFAULT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY token (token),
KEY expires_at (expires_at),
CONSTRAINT fk_reg_tokens_mairie
FOREIGN KEY (mairie_id) REFERENCES mairies(id) ON DELETE CASCADE
);
| Colonne | Description |
|---|---|
token |
64 car. hex — bin2hex(random_bytes(32)) — 256 bits d'entropie |
mairie_id |
Référence à mairies.id (CASCADE DELETE) |
expires_at |
created_at + 24h — passé ce délai, le token est rejeté |
used_at |
NULL tant que non consommé ; rempli par confirm_registration.php à la création du compte |
Règle : à chaque nouvelle demande pour une même mairie, les tokens non utilisés existants sont supprimés avant d'en générer un nouveau.
Voir Inscription des mairies pour le flux complet.