- Corrige le type de retour de UserController::export() (StreamedResponse) - Ajoute les classes dark mode manquantes sur le bloc info de la page import - Génère la documentation complète du projet dans docs/documentation.md Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
25 KiB
Documentation — MesRelevés
Table des matières
- Présentation
- Stack technique
- Installation
- Schéma de base de données
- Modèles Eloquent
- Authentification et autorisations
- Fonctionnalités
- Routes
- Services
- Énumérations
- Middlewares
- Administration
- Mises à jour
- Commandes Artisan
- Performance et indexation
1. Présentation
MesRelevés est une application web dédiée aux associations de généalogie. Elle permet :
- la saisie systématique de relevés d'actes d'état civil (naissance, mariage, décès, etc.) ;
- la recherche plein texte dans ces relevés, avec filtres par type, lieu et période ;
- l'export au format GEDCOM 5.5.1 pour import dans les logiciels de généalogie ;
- la gestion collaborative par sections locales, avec un workflow de validation par statut.
L'application supporte deux bases de données : PostgreSQL 16 (recommandé, avec JSONB et recherche plein texte native) et MySQL/MariaDB.
2. Stack technique
| Composant | Technologie |
|---|---|
| Langage | PHP 8.2+ |
| Framework | Laravel 12 |
| Base de données | PostgreSQL 16 (recommandé) ou MySQL 8+ |
| Frontend | Blade + Alpine.js + Tailwind CSS |
| Assets | Vite |
| Cartographie | Leaflet.js + OpenStreetMap |
| Authentification | Laravel Breeze (sessions) + 2FA par code PIN |
| Cache | Redis (via Laravel Cache) |
3. Installation
Prérequis
- PHP 8.2+ avec extensions :
pdo_pgsql(oupdo_mysql),mbstring,json,xml,curl - Composer
- Node.js 18+ et npm
- PostgreSQL 16+ ou MySQL 8+
- Redis (optionnel, pour le cache)
Installation manuelle
# Cloner le dépôt
git clone <url-du-depot> mesreleves
cd mesreleves
# Installer les dépendances
composer install --no-dev
npm install && npm run build
# Configurer l'environnement
cp .env.example .env
php artisan key:generate
# Éditer .env : APP_URL, DB_*, MAIL_*, CACHE_DRIVER
# Puis lancer les migrations
php artisan migrate
# Créer le lien de stockage public
php artisan storage:link
Assistant d'installation (wizard)
L'application inclut un assistant graphique accessible à l'URL /setup lors du premier lancement (avant la création du fichier storage/installed). Il guide l'administrateur à travers :
- Connexion à la base de données — saisie des paramètres et test de connexion
- Paramètres de l'application — nom du site, URL, fuseau horaire
- Compte administrateur — création du premier utilisateur
L'assistant exécute ensuite les migrations et crée le fichier storage/installed.
Environnement de développement
php artisan serve # http://localhost:8000
npm run dev # Vite en watch (CSS/JS)
php artisan migrate:fresh --seed # Reset + données de test
4. Schéma de base de données
Vue d'ensemble
lieu_types ──< lieux >── (auto-référentiel : lieu_parent_id)
│
├──< sections >──< section_user >── users
│
└──< sources >──< source_user >── users
│
├── source_types ──< source_type_fields
├── depots
└──< releves
Tables
users
| Colonne | Type | Description |
|---|---|---|
id |
bigint PK | |
name |
string | Nom de l'utilisateur |
email |
string unique | Adresse e-mail (identifiant de connexion) |
password |
string | Hash bcrypt |
role |
string | admin | section_manager | member |
is_active |
boolean | Compte actif (défaut : true) |
email_verified_at |
timestamp | Date de vérification |
remember_token |
string | |
created_at, updated_at |
timestamp |
lieu_types
| Colonne | Type | Description |
|---|---|---|
id |
bigint PK | |
nom |
string | Ex : « Commune », « Département », « Pays » |
lieux
| Colonne | Type | Description |
|---|---|---|
id |
bigint PK | |
lieu_type_id |
bigint FK nullable | → lieu_types |
nom |
string | Nom court |
code |
string nullable | Code INSEE, postal, etc. |
lieu_parent_id |
bigint FK nullable | Auto-référentiel (hiérarchie) |
nom_long |
string nullable | Calculé automatiquement (ex : « Bordeaux, Gironde, France ») |
latitude |
decimal(10,7) nullable | Coordonnées GPS |
longitude |
decimal(10,7) nullable | |
note |
text nullable |
sections
| Colonne | Type | Description |
|---|---|---|
id |
bigint PK | |
nom |
string | |
lieu_id |
bigint FK nullable | → lieux |
adresse |
string nullable | |
email_contact |
string nullable | |
url |
string nullable |
section_user (pivot)
| Colonne | Type | Description |
|---|---|---|
section_id |
bigint FK | → sections (cascade delete) |
user_id |
bigint FK | → users (cascade delete) |
role_in_section |
string | section_manager | member |
depots
| Colonne | Type | Description |
|---|---|---|
id |
bigint PK | |
nom |
string | |
description |
text nullable | |
adresse_postale |
string nullable | |
url |
string nullable |
source_types
| Colonne | Type | Description |
|---|---|---|
id |
bigint PK | |
nom |
string | Ex : « Acte de naissance », « Registre paroissial » |
description |
text nullable |
source_type_fields
Définit les champs dynamiques du formulaire de saisie pour chaque type de source.
| Colonne | Type | Description |
|---|---|---|
id |
bigint PK | |
source_type_id |
bigint FK | → source_types (cascade delete) |
name |
string | Clé JSON (ex : nom_pere) — unique par source_type |
label |
string | Libellé affiché dans le formulaire |
type |
string | FieldType : text, date, boolean, select, textarea, number, place |
required |
boolean | |
order |
smallint | Ordre d'affichage |
options |
json nullable | Pour type=select : liste des choix possibles |
sources
| Colonne | Type | Description |
|---|---|---|
id |
bigint PK | |
nom |
string | |
description |
text nullable | |
source_type_id |
bigint FK | → source_types |
depot_id |
bigint FK nullable | → depots (null si dépôt supprimé) |
section_id |
bigint FK nullable | → sections |
lieu_id |
bigint FK nullable | → lieux |
annee_debut |
smallint nullable | Période couverte |
annee_fin |
smallint nullable | |
cote |
string nullable | Cote d'archives |
auteur |
string nullable | |
status |
string | SourceStatus : a_faire → en_cours → a_valider → termine |
source_user (pivot)
| Colonne | Type | Description |
|---|---|---|
source_id |
bigint FK | → sources (cascade delete) |
user_id |
bigint FK | → users (cascade delete) |
releves
| Colonne | Type | Description |
|---|---|---|
id |
bigint PK | |
source_id |
bigint FK | → sources (cascade delete) |
data |
jsonb (pgsql) / json (mysql) | Champs variables selon le type de source |
created_by |
bigint FK | → users |
updated_by |
bigint FK | → users |
created_at, updated_at |
timestamp | |
nom |
text GENERATED | Extrait de data->>'nom' |
prenom |
text GENERATED | Extrait de data->>'prenom' |
date_evenement |
text GENERATED | Extrait de data->'date_evenement'->>'valeur' |
Les colonnes générées sont indexées (B-tree). Sur PostgreSQL, un index GIN couvre la colonne data entière.
notifications
Table standard Laravel pour les notifications en base de données (UUID comme clé primaire).
5. Modèles Eloquent
User
Relations : sections (BelongsToMany via section_user), sourcesAssignees (BelongsToMany via source_user)
Méthodes :
| Méthode | Description |
|---|---|
isAdmin() |
true si rôle admin |
isSectionManager() |
true si rôle admin ou section_manager |
isMemberOfSection(Section) |
Appartient à la section |
isManagerOfSection(Section) |
Est responsable de la section (ou admin) |
Lieu
Relations : lieuType, parent, enfants, sections, sources, releves (HasManyThrough)
Le nom_long est recalculé automatiquement à la création et à la mise à jour (remonte la hiérarchie parente).
Section
Relations : lieu, membres (BelongsToMany), sources, responsables (BelongsToMany filtré sur role_in_section = section_manager)
Source
Relations : sourceType, depot, section, lieu, membres (BelongsToMany), releves
Méthodes :
| Méthode | Description |
|---|---|
isVisibleBy(User) |
La source est visible par cet utilisateur |
canTransitionTo(SourceStatus, User) |
La transition de statut est autorisée |
Releve
Relations : source, createur (User via created_by), modificateur (User via updated_by)
Le champ data est casté en tableau PHP ('data' => 'array').
SourceType / SourceTypeField
Relations : SourceType → fields (HasMany), SourceTypeField → sourceType (BelongsTo)
6. Authentification et autorisations
Authentification
L'authentification repose sur Laravel Breeze (sessions). Le flux standard est :
- Saisie de l'e-mail et du mot de passe
- Si les credentials sont valides, un code PIN à 6 chiffres est envoyé par e-mail (2FA)
- Saisie du code PIN (validité : 10 minutes, renvoi possible)
- Accès à l'application
Le code PIN est stocké hashé en session (2fa.pin_hash) avec sa date d'expiration.
Roles
| Valeur | Label | Description |
|---|---|---|
admin |
Administrateur | Accès complet à toute l'application |
section_manager |
Responsable de section | Gestion de sa section, validation des relevés |
member |
Membre | Saisie de relevés sur les sources assignées |
Un administrateur a tous les droits d'un responsable de section, et un responsable de section a tous les droits d'un membre.
Tableau des autorisations
| Action | Admin | Resp. Section | Membre assigné | Membre |
|---|---|---|---|---|
| Voir liste des sources | ✓ | ✓ | ✓ | ✓ |
| Créer une source | ✓ | ✓ | ||
| Modifier une source | ✓ | ✓ | ||
| Supprimer une source | ✓ | |||
| Assigner un membre | ✓ | ✓ | ||
Voir relevés (source terminée) |
✓ | ✓ | ✓ | ✓ |
| Voir relevés (autres statuts) | ✓ | ✓ | ✓ | |
| Saisir / modifier un relevé | ✓ | ✓ | ✓ | |
| Supprimer un relevé | ✓ | ✓ | ||
Passer status → a_valider |
✓ | ✓ | ✓ | |
Passer status → termine |
✓ | ✓ | ||
| Gérer utilisateurs / sections | ✓ |
Workflow de statut d'une source
a_faire ──► en_cours ──► a_valider ──► termine
◄──────────── (rejet → retour en_cours)
Lors du passage en a_valider, une notification (e-mail + notification interne) est envoyée aux administrateurs et aux responsables de la section concernée.
Policies
| Policy | Modèle | Actions couvertes |
|---|---|---|
SourcePolicy |
Source |
viewAny, view, create, update, delete, assignMembre, transition |
RelevePolicy |
Releve |
viewAny, view, create, update, delete |
LieuPolicy |
Lieu |
create, update, delete |
7. Fonctionnalités
Gestion des lieux
- Hiérarchie libre de lieux (commune → département → pays, ou toute autre arborescence)
- Types de lieux configurables (admin)
- Calcul automatique du
nom_longà partir de la hiérarchie parente - Coordonnées GPS (latitude/longitude) pour la vue cartographique
- Import et export CSV
- Recherche AJAX par nom (endpoint
/lieux/search)
Gestion des sources
- Une source = un registre ou document à dépouiller
- Associée à un type de source, un dépôt d'archives, une section, un lieu et une période
- Membres assignés pour la saisie (table pivot
source_user) - Workflow de statut avec notifications automatiques
Saisie des relevés
Le formulaire de saisie est dynamique : il est piloté par les champs définis dans source_type_fields. Chaque champ peut être de type :
| Type | Description |
|---|---|
text |
Champ texte simple |
textarea |
Zone de texte multiligne |
number |
Nombre entier ou décimal |
boolean |
Case à cocher |
select |
Liste déroulante (options définies dans source_type_fields.options) |
date |
Date avec choix du calendrier (grégorien, julien, républicain) |
place |
Sélecteur de lieu (recherche AJAX) |
Les champs de type date stockent : { "valeur": "YYYY-MM-DD", "calendrier": "gregorien|julien|republicain" }.
Recherche
La recherche plein texte porte sur les colonnes générées nom, prenom, date_evenement et sur le JSON complet. Elle supporte :
- recherche textuelle (LIKE ou FTS selon le SGBD)
- filtre par type de source
- filtre par lieu (avec descente récursive dans la hiérarchie via CTE)
- filtre par période (
annee_debut/annee_fin)
Le nombre maximum de résultats est configurable par l'administrateur (défaut : 200). Un bandeau d'avertissement s'affiche lorsque la limite est atteinte.
Carte
Vue cartographique (Leaflet.js + OpenStreetMap) des lieux géolocalisés ayant des relevés. Chaque marqueur affiche un popup avec les sources associées au lieu, leur statut et un lien vers la recherche.
Export GEDCOM
Le service GedcomExportService génère un fichier .ged (GEDCOM 5.5.1) à partir d'une source ou d'une sélection de relevés. Les dates sont converties en grégorien ; le calendrier julien est annoté @#DJULIAN@ et le calendrier républicain est converti via DateConversionService.
Import de relevés CSV
Un import CSV est disponible pour les relevés (endpoint /sources/{source}/import). Le séparateur (; ou ,) et le BOM UTF-8 sont détectés automatiquement.
8. Routes
Routes publiques
| Méthode | URL | Action |
|---|---|---|
| GET | / |
Page d'accueil |
| GET/POST | /setup/* |
Assistant d'installation |
| GET/POST | /2fa |
Vérification 2FA |
| GET/POST | /login |
Connexion |
| GET/POST | /forgot-password |
Mot de passe oublié |
Routes authentifiées (/)
| Méthode | URL | Action |
|---|---|---|
| GET | /dashboard |
Tableau de bord |
| GET/PATCH/DELETE | /profile |
Profil utilisateur |
| GET | /lieux |
Liste des lieux (avec filtres) |
| GET | /lieux/search |
Recherche AJAX de lieux |
| GET/POST | /lieux/import |
Import CSV de lieux |
| GET | /lieux/export/csv |
Export CSV de lieux |
| GET/POST | /lieux/{lieu}/edit |
CRUD lieux |
| GET | /sources |
Liste des sources |
| GET/POST | /sources/{source}/membres |
Gestion des membres assignés |
| POST | /sources/{source}/transition |
Changer le statut |
| GET | /sources/{source}/releves |
Liste des relevés d'une source |
| GET/POST | /sources/{source}/releves/create |
Saisie d'un relevé |
| GET/PATCH | /releves/{releve}/edit |
Modification d'un relevé |
| GET/POST | /sources/{source}/import |
Import CSV de relevés |
| GET | /export/source/{source}/csv |
Export CSV d'une source |
| GET | /recherche |
Recherche plein texte |
| GET | /carte |
Vue cartographique |
| GET | /carte/data |
Données JSON pour la carte |
| GET | /notifications |
Centre de notifications |
Routes d'administration (/admin, middleware role:admin)
| Méthode | URL | Action |
|---|---|---|
| GET | /admin/dashboard |
Tableau de bord admin |
| GET/POST | /admin/utilisateurs |
Liste + création d'utilisateurs |
| GET/PUT/DELETE | /admin/utilisateurs/{id} |
Modification / suppression |
| GET/POST | /admin/utilisateurs/export |
Export CSV utilisateurs |
| GET/POST | /admin/utilisateurs/import |
Import CSV utilisateurs |
| POST | /admin/utilisateurs/{id}/toggle-active |
Activer / désactiver un compte |
| GET/POST/PUT/DELETE | /admin/lieu-types |
CRUD types de lieux |
| GET/POST/PUT/DELETE | /admin/sections |
CRUD sections |
| GET/POST/DELETE | /admin/sections/{id}/membres |
Membres d'une section |
| GET/POST/PUT/DELETE | /admin/depots |
CRUD dépôts d'archives |
| GET/POST/PUT/DELETE | /admin/source-types |
CRUD types de sources |
| POST/PUT/DELETE | /admin/source-types/{id}/fields |
Gestion des champs dynamiques |
| POST | /admin/source-types/{id}/fields/reorder |
Réordonner les champs |
| GET/POST | /admin/parametres |
Paramètres du site |
| POST | /admin/parametres/logo |
Upload du logo |
| POST | /admin/parametres/smtp |
Configuration SMTP |
| POST | /admin/parametres/smtp/test |
Test SMTP |
| POST | /admin/parametres/storage-link |
Recréer le lien de stockage |
9. Services
DateConversionService
Convertit les dates entre calendriers pour la génération GEDCOM.
| Méthode | Description |
|---|---|
toGedcomDate(array) |
Convertit un champ date JSONB { valeur, calendrier } en chaîne GEDCOM |
gregorianToGedcom(string) |
YYYY-MM-DD → D MON YYYY |
republicanToGregorian(string) |
"15 Vendémiaire An III" → "1794-10-06" |
Supporte les An I à XIV du calendrier républicain (1792–1806), avec les noms de mois accentués ou non.
GedcomExportService
Génère un fichier GEDCOM 5.5.1 à partir de relevés.
| Méthode | Description |
|---|---|
exportSource(Source) |
Exporte tous les relevés d'une source |
exportReleves(Collection, string) |
Exporte une sélection de relevés |
Détecte automatiquement le type d'événement (BIRT, MARR, DEAT) depuis le nom du type de source.
SiteSettingsService
Persistance des paramètres du site dans storage/app/site_settings.json. Interface statique.
| Méthode | Description |
|---|---|
get(key, default) |
Lire un paramètre |
set(key, value) |
Écrire un paramètre |
all() |
Tous les paramètres |
logoUrl() |
URL publique du logo (ou null) |
siteName() |
Nom du site |
smtpConfigured() |
SMTP configuré ? |
searchMaxResults() |
Limite de résultats de recherche (défaut : 200, min : 10) |
Paramètres gérés : site_name, logo_path, allow_registration, search_max_results, smtp.*, update.*
UpdateService
Gère la vérification et l'application des mises à jour depuis le serveur de releases (Gitea). Utilisé par les commandes Artisan app:check-update et app:update.
DbCompat
Classe utilitaire pour la compatibilité MySQL/PostgreSQL. Fournit des fragments SQL adaptés au SGBD actif.
| Méthode | Description |
|---|---|
isPgsql() |
Détecte si le SGBD est PostgreSQL |
like() |
ILIKE (pgsql) ou LIKE (mysql) |
ftsRaw() |
Expression de recherche plein texte native |
generatedJsonCol(key) |
Expression SQL pour colonne générée JSON |
jsonRegexRaw(col) |
Expression regex sur JSON |
10. Énumérations
UserRole
| Cas | Valeur | Label |
|---|---|---|
Admin |
admin |
Administrateur |
SectionManager |
section_manager |
Responsable de section |
Member |
member |
Membre |
SourceStatus
| Cas | Valeur | Label | Transitions possibles |
|---|---|---|---|
AFaire |
a_faire |
À faire | → en_cours |
EnCours |
en_cours |
En cours | → a_valider |
AValider |
a_valider |
À valider | → termine, → en_cours |
Termine |
termine |
Terminé | (aucune) |
CalendarType
| Cas | Valeur | Label |
|---|---|---|
Gregorien |
gregorien |
Grégorien |
Julien |
julien |
Julien |
Republicain |
republicain |
Républicain |
FieldType
text, date, boolean, select, textarea, number, place
11. Middlewares
CheckInstallation
Redirige vers /setup si le fichier storage/installed est absent. Redirige vers / si l'installation est déjà effectuée et qu'on accède à /setup.
RoleMiddleware
Usage : role:admin ou role:section_manager. Les administrateurs ont accès à toutes les routes quel que soit le rôle requis. Retourne HTTP 403 si le rôle est insuffisant.
EnsureUserIsActive
Déconnecte automatiquement un utilisateur dont is_active = false et affiche un message d'erreur.
12. Administration
L'interface d'administration (/admin) est réservée aux utilisateurs de rôle admin.
Gestion des utilisateurs
- Liste avec filtres (rôle, statut, recherche textuelle)
- Création manuelle (nom, e-mail, mot de passe, rôle)
- Modification (nom, e-mail, mot de passe optionnel, rôle)
- Suppression (protégée : impossible de supprimer le dernier admin ou son propre compte)
- Activation / désactivation (protégée : impossible de désactiver le dernier admin actif)
- Export CSV (avec les filtres actifs)
- Import CSV : colonnes
name,email,role,is_active(optionnel) ; mots de passe temporaires générés et affichés une seule fois
Paramètres du site
| Paramètre | Description |
|---|---|
| Nom du site | Affiché dans le titre et le header |
| Logo | Upload PNG/JPG/SVG (stocké dans storage/app/public/site/) |
| Inscriptions | Autoriser ou non les auto-inscriptions |
| Limite de résultats | Nombre maximum de résultats de recherche (10–5000, défaut 200) |
| SMTP | Serveur, port, chiffrement, identifiants (avec test d'envoi) |
| Mises à jour | Configuration de la vérification automatique |
| Lien de stockage | Recrée le symlink public/storage → storage/app/public |
Tableau de bord admin
Statistiques globales : nombre d'utilisateurs, sections, sources par statut, relevés, lieux.
13. Mises à jour
Le système de mise à jour récupère les releases depuis un serveur Gitea et applique automatiquement :
- Téléchargement de l'archive
.zipde la release - Extraction et copie des fichiers
composer install --no-devphp artisan migrate --forcephp artisan config:clear && php artisan view:clear
Commandes
php artisan app:check-update # Vérifie si une mise à jour est disponible
php artisan app:check-update --force # Ignore le cache
php artisan app:update # Télécharge et applique la mise à jour
php artisan app:update --check # Vérifie sans appliquer
php artisan app:update --force # Sans confirmation interactive
14. Commandes Artisan
| Commande | Description |
|---|---|
app:check-update |
Vérifie la disponibilité d'une mise à jour |
app:update |
Applique la dernière mise à jour |
php artisan migrate |
Applique les migrations en attente |
php artisan migrate:fresh --seed |
Recrée la base avec données de test |
php artisan view:clear |
Vide le cache des vues compilées |
php artisan config:clear |
Vide le cache de configuration |
php artisan storage:link |
Crée le lien symbolique public/storage |
php artisan test |
Lance la suite de tests |
./vendor/bin/pint |
Formatage du code PHP (Laravel Pint) |
./vendor/bin/phpstan analyse |
Analyse statique |
15. Performance et indexation
Index sur releves
| Index | Type | Colonne(s) |
|---|---|---|
releves_nom_idx |
B-tree | nom (colonne générée) |
releves_prenom_idx |
B-tree | prenom (colonne générée) |
releves_date_evenement_idx |
B-tree | date_evenement (colonne générée) |
releves_data_gin_idx |
GIN (PostgreSQL) | data (JSONB complet) |
Bonnes pratiques appliquées
- Eager loading systématique (
with()) pour éviter les requêtes N+1 - Pagination obligatoire (25 par page par défaut, jamais de SELECT sans LIMIT)
- Chargement différé des listes : les pages Lieux et Sources n'affichent aucun résultat sans filtre actif (prévient les requêtes portant sur des dizaines de milliers d'enregistrements)
- Cache Redis (TTL 1h) sur les listes fréquemment consultées
- Recherche par lieu : CTE récursive pour descendre dans la hiérarchie des lieux sans jointures multiples
Stockage des dates
Chaque champ de type date dans le JSONB stocke un objet :
{
"valeur": "1792-09-22",
"calendrier": "gregorien"
}
La colonne générée date_evenement extrait date_evenement.valeur pour permettre les tris et filtres sans parsing JSON à chaque requête.