Data
| Description | data |
| Author(s) | Tiogars |
| Repository | https://github.com/tiogars/data |
| Copyright | © 2026, Tiogars |
Table of Contents
Data¶
1 features
Features¶
Sections¶
1.1 management
Management¶
La gestion des données du projet repose sur un socle commun:
- création
- édition
- impression
- import
- export
- recherche
- sélection
- formats principaux: JSON et CSV
Convention Import / Export¶
Les operations sont exposees avec des routes dediees:
- Export JSON: GET /domain/export
- Import JSON: POST /domain/import
- Export CSV: GET /domain/export/csv
- Import CSV: POST /domain/import/csv
Exemples de domaines de management:
- /gtin
- /android
- /winget
- /brand
- /model
- /brick
- /car
- /car-mileage
- /url-manager
Convention Impression¶
Pour les domaines avec affichage de listes, l'impression suit:
- GET /domain/print
- Paramètre mode: filtered ou all
- Métadonnées de sortie: generatedAt et total
Couverture fonctionnelle par domaine¶
| Domaine | CRUD | Import / Export JSON | Import / Export CSV | Impression liste |
|---|---|---|---|---|
| GTIN | Oui | Oui | Oui | Oui |
| Android | Oui | Oui | Oui | Oui |
| Winget | Oui | Import JSON uniquement | Non | Non |
| Brand | Oui | Oui | Oui | Oui |
| Model | Oui | Oui | Oui | Oui |
| Brick | Oui | Oui | Oui | PDF / impression dédiée |
| Car | Oui | Oui | Oui | Non |
| CarMileage | Oui | Oui | Oui | Non |
| UrlManager | Oui | Oui | Oui | Non |
Notes:
- l'import / export reste documenté d'abord pour les domaines exposés via la gateway et l'interface Web
- Winget est déjà présent dans le code backend, Web et Flutter; sa documentation de flux CSV / impression reste à compléter si le besoin produit se confirme
- les domaines sans impression liste conservent les parcours de consultation et de saisie, mais sans endpoint
/print
Contrat API Import / Export¶
Tableau des endpoints¶
| Domaine | Export JSON | Import JSON | Export CSV | Import CSV |
|---|---|---|---|---|
| GTIN | GET /gtin/export | POST /gtin/import | GET /gtin/export/csv | POST /gtin/import/csv |
| Android | GET /android/export | POST /android/import | GET /android/export/csv | POST /android/import/csv |
| Winget | n/a | POST /winget/import | n/a | n/a |
| Brand | GET /brand/export | POST /brand/import | GET /brand/export/csv | POST /brand/import/csv |
| Model | GET /model/export | POST /model/import | GET /model/export/csv | POST /model/import/csv |
| Brick | GET /brick/export | POST /brick/import | GET /brick/export/csv | POST /brick/import/csv |
| Car | GET /car/export | POST /car/import | GET /car/export/csv | POST /car/import/csv |
| CarMileage | GET /car-mileage/export | POST /car-mileage/import | GET /car-mileage/export/csv | POST /car-mileage/import/csv |
| UrlManager | GET /url-manager/export | POST /url-manager/import | GET /url-manager/export/csv | POST /url-manager/import/csv |
Exemples GTIN¶
Import JSON:
{
"items": [
{ "code": "0123456789012", "description": "Produit A" },
{ "code": "9780201379624", "description": "Produit B" }
]
}
Import CSV:
Exemples Android¶
Import JSON:
{
"items": [
{
"name": "Google Keep",
"packageName": "com.google.android.keep",
"category": ["productivity", "notes"],
"description": "Application de prise de notes",
"icon": "https://example.org/keep.png"
}
]
}
Import CSV:
name,packageName,category,description,icon
Google Keep,com.google.android.keep,productivity|notes,Application de prise de notes,https://example.org/keep.png
Exemples Brand¶
Import JSON (format text):
Import JSON (format historique):
{
"items": [
{ "name": "Lego", "description": "Jeux de construction" },
{ "name": "Mattel", "description": "Jouets" }
]
}
Import CSV:
Exemples Model¶
Import JSON:
{
"items": [
{
"name": "Modele Catalogue",
"description": "Structure produit",
"modelAttributes": [
{ "name": "size", "description": "Taille" },
{ "name": "weight", "description": "Poids" }
]
}
]
}
Import CSV (attributes = name::description separes par |):
Exemples Brick¶
La gestion des liens externes se fait desormais via le menu: Bricks > Settings > External links.
Import JSON:
{
"bricks": [
{
"number": "60284",
"title": "Le camion de chantier",
"tags": ["city", "truck"],
"imageBase64": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUg..."
}
],
"externalLinks": [
{
"name": "BrickLink",
"url": "https://www.bricklink.com/v2/search.page?q=",
"enabled": true
}
]
}
Import CSV (mix type=brick et type=external-link):
type,brickNumber,brickTitle,brickTags,brickImageBase64,linkName,linkUrl,linkEnabled
brick,60284,Le camion de chantier,city|truck,data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...,,,
external-link,,,,,BrickLink,https://www.bricklink.com/v2/search.page?q=,true
Exemples UrlManager¶
Import JSON:
{
"urls": [
{
"label": "Board Sprint",
"url": "https://jira.exemple.fr/board/42",
"tags": ["sprint", "team-a"],
"description": "Tableau sprint"
}
],
"cards": [
{
"title": "Equipe Dev",
"tags": ["dev", "backend"],
"matchMode": "all"
}
]
}
Import CSV (mix type=url et type=card):
type,label,url,tags,description,title,matchMode
url,Board Sprint,https://jira.exemple.fr/board/42,sprint|team-a,Tableau sprint,,
card,,,dev|backend,,Equipe Dev,all
Notes de compatibilite¶
- Les endpoints /domain/export et /domain/import restent la reference JSON.
- Les endpoints /domain/export/csv et /domain/import/csv sont dedies aux flux fichier.
- Pour les imports CSV, le content-type accepte text/csv et text/plain.
1.2 business
UX Web¶
Cette section documente l'expérience utilisateur actuellement implémentée dans data-web.
Principes UX¶
- navigation par domaine avec pages dédiées
- parcours standards: liste -> détail -> création / édition
- rendu responsive: table sur desktop, cartes ou listes sur mobile
- formulaires cohérents avec MUI et validation côté API
- accès direct à la documentation et au support depuis l'interface
Parcours communs¶
Référentiels CRUD¶
Les domaines de référence suivent une structure homogène:
/domain/listpour consulter et filtrer/domain/createpour créer/domain/:idpour consulter le détail/domain/:id/editpour modifier
Domaines suivant ce schéma:
- Section
- Footer Link
- Menu Item
- GTIN
- Android
- Winget
- Brand
- Model
- Continent
- User Account
Parcours spécifiques¶
Certains domaines ont une UX dédiée:
| Domaine | Parcours web |
|---|---|
| Section | gestion hiérarchique via arborescence (/section/list) et paramétrage documentaire des racines (/section/settings/docs) — voir doc complète |
| GitHub Repository | recherche (/github-repository/search), création, détail, édition |
| GitHub Token Config | page de configuration (/github-token-config/search) |
| Url Manager | gestion centralisée des URLs (/url-manager) et des cartes (/url-cards) |
| Brick | liste, détail, et paramétrage des liens externes (/1.5-games/1.5.1-bricks/) — voir doc complète |
| Car | tableau de bord (/car/dashboard), liste, création, édition |
| Car Mileage | vue tableau (/car-mileage/table) et formulaire dédié (/car-mileage/form) |
| Android | liste principale et vue des releases (/android/releases) |
Responsive et impression¶
Les listes Web suivent les conventions suivantes:
- desktop (
md+): affichage tabulaire pour les données denses - mobile (
xs/sm): affichage carte ou liste avec les mêmes informations clés - impression des listes quand le domaine le supporte côté backend:
- mode
filtered - mode
all - génération côté client de formulaires vierges pour les flux de saisie papier quand la page le prévoit
Exemples déjà implémentés:
- impression de listes: Android, Model
- formulaires vierges: Car Mileage
Navigation transverse¶
Pages non métier disponibles dans l'application:
/: accueil/gateway-config: configuration de l'URL de gateway/auth-config: configuration de l'URL d'authentification/auth/account: compte OIDC courant/server-info/java-version: version Java du backend/server-info/jpa-entities: inventaire des entités JPA/icon-gallery: galerie d'icônes
Authentification¶
L'application Web s'appuie sur OIDC:
- callback de connexion:
/auth/callback - callback de déconnexion:
/auth/logout-callback - consultation du compte connecté:
/auth/account
Aide à la navigation¶
- l'en-tête expose un lien direct vers la documentation MkDocs
- le support ouvre la création d'issue GitHub
- les breadcrumbs et la barre latérale structurent la navigation entre domaines
1.2.1 sections
Sections - Hiérarchie documentaire¶
La rubrique Sections permet de gérer une arborescence de sections métier dans l'interface web, puis d'associer un emplacement documentaire à chaque section racine pour générer automatiquement des dossiers et des fichiers Markdown sous volumes/docs.
Objectif et périmètre¶
La feature couvre deux usages complémentaires :
- organiser un référentiel hiérarchique de sections et de sous-sections
- définir, pour chaque section racine, un chemin relatif de documentation
- générer automatiquement une arborescence de dossiers et de fichiers
index.md - mettre à jour cette arborescence quand une section est renommée, déplacée, réordonnée ou reconfigurée
Cette documentation concerne l'usage web actuellement disponible dans data-web.
Hors périmètre actuel :
- édition directe du contenu Markdown depuis l'interface
- aperçu temps réel des fichiers générés dans le navigateur
- impression spécifique de la hiérarchie des sections
Interface Web¶
Pages disponibles¶
| Page | Chemin | Description |
|---|---|---|
| Gestion des sections | /section/list |
Navigation hiérarchique, détail, création, modification et suppression |
| Paramètres docs | /section/settings/docs |
Association d'un chemin relatif à chaque section racine |
| Création directe | /section/create |
Création d'une section hors panneau principal |
| Détail d'une section | /section/:id |
Consultation d'une section précise |
| Édition d'une section | /section/:id/edit |
Modification d'une section précise |
Parcours utilisateur standard¶
Liste des sections (/section/list)
↓
Sélection d'une section dans l'arborescence MUI X Tree View
↓
Consultation du détail
↓
Modification / ajout d'un enfant / suppression
Parcours documentaire¶
Paramètres docs (/section/settings/docs)
↓
Choix d'une section racine
↓
Définition d'un chemin relatif sous volumes/docs
↓
Enregistrement
↓
Génération ou resynchronisation des dossiers et index.md
Parcours utilisateur et opérations courantes¶
Naviguer dans l'arborescence¶
- Ouvrez
/section/list - Utilisez le panneau Navigation à gauche
- Parcourez les niveaux grâce au composant MUI X Tree View
- Sélectionnez une section pour afficher son détail dans le panneau principal
L'arborescence affiche :
- le nom de la section
- sa description courte
- son niveau dans la hiérarchie
- son ordre d'affichage
Créer une section racine¶
- Depuis
/section/list, cliquez Nouvelle section - Renseignez :
- Nom
- Description
- Ordre d'affichage
- Section parente : laissez vide pour créer une racine
- Enregistrez
Créer une sous-section¶
- Sélectionnez une section existante dans l'arborescence
- Cliquez l'action Ajouter un enfant sur la ligne de la section sélectionnée
- Remplissez le formulaire
- Vérifiez que la section parente est préremplie
- Enregistrez
Modifier une section¶
- Sélectionnez la section dans l'arborescence
- Ouvrez l'onglet Édition
- Modifiez le nom, la description, l'ordre ou la parenté
- Cliquez Enregistrer
Effets attendus :
- la hiérarchie affichée est mise à jour
- la génération documentaire est resynchronisée si la section concernée fait partie d'une racine configurée
Supprimer une section¶
- Sélectionnez la section
- Cliquez Supprimer
- Confirmez la suppression
Effets attendus :
- la section et ses sous-sections sont supprimées
- l'arborescence documentaire correspondante est reconstruite ou supprimée selon la configuration restante
Configurer les paramètres docs d'une section racine¶
- Depuis
/section/list, cliquez Paramètres docs - Dans
/section/settings/docs, repérez la section racine à configurer - Saisissez un chemin relatif sous
volumes/docs, par exempleguides/produits - Cliquez Enregistrer
Après enregistrement, le backend génère une structure du type :
Le contenu généré suit ce format :
Règles métier et validations¶
Hiérarchie¶
- une section peut avoir zéro ou une section parente
- une section peut avoir plusieurs enfants
- une section ne peut pas être sa propre parente
- une section ne peut pas être déplacée sous une de ses propres sous-sections
Ordre d'affichage¶
- l'ordre d'affichage est un entier positif ou nul
- les sections soeurs sont triées par ordre d'affichage puis par nom
- l'index hiérarchique utilisé pour les dossiers et titres Markdown est calculé à partir de cet ordre
Paramètres docs¶
- seules les sections racines peuvent être configurées
- un seul chemin est autorisé par section racine
- le chemin doit être relatif à
volumes/docs - les chemins absolus sont refusés
- les segments
..sont refusés
Synchronisation documentaire¶
- la création d'une section met à jour l'arborescence si une racine configurée est concernée
- la modification d'un nom, d'un ordre, d'une parenté ou d'une description déclenche une resynchronisation
- la suppression reconstruit ou supprime l'arborescence documentaire concernée
- la modification des paramètres docs remplace l'implantation précédente par la nouvelle
Responsive mobile / desktop¶
Desktop¶
- arborescence à gauche et panneau de détail/édition à droite
- usage confortable du Tree View et des actions contextuelles
Mobile¶
- empilement vertical des panneaux
- conservation des mêmes informations essentielles
- accès aux paramètres docs inchangé via le bouton dédié
Dépannage¶
Le chemin est refusé¶
Vérifiez que :
- le chemin n'est pas vide
- le chemin ne commence pas par
/ - le chemin ne contient pas
.. - la section configurée est bien une racine
Les fichiers Markdown ne sont pas générés¶
Vérifiez que :
- la section racine possède une configuration dans
/section/settings/docs - le backend a accès au dossier
volumes/docs - le service
data-serverest démarré avec le montage du volume documentaire
L'arborescence générée ne reflète pas l'ordre attendu¶
Vérifiez l'ordre d'affichage de chaque section soeur. L'index hiérarchique et les noms de dossiers dépendent de cette valeur.
Concepts clés¶
- Section racine : section sans parent, seule éligible au paramétrage documentaire
- Sous-section : section rattachée à une autre section
- Ordre d'affichage : entier utilisé pour trier les sections soeurs et calculer l'index hiérarchique
- Index hiérarchique : séquence du type
1,1.2,1.2.3 - Chemin relatif : sous-chemin stocké dans l'application puis résolu sous
volumes/docs
1.3 mobile
1.3 Mobile Android¶
Cette section décrit l'application Flutter du monorepo, avec un focus principal sur Android et les flux offline-first.
Périmètre¶
- authentification OIDC
- tableau de bord et configuration de la gateway
- gestion des GTIN
- gestion des voitures et du kilométrage par voiture
- gestion des applications Android
- gestion des applications Winget quand la plateforme Flutter le permet
- stockage local SQLite + synchronisation serveur
Navigation¶
Écrans principaux¶
- Login: démarre la connexion OIDC et gère la reprise d'une session interrompue
- Dashboard: point d'entrée avec indicateurs, synchronisations et paramétrage runtime
- CRUD offline: chaque domaine ouvre ses listes, détails et formulaires hors ligne
Domaines couverts¶
| Domaine | Objectif mobile | Plateforme |
|---|---|---|
| GTIN | saisie et consultation hors ligne | Android |
| Car | gestion du parc et sélection pour les relevés | Android |
| CarMileage | saisie des relevés et historique local | Android |
| Android apps | inventaire applicatif Android | Android |
| Winget apps | inventaire applicatif Winget | Flutter Desktop / Windows selon disponibilité |
Specificites plate-forme¶
- Android: cible principale, avec APK debug et authentification OIDC mobile
- Windows: certains ecrans Flutter exposent aussi le domaine Winget pour les usages poste de travail
- Synchronisation: la gateway reste l'unique point d'acces reseau, quelle que soit la plateforme cliente
Setup Android (Flutter)¶
Prérequis¶
- Flutter SDK (compatible Dart
^3.12.2) - Android Studio + Android SDK
- un émulateur Android ou un appareil physique
- accès à une gateway locale ou distante compatible avec
data-gateway
Démarrage rapide¶
Build APK debug¶
Vérifications recommandées¶
Authentification mobile¶
L'application Android utilise OIDC avec Keycloak:
- client public:
data-mobile-android - redirect URI Android:
fr.tiogars.data:/oauth2redirect - scopes:
openid profile email offline_access
En cas d'erreur de retour de session:
- vérifier l'intent filter Android
- vérifier la redirect URI côté Keycloak
- relancer la connexion depuis l'écran
Login
Configuration runtime¶
Le paramétrage réseau se fait dans l'application:
- écran
Paramétrage gateway - URL locale par défaut orientée développement
- possibilité d'utiliser un JWT manuel pour les diagnostics
Avant un test sur appareil:
- vérifier que
data-gatewayrépond sur l'URL configurée - vérifier que le réseau de l'appareil permet d'atteindre la machine de développement
- adapter l'URL si
localhostn'est pas accessible depuis l'émulateur ou le téléphone
Spécificités Android¶
- l'application cible d'abord Android pour les domaines GTIN, Car, CarMileage et Android apps
- la synchronisation démarre au lancement puis au retour réseau
- l'APK debug est produit par Flutter dans
build/app/outputs/flutter-apk/
Dépannage¶
- Si l'émulateur n'est pas visible, exécuter
flutter devices. - Si le build échoue sur Android SDK, vérifier
sdkmanager --listet les licences Android. - Si l'API locale est inaccessible, vérifier que
data-gatewaytourne surhttp://localhost:8081. - Si la connexion OIDC expire, vérifier la configuration Keycloak, le schéma
fr.tiogars.data, et la réception du callback mobile.
Architecture Sync (Local + Serveur)¶
Principe¶
L'application mobile fonctionne en mode offline-first:
- écriture locale immédiate (SQLite)
- ajout d'une opération dans la file de synchro
- envoi asynchrone vers le serveur
- pull périodique des changements distants
Déclencheurs actuellement implémentés dans l'application:
- au démarrage de l'application (bootstrap)
- au retour réseau (changement de connectivité)
- en tâche périodique adaptative (30s, 2min ou 5min selon charge de file)
- push de la file locale vers gateway via
SyncEngine - pull serveur (refresh complet des listes) vers SQLite apres traitement de la file
- mode cursor-ready: tentative de pull incremental via curseur (
cursor/updatedAfter) si disponible, sinon fallback automatique en refresh complet
Domaines synchronisés¶
- GTIN
- Car
- CarMileage
- Android
Politique de conflit¶
Politique courante: serveur prioritaire.
En cas de conflit de version:
- la version serveur est conservée
- la version locale est écrasée
- l'utilisateur reçoit une notification métier si nécessaire
Endpoints API¶
Routes consommées via gateway:
/gtin/car/car-mileage/android/api/v1/sync/{domain}/changes
État local minimal¶
sync_queuepour les opérations en attentesync_statepour les curseurs de reprisedeleted_atdans les tables métier pour soft-delete local
Soft-delete local¶
Quand le backend renvoie deletedIds en synchro incrémentale:
- l'élément est masqué localement (
deleted_atnon null) - aucune suppression physique immédiate n'est faite
- l'utilisateur peut déclencher la purge explicite via l'action dédiée dans le dashboard
Sécurité¶
- Authentification JWT côté gateway
- token transmis via
Authorization: Bearer <token>
Fonctionnalités Android¶
GTIN¶
Capacités visées:
- création et édition en local
- recherche locale
- synchronisation serveur
Kilométrage des voitures¶
Capacités visées:
- sélection d'une voiture (
Car) - création de relevés (
CarMileage) - consultation de l'historique par voiture
- synchronisation locale/serveur
Applications Android¶
Capacités visées:
- gestion de la liste (nom, package, catégorie, description)
- édition hors ligne
- synchronisation serveur
Scénario type offline -> online¶
- l'utilisateur saisit des données hors connexion
- les données sont stockées en SQLite
- au retour réseau, la file de synchro est rejouée
- l'état local est réaligné avec la réponse serveur
Actions disponibles dans le dashboard¶
- appui sur une carte domaine pour saisir une nouvelle donnée hors ligne
- action "Synchroniser maintenant" dans la barre supérieure
- flux recommande pour le kilometrage: creer/synchroniser d'abord une voiture puis utiliser son identifiant backend (UUID)
1.4 cave
Cave - Collection de vins¶
La rubrique Cave permet de gérer une collection de vins dégustés, avec tous les domaines de référence associés.
Domaines¶
Domaines de référence¶
| Domaine | Description | Chemin API | Page web |
|---|---|---|---|
| Appellation | Appellations vinicoles (Champagne, Bordeaux, Bourgogne…) | /appellation |
/appellation/list |
| Couleur | Couleurs de vin (Blanc, Rouge, Rosé) | /couleur |
/couleur/list |
| Type de vin | Types de vin (Brut, Blanc de blancs, Rosé…) | /type-vin |
/type-vin/list |
| Cépage | Cépages (Chardonnay, Pinot noir, Pinot meunier…) | /cepage |
/cepage/list |
| Contenant | Contenants avec volume en cl (Bouteille, Magnum…) | /contenant |
/contenant/list |
| Circonstance | Circonstances de dégustation (Anniversaire, Mariage…) | /circonstance |
/circonstance/list |
| Maison | Maisons vinicoles avec site web | /maison |
/maison/list |
| Nom de vin | Noms de vins associés à une maison | /vin-nom |
/vin-nom/list |
| Tag vin | Tags libres pour les vins | /vin-tag |
/vin-tag/list |
Domaine principal¶
| Domaine | Description | Chemin API | Page web |
|---|---|---|---|
| Vin | Fiche complète d'un vin dégusté | /vin |
/vin/list |
Fonctionnalités¶
Chaque domaine dispose de :
- CRUD : création, lecture, modification, suppression (unitaire et globale)
- Recherche paginée :
GET /{domain}/search?page=0&size=10&q= - Export JSON :
GET /{domain}/export - Import JSON :
POST /{domain}/import - Export CSV :
GET /{domain}/export/csv - Import CSV :
POST /{domain}/import/csv - Impression :
GET /{domain}/print?mode=filtered|all - Interface web : liste, fiche, création, modification
Fiche Vin¶
La fiche d'un vin contient :
- Référence : appellation, couleur, type de vin, maison, nom du vin, contenant
- Géographie : année, commune, région
- Cépages : liste des cépages avec pourcentage d'assemblage
- Circonstances : liste des circonstances de dégustation
- Tags : tags libres
- Dégustation : commentaires, accords mets & vins
- Photos : stockage de photos associées
Contrat API Import / Export¶
| Domaine | Export JSON | Import JSON | Export CSV | Import CSV |
|---|---|---|---|---|
| Appellation | GET /appellation/export |
POST /appellation/import |
GET /appellation/export/csv |
POST /appellation/import/csv |
| Couleur | GET /couleur/export |
POST /couleur/import |
GET /couleur/export/csv |
POST /couleur/import/csv |
| Type de vin | GET /type-vin/export |
POST /type-vin/import |
GET /type-vin/export/csv |
POST /type-vin/import/csv |
| Cépage | GET /cepage/export |
POST /cepage/import |
GET /cepage/export/csv |
POST /cepage/import/csv |
| Contenant | GET /contenant/export |
POST /contenant/import |
GET /contenant/export/csv |
POST /contenant/import/csv |
| Circonstance | GET /circonstance/export |
POST /circonstance/import |
GET /circonstance/export/csv |
POST /circonstance/import/csv |
| Maison | GET /maison/export |
POST /maison/import |
GET /maison/export/csv |
POST /maison/import/csv |
| Nom de vin | GET /vin-nom/export |
POST /vin-nom/import |
GET /vin-nom/export/csv |
POST /vin-nom/import/csv |
| Tag vin | GET /vin-tag/export |
POST /vin-tag/import |
GET /vin-tag/export/csv |
POST /vin-tag/import/csv |
| Vin | GET /vin/export |
POST /vin/import |
— | — |
Architecture¶
Backend (data-server)¶
data-server/src/main/java/fr/tiogars/data/cave/
├── appellation/ # Appellations vinicoles
├── cepage/ # Cépages
├── circonstance/ # Circonstances de dégustation
├── contenant/ # Contenants
├── couleur/ # Couleurs de vin
├── maison/ # Maisons vinicoles
├── typevin/ # Types de vin
├── vin/ # Vins dégustés (domaine principal)
├── vinnom/ # Noms de vins
└── vintag/ # Tags de vins
Chaque sous-domaine suit le même pattern :
- controllers/ — contrôleurs REST (un par opération)
- entities/ — entités JPA
- forms/ — formulaires de création/import
- menu/ — contribution au menu (rubrique Cave)
- models/ — modèles de réponse (DTOs)
- repositories/ — repositories Spring Data JPA
- services/ — services métier (un par opération)
Gateway (data-gateway)¶
Les routes Cave sont exposées via GatewayRoutesConfiguration :
/appellation, /couleur, /circonstance, /contenant, /type-vin, /cepage, /maison, /vin-nom, /vin-tag, /vin
Frontend (data-web)¶
data-web/src/
├── services/
│ ├── appellationApi.ts
│ ├── cepageApi.ts
│ ├── circonstanceApi.ts
│ ├── contenantApi.ts
│ ├── couleurApi.ts
│ ├── maisonApi.ts
│ ├── typeVinApi.ts
│ ├── vinApi.ts
│ ├── vinNomApi.ts
│ └── vinTagApi.ts
└── pages/
├── appellation/ # List, Create, Detail, Edit
├── cepage/
├── circonstance/
├── contenant/
├── couleur/
├── maison/
├── typeVin/
├── vin/
├── vinNom/
└── vinTag/
Tests¶
Des tests d'intégration sont disponibles pour :
- AppellationApiIntegrationTest
- ContenantApiIntegrationTest
- MaisonApiIntegrationTest
- VinApiIntegrationTest
1.5 games
Games¶
- Bricks — cataloguer une collection de briques (LEGO ou autres)
1.5.1 bricks
Bricks - Collection de briques¶
La rubrique Bricks permet de gérer une collection personnelle de briques (LEGO ou autres), avec catalogage, recherche et liens de référence.
Objectif et périmètre¶
Les bricks sont des unités de construction individuelles. Cette feature vous permet de :
- Cataloguer votre collection complète
- Organiser par tags (thème, couleur, taille, etc.)
- Stocker des images pour chaque brique
- Consulter via des moteurs de recherche externes (BrickLink, BrickSet, etc.)
- Exporter/importer votre collection pour sauvegarde ou partage
Cette documentation couvre l'usage de la collection de bricks dans l'interface web.
Hors périmètre actuel :
- Synchronisation mobile Android
- Liens fonctionnels avec les autres collections du produit
- Gestion avancée d'inventaire pièce par pièce
Interface Web¶
Pages disponibles¶
| Page | Chemin | Description |
|---|---|---|
| Liste des bricks | /brick |
Vue paginée avec filtrage et actions |
| Détail d'une brick | /brick/:id |
Affichage complet avec actions (modifier, supprimer, partager) |
| Paramétrage | /brick/settings/external-links |
Gestion des liens de recherche externes |
Parcours utilisateur standard¶
Captures d'écran¶
Les captures ci-dessous illustrent un scénario de démonstration cohérent avec l'interface Bricks.
Liste desktop¶

Liste des bricks sur desktop avec recherche, actions d'import/export, tableau et pagination.
Vue mobile¶

Vue mobile de la collection avec cartes, recherche et bouton d'ajout flottant.
Formulaire de création¶

Exemple de formulaire de création ou de modification d'une brick.
Paramétrage des liens externes¶

Gestion des moteurs de recherche externes utilisables depuis la feature Bricks.
Parcours utilisateur et opérations courantes¶
Consulter votre collection¶
- Accédez à
/brickpour voir toutes les bricks - Sur desktop : tableau avec pagination
- Sur mobile : cartes avec navigation swipe
- Filtrez par tags ou recherchez par titre/numéro
Ajouter une brick¶
Via le formulaire :
- Sur la liste, cliquez + Ajouter (desktop) ou bouton flottant (mobile)
- Remplissez le formulaire :
- Numéro : identifiant unique (ex:
60284,75192) — ce champ est obligatoire - Titre : nom de la set/collection (ex: Le camion de chantier)
- Tags : classification (ex:
city,truck,2024,voiturette) - Image : cliquez pour uploader une photo de la brique
- Cliquez Ajouter
Modifier une brick¶
- Consultez le détail (cliquez Voir depuis la liste)
- Cliquez Modifier (ou icône d'édition)
- Mettez à jour les champs selon vos besoins
- Cliquez Enregistrer
Supprimer une brick¶
Depuis la liste : - Desktop : cliquez l'icône ✕ dans la rangée - Mobile : balayez à gauche ou cliquez Supprimer
Depuis le détail : 1. Cliquez Supprimer 2. Confirmez dans la boîte de dialogue
⚠️ Attention : la suppression est définitive et ne peut pas être annulée. Les données ne seront pas sauvegardées dans les exports suivants.
Rechercher et filtrer¶
- Barre de recherche : tapez pour filtrer par titre ou numéro
- Tags : cliquez sur un tag dans le détail pour voir d'autres bricks avec le même tag
- Pagination : navigez avec les flèches ou sélectionnez une taille de page (10, 20, 50)
Générer un PDF catalogue¶
- Sur la liste, cliquez PDF (icône 📄)
- Le catalogue complet se télécharge en PDF
- Chaque brick a une page dédiée avec titre, numéro, tags et image
- Idéal pour imprimer ou conserver un backup visuel
Règles métier et validations¶
Champs et contraintes¶
- Le numéro doit être unique : deux bricks ne peuvent pas partager le même numéro
- Le titre et le numéro sont obligatoires
- Les tags sont séparés par des espaces ou des points-virgules dans le formulaire
- Une brick peut avoir plusieurs tags pour le classement et la recherche
Image et formats acceptés¶
- L'image doit être au format PNG, JPEG, GIF ou WebP
- Taille maximale : 5 MB
- Résolution recommandée : 400 × 400 px ou plus
- Les images sont stockées en base64 pour rester transportables dans les exports
Effets importants¶
- La suppression est définitive et ne peut pas être annulée
- Un import peut remplacer les bricks existantes qui portent le même numéro
- Le numéro est l'identifiant métier de référence de la collection
Import / Export¶
Exporter votre collection¶
Deux formats de sortie :
JSON¶
- Cliquez Export JSON sur la liste
- Fichier
bricks-YYYYMMDD.jsontéléchargé
Format JSON :
{
"bricks": [
{
"number": "60284",
"title": "Le camion de chantier",
"tags": ["city", "truck"],
"imageBase64": "data:image/png;base64,iVBORw0KGgo..."
},
{
"number": "75192",
"title": "X-Wing Starfighter",
"tags": ["star-wars", "ship"]
}
],
"externalLinks": [
{
"name": "BrickLink",
"url": "https://www.bricklink.com/v2/search.page?q=",
"enabled": true
}
]
}
CSV¶
- Cliquez Export CSV sur la liste
- Fichier
bricks-YYYYMMDD.csvtéléchargé
Format CSV :
type,brickNumber,brickTitle,brickTags,brickImageBase64,linkName,linkUrl,linkEnabled
brick,60284,Le camion de chantier,city|truck,data:image/png;base64,iVBORw0KGgo...,,,
brick,75192,X-Wing Starfighter,star-wars|ship,,,
external-link,,,,,BrickLink,https://www.bricklink.com/v2/search.page?q=,true
Importer une collection¶
- Cliquez Importer (📥 icône ou bouton) sur la liste
- Sélectionnez votre fichier JSON ou CSV
- Vérifiez les données dans l'aperçu
- Cliquez Confirmer l'import
⚠️ Attention : les imports remplacent les bricks existantes avec le même numéro.
Format JSON attendu¶
{
"bricks": [
{
"number": "60284",
"title": "Le camion de chantier",
"tags": ["city", "truck"],
"imageBase64": "data:image/png;base64,iVBORw0KGgo..."
}
],
"externalLinks": [
{
"name": "BrickLink",
"url": "https://www.bricklink.com/v2/search.page?q=",
"enabled": true
}
]
}
Format CSV attendu¶
Colonnes requises (ordre libre) :
- type : brick ou external-link
- Pour type brick : brickNumber, brickTitle, brickTags (séparés par |), brickImageBase64 (optionnel)
- Pour type external-link : linkName, linkUrl, linkEnabled (true/false)
Responsive : desktop et mobile¶
L'interface conserve les mêmes informations principales sur desktop et mobile, avec une présentation adaptée.
Desktop¶
- Vue en tableau avec pagination
- Actions accessibles directement dans la ligne
- Bouton d'ajout visible dans la page de liste
Mobile¶
- Vue en cartes à la place du tableau
- Bouton flottant pour l'ajout
- Suppression possible par balayage ou via l'action dédiée
Point d'attention¶
- Le parcours recommandé reste : liste, détail, puis action de modification ou suppression
Liens externes¶
Ajouter/modifier des liens de recherche¶
Les liens externes vous permettent de rechercher rapidement des bricks sur des moteurs spécialisés.
- Allez à
/brick/settings/external-links - Cliquez + Ajouter un lien
- Remplissez :
- Nom : label du lien (ex: BrickLink)
- URL : template de recherche (ex:
https://www.bricklink.com/v2/search.page?q=) - Activé : cochez pour rendre le lien visible dans la liste
- Cliquez Enregistrer
Utiliser un lien externe¶
- Consultez le détail d'une brick
- Cliquez sur l'icône 🔗 à côté du lien
- Une nouvelle fenêtre s'ouvre avec la brick recherchée
⚠️ Le numéro de la brick est ajouté à la fin du template URL.
Concepts clés¶
Numéro de brick¶
Identifiant unique assigné lors de la création. Exemples :
- LEGO : 60284, 75192
- Brick générique : BK001, BK002
Le numéro ne peut pas être changé après création (modifiez le détail et recréez si nécessaire).
Tags¶
Mots-clés pour classifier votre collection. Utilisations courantes :
- Thème : city, star-wars, friends, ninjago
- Type : set, minifigure, accessoire
- Couleur : red, blue, transparent
- Taille : small, large, micro
- État : neuf, occasion, restauré
- Année : 2024, 2023, vintage
Un brick peut avoir plusieurs tags (ex: city|truck|2024).
Image¶
Stockée en base64 (texte) pour permettre la portabilité dans les exports. Formats supportés : - PNG, JPEG, GIF, WebP - Résolution recommandée : 400×400 px ou plus - Taille max : 5 MB
Aspects techniques utiles¶
- Les exports incluent à la fois les bricks et les liens externes configurés
- Les imports acceptent JSON et CSV avec un aperçu avant confirmation
- Le PDF catalogue génère une page dédiée par brick pour l'impression ou l'archivage
Endpoints disponibles¶
| Méthode | Endpoint | Description |
|---|---|---|
| GET | /brick/list |
Lister toutes les bricks |
| POST | /brick |
Créer une brick |
| GET | /brick/:id |
Récupérer une brick |
| PUT | /brick/:id |
Mettre à jour une brick |
| DELETE | /brick/:id |
Supprimer une brick |
| DELETE | /brick/all |
Supprimer toutes les bricks |
| GET | /brick/export |
Exporter en JSON |
| POST | /brick/import |
Importer depuis JSON |
| GET | /brick/export/csv |
Exporter en CSV |
| POST | /brick/import/csv |
Importer depuis CSV |
| GET | /brick/catalog/pdf |
Générer le catalogue PDF |
Modèles utiles¶
Brick :
{
"id": "uuid",
"number": "60284",
"title": "Le camion de chantier",
"tags": ["city", "truck"],
"imageBase64": "data:image/png;base64,..." (optionnel),
"createdAt": "2024-06-24T10:00:00Z",
"updatedAt": "2024-06-24T10:00:00Z"
}
External Link :
{
"id": "uuid",
"name": "BrickLink",
"url": "https://www.bricklink.com/v2/search.page?q=",
"enabled": true
}
Dépannage¶
« Le numéro de brique est déjà utilisé »¶
Deux bricks ont le même numéro. Solutions : - Vérifiez l'import (doublon dans le fichier CSV/JSON) - Modifiez le numéro d'une des deux bricks - Supprimez l'ancienne si elle est obsolète
Les images ne s'affichent pas après import¶
- Vérifiez que l'image est en base64 dans le fichier JSON
- Format attendu :
data:image/png;base64,iVBORw0KGgo... - CSV : l'image doit être codée en base64 dans la colonne
brickImageBase64
L'export PDF est vide¶
- Vérifiez que vous avez au moins une brick
- Si vide, une page blanche est générée (comportement attendu)
Limites et intégration avec d'autres features¶
- Mobile (Android) : bricks ne sont pas encore synchronisées sur l'app mobile (phase future)
- Cave (vins) : collections indépendantes, pas de lien direct
- Cars : collections indépendantes, pas de lien direct
Bonnes pratiques¶
- Sauvegardez régulièrement : exportez votre collection en JSON tous les mois
- Utilisez des tags cohérents : restez avec une convention (ex: toujours
star-warset jamaisstarwars) - Versionnez vos exports : mettez la date dans le nom du fichier (ex:
bricks-20240624.json) - Complétez les images : une collection photographiée est plus facile à naviguer
- Testez les liens externes : vérifiez les URLs des moteurs de recherche
2 development
Dévelopement¶
2.1 documentation
Documentation¶
Stack technique¶
Exemples :
- pnpm
- vite
- react
- java
- spring
Types de services¶
Exemples :
- api
- web
- gateway
- database
Note technique: CsvSupport (P2)¶
Le backend partage maintenant un module CSV commun pour reduire la duplication entre domaines:
- Module: data-server/src/main/java/fr/tiogars/data/common/csv/CsvSupport.java
- Capacites communes:
- detection du delimiteur (
,ou;) - parsing CSV (quotes, CRLF/LF, lignes vides)
- echappement CSV standard
- acces securise aux colonnes (
valueAt) - normalisation d'en-tetes (mode strict ou permissif)
- detection du delimiteur (
- Extensions prevues:
- escapeCsv(value, additionalQuoteTriggers...) pour besoins metier (ex:
|) - normalizeHeader(value, permissive) pour en-tetes heterogenes
- escapeCsv(value, additionalQuoteTriggers...) pour besoins metier (ex:
Architecture Import/Export (SRP)¶
Les flux import/export sont desormais decoupes par responsabilite:
- 1 service JSON export par domaine: DomainExportService
- 1 service JSON import par domaine: DomainImportService
- 1 service CSV export par domaine: DomainExportCsvService
- 1 service CSV import par domaine: DomainImportCsvService
- controllers separes pour import/export et json/csv
Cette decomposition evite les classes monolithiques de type ImportExportService et applique le principe de responsabilite unique.
Matrice de migration P2 (CSV)¶
| Domaine | Statut | Details |
|---|---|---|
| GTIN | Migre | Services dedies: GtinExportCsvService et GtinImportCsvService |
| Android | Migre | Services dedies: AndroidExportCsvService et AndroidImportCsvService |
| Brand | Migre | Services dedies: BrandExportCsvService et BrandImportCsvService |
| Model | Migre | Services dedies: ModelExportCsvService et ModelImportCsvService |
| Brick | Migre | Services dedies: BrickExportCsvService et BrickImportCsvService |
| UrlManager | Migre | Services dedies: UrlManagerExportCsvService et UrlManagerImportCsvService |
Regle d'evolution¶
Pour tout nouveau domaine CSV:
- Reutiliser CsvSupport en premier choix.
- Ajouter les adaptations metier via options (sans forker la logique CSV de base).
- Exposer les endpoints dedies: /domain/export/csv et /domain/import/csv.
- Valider la non-regression via tests d'integration import/export du domaine.
Instructions
Development Instructions & Code Standards¶
This section documents the principles, patterns, and rules that guide feature development across the data project. These instructions target both human developers and GitHub Copilot to ensure consistent, high-quality implementations.
Quick Navigation¶
| Section | Purpose |
|---|---|
| Introduction | Core principles & objectives |
| 00 - Architecture & Structure | How the codebase is organized |
| 01 - Frontend Patterns | React, MUI, state management conventions |
| 02 - Backend Patterns | Spring Boot, services, repositories patterns |
| 03 - Testing Conventions | Test organization, naming, strategies |
| 04 - Documentation Standards | Javadoc, comments, OpenAPI, user docs |
| 05 - Security Practices | Validation, authentication, authorization |
| 06 - Improvement Proposals | Future enhancements & evolution opportunities |
| 07 - Mobile Flutter | Android architecture, local persistence, server sync |
| 08 - IDE Alerts Review | Workflow to triage and resolve VS Code IDE diagnostics |
| P2 Migration History (CSV) | Consolidation status for shared CsvSupport migration |
General Principles¶
1. Responsive by Default¶
All web UI features must be responsive. Mobile-first layout with content stacking vertically; desktop views use tables for dense data; mobile replaces tables with card/list views maintaining full feature parity. Data list pages must also support printing for both current filtered results and full dataset.
2. API-Centric Design¶
Backend provides comprehensive OpenAPI documentation. Frontend consumes generated RTK Query APIs (not manual HTTP calls). API contracts are the source of truth.
3. Separation of Concerns¶
- Backend: Controllers → Services → Repositories (clear layering with validation in Services)
- Frontend: Pages → Components → Hooks (state management via Redux Toolkit + RTK Query)
- Features: Self-contained modules organized by domain
4. Type Safety¶
- TypeScript for frontend: strict mode, explicit types
- Java: generics, Java 25 features, no raw types
- OpenAPI schemas drive both client and server type generation
5. Testing as First-Class¶
Test code is as important as production code. New features require:
- Unit tests for business logic
- Integration tests for API endpoints
- Test naming reflects intent (e.g., shouldCreateUpdateAndDeleteBrick)
6. Documentation Everywhere¶
- API endpoints documented via OpenAPI annotations (
@Operation,@Schema) - Complex logic documented with inline comments (in French, matching project locale)
- User-facing features documented in MkDocs
How to Implement a New Feature¶
Phase 1: Backend¶
- Define domain entities (JPA
@Entity) - Create repositories (Spring Data
JpaRepository) - Implement service classes (business logic + validation)
- Create controllers (REST endpoints with OpenAPI annotations)
- Write integration tests
- For list screens, support print use cases in API contracts (filtered print and full print)
Phase 2: API Generation¶
- Run
pnpm -C data-web run openapi:pullto fetch API spec - Run
pnpm -C data-web run rtk:codegento regenerate client APIs - Verify generated services in
data-web/src/services/ - Ensure every domain API expected to be regenerated is declared in
data-web/openapi-config.tsunderoutputFilesbefore running codegen - If generation output is wrong, fix backend OpenAPI source and/or
data-web/openapi-config.ts, then rerun generation (never patch generated files directly)
Phase 3: Frontend¶
- Create pages/forms consuming generated APIs
- Use Redux Toolkit for complex state
- Apply MUI components with responsive patterns
- Write component tests
- Add print action on list pages with two modes: print filtered results and print all
Phase 4: Documentation & Validation¶
- Ensure OpenAPI spec is complete
- Add user-facing documentation in MkDocs
- Run full test suite
- Create PR with comprehensive description
Phase 5: Mobile (if feature is also exposed in Android app)¶
- Add/extend endpoints needed for mobile sync and conflict handling
- Implement SQLite mapping and sync queue in Flutter app
- Validate offline -> online reconciliation flows
- Update mobile docs in
docs/1-features/1.3-mobile/
Language Convention¶
- Code: English (class names, method names, variable names)
- Comments/Javadoc: French (matches project documentation locale)
- OpenAPI descriptions: French (user-facing)
- Commit messages: English or French (flexible)
References¶
00 - Architecture & Project Structure¶
Monorepo Organization¶
The data project is organized as a monorepo containing five main modules:
data/
├── data-web/ # React frontend
├── flutter_application/ # Flutter Android app
├── data-server/ # Spring Boot API server
├── data-gateway/ # Spring Cloud Gateway (routing, rate-limiting, auth)
├── docs/ # MkDocs documentation
├── docker-compose.yml # Local development orchestration
└── .github/ # CI/CD and configuration
Module Responsibilities¶
| Module | Role | Technology |
|---|---|---|
data-web |
User interface & client | React 19, TypeScript, Vite, MUI v9, Redux Toolkit |
flutter_application |
Mobile user interface | Flutter, Dart, SQLite |
data-server |
Business logic & data access | Spring Boot 4, Spring Data JPA, PostgreSQL, OpenAPI |
data-gateway |
API gateway & security | Spring Cloud Gateway MVC, OAuth2, Bucket4j (rate-limiting) |
docs |
Product & technical docs | MkDocs, Markdown |
Feature Organization Pattern¶
Features are organized by domain with consistent structure across frontend and backend.
Backend Feature Structure¶
data-server/src/main/java/fr/tiogars/data/dev/docs/<domain>/
├── controllers/ # @RestController classes
│ ├── <Entity>Controller.java
│ ├── <Entity>ByIdController.java
│ └── <Entity>PdfController.java (if applicable)
├── services/ # @Service classes (one per operation)
│ ├── <Entity>ListService.java
│ ├── <Entity>CreationService.java
│ ├── <Entity>DeletionService.java
│ ├── <Entity>UpdateService.java
│ └── <Entity>ModelMapper.java
├── repositories/ # Spring Data JpaRepository interfaces
│ └── <Entity>Repository.java
├── entities/ # JPA @Entity classes
│ └── <Entity>Entity.java
├── models/ # DTOs (response models)
│ └── <Entity>.java
├── forms/ # Request DTOs (form inputs)
│ ├── <Entity>CreationForm.java
│ ├── <Entity>UpdateForm.java
│ └── <Entity>SearchForm.java (if applicable)
└── [exception/] # Domain-specific exceptions (optional)
Pattern: Each service class handles a single responsibility (e.g., BrickCreationService only creates bricks). This makes testing and maintenance easier.
Frontend Feature Structure¶
data-web/src/
├── pages/<domain>/ # Page components
│ ├── <Entity>Page/
│ │ ├── index.tsx
│ │ ├── <Entity>Form.tsx
│ │ └── <Entity>List.tsx
├── components/<domain>/ # Reusable components
│ ├── <Entity>Form.tsx
│ ├── <Entity>Card.tsx
│ └── ...
├── features/<domain>/ # Redux slices & hooks
│ ├── types.ts # Domain types
│ └── iconRegistry.tsx (if needed)
└── services/ # Generated RTK Query APIs
├── <domain>Api.ts # Generated from OpenAPI
└── emptyApi.ts # Base split API
Mobile Feature Structure (Flutter)¶
flutter_application/lib/
├── app/ # app bootstrap & routing
├── core/ # api client, auth, db, sync engine
│ ├── api/
│ ├── database/
│ └── sync/
├── features/
│ ├── gtin/
│ ├── vehicles/ # car + car mileage
│ └── android_apps/
└── shared/ # common models, widgets, utils
Layering & Dependency Flow¶
Backend Layering (Spring)¶
REST Controllers
↓ (handles HTTP)
Service Layer
↓ (business logic)
Repository Layer
↓ (data access)
Database (PostgreSQL)
Rule: Controllers call Services, Services call Repositories. No skipping layers.
Validation: Performed in Service layer, not in controllers or entities.
Frontend Layering¶
Pages
↓ (compose & orchestrate)
Components + Hooks
↓ (UI & local logic)
Redux Toolkit Store + RTK Query
↓ (state & API calls)
Generated API Services
↓ (HTTP)
Gateway (localhost:8081)
↓
Backend (localhost:8080)
Rule: Pages orchestrate components. Components don't know about routing. Hooks encapsulate reusable logic.
Mobile Layering (Flutter)¶
Presentation (screens, state notifiers)
↓
Domain (use cases, business rules)
↓
Data (repositories, API client, SQLite)
↓
Gateway API (localhost:8081)
Rule: Offline-first by default for Android app. Writes are persisted locally and synchronized through a queue.
Key Conventions¶
Naming¶
| Item | Convention | Example |
|---|---|---|
| Java classes | PascalCase | BrickCreationService, BrickRepository |
| Java packages | lowercase, domain-based | fr.tiogars.data.dev.docs.brick.services |
| React components | PascalCase with .tsx |
BrickForm.tsx, BrickCard.tsx |
| React hooks | camelCase, prefix use |
useBrickForm(), useBrickList() |
| Dart classes | PascalCase | GtinSyncService, CarMileageRepository |
| Dart files | snake_case | gtin_sync_service.dart |
| Redux slices | camelCase | apiErrorSnackbar |
| API services (generated) | <domain>Api.ts |
sectionApi.ts, footerLinkApi.ts |
| Database tables | snake_case | brick, external_link |
Directories¶
- Plural for collections:
controllers/,services/,entities/,forms/,pages/,components/,features/ - Singular for modules:
data-web/,data-server/,data-gateway/(one per responsibility) - Mobile domain folders:
features/gtin,features/vehicles,features/android_apps
Local Development Structure¶
Environment Setup¶
# Launch entire stack
docker compose up --build --watch
# Or run modules separately:
pnpm -C data-web install && pnpm -C data-web dev
.\data-server\mvnw.cmd -f data-server\pom.xml spring-boot:run
.\data-gateway\mvnw.cmd -f data-gateway\pom.xml spring-boot:run
cd flutter_application
flutter pub get
flutter run -d emulator-5554
External Networks (Docker)¶
The project requires pre-existing Docker networks:
- data_network — internal communication
- npm_network — shared npm cache
Create once:
Port Configuration¶
| Service | Port | URL |
|---|---|---|
| Frontend (Vite) | 5173 | http://localhost:5173 |
| Gateway | 8081 | http://localhost:8081 |
| API Server | 8080 | http://localhost:8080 |
| Flutter Android (debug) | n/a | Android emulator/device |
| PostgreSQL | 5432 | (internal) |
| MkDocs | 8000 | http://localhost:8000/data/ |
| Grafana (observability) | 3000 | http://localhost:3000/ |
Adding a New Feature¶
Step 1: Identify the Domain¶
- Choose or create a domain folder (e.g.,
brick,section,footerLink) - Follow naming: singular lowercase (e.g.,
bricknotbricks)
Step 2: Backend¶
- Create entities, repositories, services following the pattern above
- Add OpenAPI annotations to controllers
- Write integration tests
- Run
mvn clean testto verify
Step 3: API Generation¶
- Run
pnpm -C data-web run openapi:pull - Run
pnpm -C data-web run rtk:codegen - Verify generated services exist in
data-web/src/services/
Step 4: Frontend¶
- Create pages using generated API services
- Use responsive patterns (MUI Grid, mobile-first)
- Write component tests
Step 5: Documentation¶
- Add OpenAPI descriptions (in French) to API operations
- Update MkDocs if user-facing behavior changed
- Link new feature in sidebar if applicable
Cross-Cutting Concerns¶
Error Handling¶
Backend: Use @RestControllerAdvice with @ExceptionHandler (see GlobalControllerExceptionHandler)
Frontend: RTK Query errors captured by Redux middleware (rtkQueryErrorSnackbarMiddleware), displayed as snackbar
Mobile: Offline saves enqueue operations in local SQLite queue; sync failures are retried via SyncEngine and attempts counter.
Logging & Observability¶
- Backend: Use Spring Actuator (
/actuator/health,/actuator/metrics) - Frontend: Browser DevTools + network inspector
- Monitoring: Grafana (localhost:3000) reads LGTM stack (Loki, Grafana, Tempo, Mimir)
Security¶
- Gateway: OAuth2 Resource Server, rate-limiting via Bucket4j
- Validation: Input validation in service layer (not controller)
- Database: Parameterized queries (Spring Data JPA handles this)
See Security Practices for details.
References¶
01 - Frontend Patterns (React, MUI, TypeScript)¶
Component Architecture¶
Functional Components (FC) with TypeScript¶
All components are functional components with explicit type annotations.
import { FC, ReactNode } from 'react';
import Box from '@mui/material/Box';
type MyComponentProps = {
children: ReactNode;
disabled?: boolean;
};
export const MyComponent: FC<MyComponentProps> = ({ children, disabled = false }) => {
return (
<Box sx={{ /* styles */ }}>
{children}
</Box>
);
};
Rules:
- Use FC type for functional components
- Define props as a separate type (not inline)
- Props type name: <ComponentName>Props
- Export components explicitly
- Use named exports (not default)
Component Organization¶
Page Components¶
Pages are in pages/<domain>/ and orchestrate features:
import { FC } from 'react';
import { useListBricksQuery } from '../../services/brickApi';
import { BrickForm } from '../../components/BrickForm';
import { BrickTable } from '../../components/BrickTable';
export const BrickPage: FC = () => {
const { data, loading, error } = useListBricksQuery();
return (
<div>
<BrickForm onSubmit={/* ... */} />
<BrickTable items={data?.items ?? []} loading={loading} />
</div>
);
};
Responsibilities: - Fetch data from API services - Manage page-level state (e.g., filters) - Compose multiple components - Handle navigation
Feature Components¶
Reusable domain-specific components in components/<domain>/:
// BrickForm.tsx - form for creating/editing a brick
type BrickFormProps = {
onSubmit: (data: BrickCreationForm) => Promise<void>;
loading?: boolean;
};
export const BrickForm: FC<BrickFormProps> = ({ onSubmit, loading = false }) => {
// Component logic
};
// BrickCard.tsx - displays a single brick
type BrickCardProps = {
brick: Brick;
onDelete?: (id: string) => void;
};
export const BrickCard: FC<BrickCardProps> = ({ brick, onDelete }) => {
// Component logic
};
Rules: - Components are single-responsibility - Props are immutable (no mutation) - Callbacks passed as props - No direct routing calls (pages handle navigation)
Presentational Components¶
Generic, reusable components in components/:
State Management¶
Redux Toolkit + RTK Query¶
Redux Toolkit manages app-wide state. RTK Query handles server state (caching, refetching).
Store Setup¶
// store.ts
import { configureStore } from '@reduxjs/toolkit';
import { emptySplitApi } from './services/emptyApi';
import { apiErrorSnackbarReducer } from './features/apiErrorSnackbar/slice';
import { rtkQueryErrorSnackbarMiddleware } from './middleware/rtkQueryErrorSnackbarMiddleware';
export const store = configureStore({
reducer: {
apiErrorSnackbar: apiErrorSnackbarReducer,
[emptySplitApi.reducerPath]: emptySplitApi.reducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(rtkQueryErrorSnackbarMiddleware, emptySplitApi.middleware),
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
Feature Slices¶
Local domain state in features/<domain>/:
// features/menuItem/slice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
type MenuItemState = {
selectedId: string | null;
editMode: boolean;
};
const initialState: MenuItemState = {
selectedId: null,
editMode: false,
};
export const menuItemSlice = createSlice({
name: 'menuItem',
initialState,
reducers: {
selectItem: (state, action: PayloadAction<string>) => {
state.selectedId = action.payload;
},
toggleEditMode: (state) => {
state.editMode = !state.editMode;
},
},
});
export const { selectItem, toggleEditMode } = menuItemSlice.actions;
export const menuItemReducer = menuItemSlice.reducer;
Rules:
- One slice per domain feature
- Use PayloadAction<T> for type safety
- Keep slices small (avoid mega-reducers)
- Redux for UI state (not server state)
Generated API Services¶
API services are auto-generated from OpenAPI specs:
// Generated: services/brickApi.ts
import { emptySplitApi } from './emptyApi';
export const brickApi = emptySplitApi.injectEndpoints({
endpoints: (builder) => ({
listBricks: builder.query<BrickListResponse, void>({
query: () => '/brick',
}),
getBrickById: builder.query<Brick, string>({
query: (id) => `/brick/${id}`,
}),
createBrick: builder.mutation<Brick, BrickCreationForm>({
query: (form) => ({
url: '/brick',
method: 'POST',
body: form,
}),
invalidatesTags: ['Brick'], // Auto-refetch list
}),
}),
});
export const { useListBricksQuery, useGetBrickByIdQuery, useCreateBrickMutation } = brickApi;
CRITICAL: Generated files are read-only. Do not manually edit.
To regenerate after API changes:
Additional generation rules:
- Always run openapi:pull before rtk:codegen to avoid stale endpoints.
- Every domain API expected to be generated must be declared in data-web/openapi-config.ts under outputFiles (for example ./src/services/androidApi.ts).
- If a generated endpoint is wrong, update backend OpenAPI source and/or openapi-config.ts, then regenerate. Never patch generated files directly.
Form Handling with React Hook Form¶
Forms use react-hook-form + MUI components + zod (optional validation).
import { useForm, useFormContext, FormProvider } from 'react-hook-form';
import Stack from '@mui/material/Stack';
import TextField from '@mui/material/TextField';
import Button from '@mui/material/Button';
type BrickFormValues = {
number: string;
title: string;
tags: string[];
imageBase64?: string;
};
export const BrickForm: FC<{ onSubmit: (data: BrickFormValues) => Promise<void> }> = ({ onSubmit }) => {
const methods = useForm<BrickFormValues>({
defaultValues: {
number: '',
title: '',
tags: [],
},
});
return (
<FormProvider {...methods}>
<form onSubmit={methods.handleSubmit(onSubmit)}>
<Stack spacing={2}>
<BrickNumberField />
<BrickTitleField />
<BrickTagsField />
<Button type="submit" variant="contained">
Save
</Button>
</Stack>
</form>
</FormProvider>
);
};
// Field component (extracts logic)
const BrickNumberField: FC = () => {
const {
register,
formState: { errors },
} = useFormContext<BrickFormValues>();
return (
<TextField
label=\"Brick Number\"
fullWidth
error={Boolean(errors.number)}
helperText={errors.number?.message ?? 'Unique brick reference.'}
{...register('number', {
required: 'Number is required.',
validate: (value) => value.trim().length > 0 || 'Number cannot be empty.',
})}
/>
);
};
Rules:
- Use FormProvider to share form state across nested components
- Extract field components to keep forms readable
- Validation in register() or via external schema
- Use MUI's error and helperText props for error display
- Loading states managed via useFormState() or dedicated Redux slice
Responsive Design (Mobile-First)¶
MUI sx Prop for Styling¶
Use sx prop for inline styles (preferred):
<Box
sx={{
display: 'flex',
flexDirection: { xs: 'column', md: 'row' },
gap: 2,
}}
>
{/* Children */}
</Box>
Desktop Table vs Mobile Card View¶
Desktop:
import { DataGrid, GridColDef } from '@mui/x-data-grid';
const columns: GridColDef[] = [
{ field: 'id', headerName: 'ID', width: 100 },
{ field: 'title', headerName: 'Title', width: 300 },
// ...
];
export const BrickTable: FC<{ bricks: Brick[] }> = ({ bricks }) => (
<DataGrid rows={bricks} columns={columns} />
);
Mobile:
export const BrickCardList: FC<{ bricks: Brick[] }> = ({ bricks }) => (
<Stack spacing={2}>
{bricks.map((brick) => (
<BrickCard key={brick.id} brick={brick} />
))}
</Stack>
);
Page-level Decision:
import { useMediaQuery } from '@mui/material';
export const BrickListPage: FC = () => {
const isDesktop = useMediaQuery('(min-width: 960px)');
const { data } = useListBricksQuery();
return isDesktop ? (
<BrickTable bricks={data?.items ?? []} />
) : (
<BrickCardList bricks={data?.items ?? []} />
);
};
Rules:
- Mobile-first: stack vertically, minimal horizontal scrolling
- Use MUI breakpoints (xs, sm, md, lg, xl)
- Maintain feature parity between views
- Touch-friendly targets (buttons ≥ 48px)
List Printing UX (Required)¶
All data list pages must provide a print action with two modes: - Print filtered results (uses current search/filter/sort context) - Print all results (ignores active filters when allowed by business rules)
Recommended implementation rules:
- Add a visible Print action in page-level toolbar
- Keep print output aligned with backend response to avoid data mismatch
- Use a print-focused view (@media print) that hides navigation/actions
- Include print metadata: domain title, timestamp, and printed total
type PrintMode = 'filtered' | 'all';
const handlePrint = async (mode: PrintMode) => {
const payload = {
mode,
filters: mode === 'filtered' ? activeFilters : undefined,
sort: activeSort,
};
const response = await printList(payload).unwrap();
setPrintData(response);
window.print();
};
Form Printing UX (Required)¶
For domains with data-entry workflows, provide blank paper forms for pre-entry data collection (data collected on paper before being typed into the software).
Two form types must be supported:
- Unitaire (formulaire vierge): a blank form with all fields and write lines for a single record
- Listing (listing vierge): a blank table with column headers and empty rows for batch entry of multiple records
Implementation rules:
- Add Formulaire vierge and Listing vierge buttons in the page toolbar
- Both are generated client-side as HTML strings and opened in a new browser tab — no API call required
- Unit form: use labeled field rows with a write line below each label
- Listing form: use a table with column headers and 20 empty rows by default
- Include a title, domain name, and print date in the form header
- Apply @media print CSS to hide browser chrome and optimize margins
function escapeHtml(value: string): string {
return value
.replaceAll('&', '&')
.replaceAll('<', '<')
.replaceAll('>', '>')
.replaceAll('"', '"')
.replaceAll("'", ''');
}
function printUnitFormHtml(title: string, fields: string[]): string {
const date = new Date().toLocaleDateString('fr-FR');
const fieldRows = fields
.map((f) => `<div class="field"><label>${escapeHtml(f)} :</label><span class="line"></span></div>`)
.join('');
return `<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>${escapeHtml(title)}</title>
<style>
body { font-family: Arial, sans-serif; margin: 24px; }
h1 { margin: 0 0 4px 0; font-size: 18px; }
.meta { margin-bottom: 24px; color: #666; font-size: 12px; }
.field { margin-bottom: 20px; }
label { display: block; font-size: 13px; color: #333; margin-bottom: 4px; }
.line { border: none; border-bottom: 1px solid #999; width: 100%; height: 28px; display: block; }
@media print { body { margin: 12mm; } }
</style>
</head>
<body>
<h1>${escapeHtml(title)}</h1>
<div class="meta">Imprime le : ${escapeHtml(date)}</div>
${fieldRows}
</body>
</html>`;
}
function printListingFormHtml(title: string, columns: string[], rowCount = 20): string {
const date = new Date().toLocaleDateString('fr-FR');
const headerCells = columns.map((c) => `<th>${escapeHtml(c)}</th>`).join('');
const emptyRow = `<tr>${columns.map(() => '<td> </td>').join('')}</tr>`;
const emptyRows = Array.from({ length: rowCount }, () => emptyRow).join('');
return `<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>${escapeHtml(title)}</title>
<style>
body { font-family: Arial, sans-serif; margin: 24px; }
h1 { margin: 0 0 4px 0; font-size: 18px; }
.meta { margin-bottom: 16px; color: #666; font-size: 12px; }
table { border-collapse: collapse; width: 100%; }
th, td { border: 1px solid #999; padding: 8px; text-align: left; height: 28px; }
th { background: #f5f5f5; font-size: 13px; }
td { font-size: 12px; }
@media print { body { margin: 12mm; } }
</style>
</head>
<body>
<h1>${escapeHtml(title)}</h1>
<div class="meta">Imprime le : ${escapeHtml(date)}</div>
<table>
<thead><tr>${headerCells}</tr></thead>
<tbody>${emptyRows}</tbody>
</table>
</body>
</html>`;
}
// Open form in a new tab for printing
const handlePrintForm = (html: string) => {
const blob = new Blob([html], { type: 'text/html' });
const url = URL.createObjectURL(blob);
window.open(url, '_blank');
};
MUI v9 Typography & Styling¶
import Typography from '@mui/material/Typography';
// GOOD: Use sx for fontWeight
<Typography sx={{ fontWeight: 600 }}>Bold text</Typography>
// AVOID: Direct prop fontWeight (TS overload issues)
// <Typography fontWeight={600}>Bold</Typography>
Hooks & Custom Logic¶
Custom Hooks Pattern¶
Extract reusable logic into custom hooks:
// useSectionFormControllers.ts
export const useSectionFormControllers = () => {
const [editingId, setEditingId] = useState<string | null>(null);
const { data: sections } = useListSectionsQuery();
const [createSection] = useCreateSectionMutation();
const [updateSection] = useUpdateSectionMutation();
const handleCreate = async (form: SectionCreationForm) => {
await createSection(form).unwrap();
};
return {
editingId,
setEditingId,
sections,
handleCreate,
// ...
};
};
Rules:
- Custom hooks prefix with use
- Encapsulate related state + effects
- Return object for easy extension
- No side effects on initial call (wrap in useEffect)
Error Handling¶
Errors are captured by RTK Query and displayed via Redux middleware:
// middleware/rtkQueryErrorSnackbarMiddleware.ts
// Auto-catches API errors, dispatches snackbar action
// Usage: Any API error → snackbar appears automatically
Manual error handling:
const [createBrick] = useCreateBrickMutation();
const handleSubmit = async (form: BrickCreationForm) => {
try {
await createBrick(form).unwrap();
// Success
} catch (error) {
// Handle error (already shown in snackbar)
console.error('Failed to create brick:', error);
}
};
Accessibility (A11y)¶
Required for all components:
- Semantic HTML (<button>, <form>, <label>)
- ARIA labels for icons: <IconButton aria-label="delete" />
- Color not only distinguisher (use patterns + text)
- Keyboard navigation support (MUI buttons by default)
- Focus visible indicators
- Alt text for images
import IconButton from '@mui/material/IconButton';
import DeleteIcon from '@mui/icons-material/Delete';
<IconButton
aria-label="delete brick"
onClick={() => deleteBrick(id)}
>
<DeleteIcon />
</IconButton>
Testing Components¶
Test files colocated with components or in __tests__ folder:
// BrickForm.test.tsx
import { render, screen } from '@testing-library/react';
import { BrickForm } from './BrickForm';
describe('BrickForm', () => {
it('should display the brick number field', () => {
render(<BrickForm onSubmit={vi.fn()} />);
expect(screen.getByLabelText(/brick number/i)).toBeInTheDocument();
});
});
See Testing Conventions for full details.
References¶
02 - Backend Patterns (Spring Boot, Services, Repositories)¶
Layered Architecture¶
Backend follows a strict three-layer architecture:
REST Controllers (HTTP handling)
↓
Service Layer (business logic)
↓
Repository Layer (data access)
↓
Database (PostgreSQL)
Rule: Each layer has a single responsibility. No skipping layers.
Controllers (REST Endpoints)¶
Controller Design¶
Controllers are thin HTTP adapters. They delegate to services:
package fr.tiogars.data.dev.docs.brick.controllers;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
@RestController
@Tag(name = "brick", description = "Gestion de la collection de briques et des liens externes associes.")
public class BrickController {
private final BrickListService brickListService;
private final BrickCreationService brickCreationService;
private final BrickDeleteAllService brickDeleteAllService;
public BrickController(
BrickListService brickListService,
BrickCreationService brickCreationService,
BrickDeleteAllService brickDeleteAllService
) {
this.brickListService = brickListService;
this.brickCreationService = brickCreationService;
this.brickDeleteAllService = brickDeleteAllService;
}
@GetMapping(\"/brick\")
@Operation(summary = \"Lister les briques\", description = \"Retourne la collection complete de briques.\")
public ResponseEntity<BrickListResponse> listBricks() {
return ResponseEntity.ok(brickListService.listBricks());
}
@PostMapping(\"/brick\")
@Operation(summary = \"Creer une brique\", description = \"Ajoute une nouvelle brique a la collection.\")
public ResponseEntity<Brick> createBrick(@RequestBody BrickCreationForm form) {
return ResponseEntity.ok(brickCreationService.createBrick(form));
}
@DeleteMapping(\"/brick\")
@Operation(summary = \"Supprimer toutes les briques\", description = \"Supprime l'ensemble des briques de la collection.\")
public ResponseEntity<Void> deleteAllBricks() {
brickDeleteAllService.deleteAllBricks();
return ResponseEntity.noContent().build();
}
}
Rules:
- Inject services via constructor dependency injection
- One service per operation (keep controllers thin)
- Use OpenAPI annotations (@Tag, @Operation) on all endpoints
- Return appropriate HTTP status codes (200, 201, 204, 400, 404, 500)
- No business logic in controllers
List Printing Support (Required)¶
For domains exposing list screens, backend must support printing both full and filtered data.
Contract and behavior rules:
- Reuse list filtering/sorting parameters for print requests
- Support two print modes: filtered and all
- Return stable ordering matching UI sort order
- Include print metadata (generatedAt, total) in the response
- Protect performance with bounded queries and validation on filter inputs
Suggested endpoint style:
@PostMapping("/brick/print")
@Operation(summary = "Imprimer la liste des briques", description = "Retourne les donnees imprimees en mode filtre ou complet.")
public ResponseEntity<BrickPrintResponse> printBricks(@RequestBody BrickPrintRequest request) {
return ResponseEntity.ok(brickPrintService.printBricks(request));
}
Separation of Concerns: One Service Per Operation¶
Instead of a single BrickService:
// GOOD: Separate services for each operation
- BrickListService
- BrickCreationService
- BrickUpdateService
- BrickDeleteOneService
- BrickDeleteAllService
- BrickGetOneService
Why? Single Responsibility Principle makes testing and maintenance easier.
Service Layer (Business Logic)¶
Services contain all business logic, validation, and error handling.
Service Structure¶
package fr.tiogars.data.dev.docs.brick.services;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class BrickCreationService {
private final BrickRepository brickRepository;
// Constructor injection (required for testing)
public BrickCreationService(BrickRepository brickRepository) {
this.brickRepository = brickRepository;
}
@Transactional
public Brick createBrick(BrickCreationForm form) {
// 1. Validate input
validateUniqueNumber(form.getNumber(), null);
// 2. Create entity
BrickEntity entity = new BrickEntity();
applyValues(entity, form.getNumber(), form.getTitle(), form.getTags(), form.getImageBase64());
// 3. Persist
return BrickModelMapper.toModel(brickRepository.save(entity));
}
private void validateUniqueNumber(String number, String excludeId) {
Optional<BrickEntity> existing = brickRepository.findByNumber(number);
if (existing.isPresent() && !existing.get().getId().equals(excludeId)) {
throw new IllegalArgumentException(\"Le numero de brique est deja utilise: \" + number);
}
}
static void applyValues(BrickEntity entity, String number, String title, List<String> tags, String imageBase64) {
entity.setNumber(requireText(number, \"Le numero de brique est obligatoire.\"));
entity.setTitle(requireText(title, \"Le titre de la brique est obligatoire.\"));
entity.setTags(BrickModelMapper.tagsToCsv(normalizeTags(tags)));
entity.setImageBase64(normalizeNullableText(imageBase64));
}
private static String requireText(String value, String message) {
if (value == null || value.trim().isEmpty()) {
throw new IllegalArgumentException(message);
}
return value.trim();
}
}
Rules:
- Use @Transactional for methods that modify data
- Validate inputs early (fail fast)
- Throw IllegalArgumentException for validation errors (caught by @RestControllerAdvice)
- Inject repositories, not entities
- Keep methods focused (do one thing)
- Use helper static methods for repeated logic
Validation Pattern¶
Validation happens in the Service layer, not in controllers or entities:
public void validateBrick(BrickCreationForm form) {
if (form.getNumber() == null || form.getNumber().trim().isEmpty()) {
throw new IllegalArgumentException(\"Le numero est obligatoire.\");
}
if (form.getTitle() == null || form.getTitle().trim().isEmpty()) {
throw new IllegalArgumentException(\"Le titre est obligatoire.\");
}
// Check uniqueness
brickRepository.findByNumber(form.getNumber()).ifPresent(existing -> {
throw new IllegalArgumentException(\"Le numero est deja utilise.\");
});
}
Repository Layer (Data Access)¶
Repositories are Spring Data JpaRepository interfaces:
package fr.tiogars.data.dev.docs.brick.repositories;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
public interface BrickRepository extends JpaRepository<BrickEntity, String> {
Optional<BrickEntity> findByNumber(String number);
// Custom queries can be added, but keep them simple
// List<BrickEntity> findByTagsContaining(String tag);
}
Rules:
- Extend JpaRepository<Entity, IdType>
- Declare as @Repository if not extending JpaRepository
- Use Spring Data query methods (avoid custom @Query)
- Let services handle complex query logic
Entity & DTO Patterns¶
Entities (JPA)¶
Entities represent database tables:
package fr.tiogars.data.dev.docs.brick.entities;
import jakarta.persistence.*;
import java.time.Instant;
@Entity
@Table(name = \"brick\")
public class BrickEntity {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
@Column(name = \"id\", nullable = false, updatable = false)
private String id;
@Column(name = \"number\", nullable = false, unique = true)
private String number;
@Column(name = \"title\", nullable = false)
private String title;
@Column(name = \"tags\", nullable = false, length = 4000)
private String tags; // CSV format
@Column(name = \"image_base64\", columnDefinition = \"TEXT\")
private String imageBase64;
@Column(name = \"created_at\", nullable = false, updatable = false)
private Instant createdAt;
@Column(name = \"updated_at\", nullable = false)
private Instant updatedAt;
// Lifecycle hooks
@PrePersist
void onCreate() {
Instant now = Instant.now();
if (createdAt == null) {
createdAt = now;
}
updatedAt = now;
}
@PreUpdate
void onUpdate() {
updatedAt = Instant.now();
}
// Getters and setters (generated by IDE)
public String getId() { return id; }
public void setId(String id) { this.id = id; }
// ... etc
}
Rules:
- Use @Entity and @Table annotations
- Use @Id with @GeneratedValue(strategy = GenerationType.UUID)
- Database column names are snake_case
- Use @PrePersist and @PreUpdate for timestamps
- No business logic in entities
DTOs (Data Transfer Objects)¶
Response Models (exported to API)¶
package fr.tiogars.data.dev.docs.brick.models;
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.Instant;
import java.util.List;
public class Brick {
@Schema(description = \"Identifiant unique de la brique.\", example = \"123e4567-e89b-12d3-a456-426614174000\")
private String id;
@Schema(description = \"Numero de reference de la brique.\", example = \"60284\")
private String number;
@Schema(description = \"Titre de la brique.\", example = \"Le camion de chantier\")
private String title;
@Schema(description = \"Tags de classification de la brique.\", example = \"[\\\"city\\\",\\\"truck\\\"]\")
private List<String> tags;
@Schema(description = \"Image en data URL base64.\")
private String imageBase64;
@Schema(description = \"Date de creation.\", example = \"2025-12-22T12:14:59.569Z\")
private Instant createdAt;
@Schema(description = \"Date de derniere modification.\")
private Instant updatedAt;
// Getters and setters
}
Request Forms (input validation)¶
package fr.tiogars.data.dev.docs.brick.forms;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.List;
public class BrickCreationForm {
@Schema(description = \"Numero de reference.\", example = \"60284\")
private String number;
@Schema(description = \"Titre.\", example = \"Le camion de chantier\")
private String title;
@Schema(description = \"Tags de classification.\")
private List<String> tags;
@Schema(description = \"Image en data URL base64 (optional).\")
private String imageBase64;
// Getters and setters
}
Rules:
- Response models: immutable from client perspective, all fields with @Schema
- Request forms: mutable, only fields needed for input
- All public fields have @Schema documentation (French)
- Use descriptive examples in schemas
Model Mappers¶
Mappers convert entities ↔ DTOs:
package fr.tiogars.data.dev.docs.brick.services;
import fr.tiogars.data.dev.docs.brick.entities.BrickEntity;
import fr.tiogars.data.dev.docs.brick.models.Brick;
import java.util.List;
final class BrickModelMapper {
private BrickModelMapper() {
}
static Brick toModel(BrickEntity entity) {
Brick model = new Brick();
model.setId(entity.getId());
model.setNumber(entity.getNumber());
model.setTitle(entity.getTitle());
model.setTags(csvToTags(entity.getTags()));
model.setImageBase64(entity.getImageBase64());
model.setCreatedAt(entity.getCreatedAt());
model.setUpdatedAt(entity.getUpdatedAt());
return model;
}
static BrickEntity toEntity(Brick model) {
BrickEntity entity = new BrickEntity();
entity.setNumber(model.getNumber());
entity.setTitle(model.getTitle());
entity.setTags(tagsToCsv(model.getTags()));
entity.setImageBase64(model.getImageBase64());
return entity;
}
private static List<String> csvToTags(String csv) {
if (csv == null || csv.isEmpty()) return List.of();
return Arrays.asList(csv.split(\",\"));
}
static String tagsToCsv(List<String> tags) {
if (tags == null || tags.isEmpty()) return \"\";
return String.join(\",\", tags);
}
}
Rules: - Keep mappers simple (no business logic) - Use static methods - Package-private (only used within domain)
Error Handling (Global Exception Handler)¶
Exceptions are caught by a centralized @RestControllerAdvice:
package fr.tiogars.data.common.controllers.advices;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import fr.tiogars.data.common.exceptions.DataNotFoundException;
@RestControllerAdvice
public class GlobalControllerExceptionHandler {
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<String> handleIllegalArgument(IllegalArgumentException ex) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ex.getMessage());
}
@ExceptionHandler(DataNotFoundException.class)
public ResponseEntity<String> handleNotFound(DataNotFoundException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage());
}
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleGenericException(Exception ex) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(\"Une erreur inattendue s'est produite.\");
}
}
Rules:
- Throw IllegalArgumentException for validation errors → 400 Bad Request
- Throw DataNotFoundException for missing resources → 404 Not Found
- Centralize error responses in @RestControllerAdvice
- Log unexpected errors
Transactions¶
Use @Transactional on service methods that modify data:
@Service
public class BrickUpdateService {
@Transactional
public Brick updateBrick(String id, BrickUpdateForm form) {
BrickEntity entity = brickRepository.findById(id)
.orElseThrow(() -> new DataNotFoundException(\"Brick not found: \" + id));
entity.setTitle(form.getTitle());
entity.setTags(tagsTocsv(form.getTags()));
return BrickModelMapper.toModel(brickRepository.save(entity));
}
}
Rules:
- Use @Transactional on update/delete operations
- Omit on read-only queries (optional)
- Rollback automatically on exception
Dependency Injection & Testing¶
Always use constructor injection for testability:
// GOOD: Constructor injection
@Service
public class BrickCreationService {
private final BrickRepository repo;
public BrickCreationService(BrickRepository repo) {
this.repo = repo;
}
}
// AVOID: Field injection
@Service
public class BrickCreationService {
@Autowired
private BrickRepository repo;
}
Constructor injection makes mocking easier in tests.
OpenAPI Documentation¶
All endpoints and models must have OpenAPI annotations:
@RestController
@Tag(name = \"brick\", description = \"Gestion des briques.\")
public class BrickController {
@GetMapping(\"/brick/{id}\")
@Operation(summary = \"Obtenir une brique\", description = \"Retourne une brique par son identifiant.\")
public ResponseEntity<Brick> getBrickById(@PathVariable String id) {
// Implementation
}
}
In models:
public class Brick {
@Schema(description = \"Identifiant unique.\", example = \"uuid-123\")
private String id;
}
Testing Services¶
See Testing Conventions for detailed testing patterns.
Quick example:
@SpringBootTest
class BrickCreationServiceTest {
@Autowired
private BrickCreationService service;
@MockBean
private BrickRepository repository;
@Test
void shouldCreateBrick() {
BrickCreationForm form = new BrickCreationForm();
form.setNumber(\"60284\");
form.setTitle(\"Test\");
service.createBrick(form);
verify(repository).save(any(BrickEntity.class));
}
}
References¶
03 - Testing Conventions¶
Testing Strategy¶
Testing follows a pyramid approach:
/\\
/ \\ E2E Tests (few, full flow)
/----\\
/ \\ Integration Tests (moderate, API+DB)
/--------\\
/ \\ Unit Tests (many, isolated logic)
/____________\\
Target coverage: ≥ 80% for business logic, ≥ 70% overall.
Unit Tests¶
Unit tests verify business logic in isolation.
Backend Unit Tests (Java)¶
package fr.tiogars.data.dev.docs.brick.services;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeEach;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
class BrickCreationServiceTest {
private BrickCreationService service;
private BrickRepository repository;
@BeforeEach
void setUp() {
repository = mock(BrickRepository.class);
service = new BrickCreationService(repository);
}
@Test
void shouldCreateBrickWithValidForm() {
BrickCreationForm form = new BrickCreationForm();
form.setNumber(\"60284\");
form.setTitle(\"Le camion\");
form.setTags(List.of(\"city\", \"truck\"));
when(repository.findByNumber(\"60284\")).thenReturn(Optional.empty());
when(repository.save(any())).thenAnswer(inv -> {
BrickEntity entity = inv.getArgument(0);
entity.setId(\"uuid-123\");
return entity;
});
Brick result = service.createBrick(form);
assertThat(result)
.isNotNull()
.hasFieldOrPropertyWithValue(\"id\", \"uuid-123\")
.hasFieldOrPropertyWithValue(\"number\", \"60284\")
.hasFieldOrPropertyWithValue(\"title\", \"Le camion\");
}
@Test
void shouldThrowWhenNumberAlreadyExists() {
BrickCreationForm form = new BrickCreationForm();
form.setNumber(\"60284\");
form.setTitle(\"Le camion\");
when(repository.findByNumber(\"60284\"))
.thenReturn(Optional.of(new BrickEntity()));
assertThatThrownBy(() -> service.createBrick(form))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining(\"deja utilise\");
}
@Test
void shouldThrowWhenNumberIsEmpty() {
BrickCreationForm form = new BrickCreationForm();
form.setNumber(\"\");
form.setTitle(\"Le camion\");
assertThatThrownBy(() -> service.createBrick(form))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining(\"obligatoire\");
}
}
Rules:
- Use @BeforeEach for setup
- Mock external dependencies (repositories)
- Name tests: should<Expected>When<Condition> or shouldThrow<Exception>When<Condition>
- One assertion focus per test
- Use assertThat() from AssertJ (fluent API)
Frontend Unit Tests (React)¶
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { BrickForm } from './BrickForm';
import { vi } from 'vitest';
describe('BrickForm', () => {
it('should display all form fields', () => {
const handleSubmit = vi.fn();
render(<BrickForm onSubmit={handleSubmit} />);
expect(screen.getByLabelText(/brick number/i)).toBeInTheDocument();
expect(screen.getByLabelText(/title/i)).toBeInTheDocument();
expect(screen.getByRole('button', { name: /save/i })).toBeInTheDocument();
});
it('should call onSubmit with form data when submitted', async () => {
const handleSubmit = vi.fn();
const user = userEvent.setup();
render(<BrickForm onSubmit={handleSubmit} />);
const numberInput = screen.getByLabelText(/brick number/i);
const titleInput = screen.getByLabelText(/title/i);
const submitButton = screen.getByRole('button', { name: /save/i });
await user.type(numberInput, '60284');
await user.type(titleInput, 'Le camion');
await user.click(submitButton);
expect(handleSubmit).toHaveBeenCalledWith(
expect.objectContaining({
number: '60284',
title: 'Le camion',
})
);
});
it('should display validation error when number is missing', async () => {
const handleSubmit = vi.fn();
const user = userEvent.setup();
render(<BrickForm onSubmit={handleSubmit} />);
const submitButton = screen.getByRole('button', { name: /save/i });
await user.click(submitButton);
expect(screen.getByText(/number is required/i)).toBeInTheDocument();
});
});
Rules:
- Use @testing-library/react (not Enzyme)
- Test user interactions, not implementation details
- Use userEvent for realistic interactions
- Test accessibility (query by role, label, etc.)
- One behavior focus per test
Integration Tests¶
Integration tests verify components working together (API + DB, Pages + Services).
Backend Integration Tests¶
package fr.tiogars.data.dev.docs.brick.controllers;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@SpringBootTest
class BrickApiIntegrationTest {
private MockMvc mockMvc;
@Autowired
private WebApplicationContext webApplicationContext;
@Autowired
private BrickRepository brickRepository;
@BeforeEach
void cleanData() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
brickRepository.deleteAllInBatch();
}
@Test
void shouldCreateUpdateAndDeleteBrick() throws Exception {
String createPayload = \"\"\"
{
\"number\": \"60284\",
\"title\": \"Le camion de chantier\",
\"tags\": [\"city\", \"truck\"]
}
\"\"\";
// CREATE
var createResult = mockMvc.perform(post(\"/brick\")
.contentType(MediaType.APPLICATION_JSON)
.content(createPayload))
.andExpect(status().isOk())
.andExpect(jsonPath(\"$.id\").isNotEmpty())
.andExpect(jsonPath(\"$.number\").value(\"60284\"))
.andReturn();
String createdId = extractId(createResult);
// LIST
mockMvc.perform(get(\"/brick\"))
.andExpect(status().isOk())
.andExpect(jsonPath(\"$.count\").value(1))
.andExpect(jsonPath(\"$.items[0].id\").value(createdId));
// UPDATE
String updatePayload = \"\"\"
{
\"id\": \"%s\",
\"number\": \"60284\",
\"title\": \"Updated title\",
\"tags\": [\"city\"]
}
\"\"\".formatted(createdId);
mockMvc.perform(put(\"/brick/{id}\", createdId)
.contentType(MediaType.APPLICATION_JSON)
.content(updatePayload))
.andExpect(status().isOk())
.andExpect(jsonPath(\"$.title\").value(\"Updated title\"));
// DELETE
mockMvc.perform(delete(\"/brick/{id}\", createdId))
.andExpect(status().isNoContent());
mockMvc.perform(get(\"/brick/{id}\", createdId))
.andExpect(status().isNotFound());
}
}
Rules:
- Use @SpringBootTest for full app context
- Use MockMvc for API testing
- Clean database before each test (@BeforeEach)
- Test full CRUD flows (Create, Read, Update, Delete)
- Verify HTTP status codes and response structure
Frontend Integration Tests¶
Test pages with mocked API services:
import { render, screen, waitFor } from '@testing-library/react';
import { Provider } from 'react-redux';
import { configureStore } from '@reduxjs/toolkit';
import { BrickPage } from './BrickPage';
import { brickApi } from '../services/brickApi';
// Mock the API endpoint
vi.mock('../services/brickApi', () => ({
useListBricksQuery: vi.fn(() => ({
data: { items: [{ id: '1', number: '60284', title: 'Test' }] },
loading: false,
error: null,
})),
}));
describe('BrickPage', () => {
it('should display list of bricks', async () => {
const store = configureStore({
reducer: {
[brickApi.reducerPath]: brickApi.reducer,
},
});
render(
<Provider store={store}>
<BrickPage />
</Provider>
);
await waitFor(() => {
expect(screen.getByText('Test')).toBeInTheDocument();
});
});
});
Test Naming Convention¶
Method Names¶
Follow the pattern: should<ExpectedBehavior>When<Condition>
Examples:
shouldCreateBrickWithValidForm()
shouldThrowWhenNumberAlreadyExists()
shouldReturnBrickListWithCount()
shouldUpdateBrickTitleOnly()
shouldDeleteAllBricksSuccessfully()
shouldReturnNotFoundWhenIdDoesNotExist()
Test Class Names¶
Pattern: <ClassName>Test or <ClassName>IntegrationTest
Examples:
Test Organization¶
File Structure¶
data-server/src/test/java/fr/tiogars/data/dev/docs/<domain>/
├── controllers/
│ └── <Entity>ApiIntegrationTest.java
├── services/
│ ├── <Entity>CreationServiceTest.java
│ ├── <Entity>ListServiceTest.java
│ └── ...
└── repositories/
└── <Entity>RepositoryTest.java
data-web/src/
├── components/
│ └── <Component>.test.tsx
├── pages/
│ └── <Page>.test.tsx
└── services/
└── <service>.test.ts
Rule: Tests colocated with source code (same directory structure).
Coverage Requirements¶
- Services: ≥ 80% coverage
- Controllers: ≥ 60% coverage (structural testing)
- Components: ≥ 70% coverage
- Overall: ≥ 70% coverage
Run coverage:
Backend:
Frontend:
Testing Best Practices¶
DO¶
✅ Test behavior, not implementation
✅ Use descriptive test names
✅ One logical assertion per test (or related assertions)
✅ Mock external dependencies
✅ Use fixtures for common test data
✅ Test edge cases (null, empty, invalid input)
✅ Verify error messages
DON'T¶
❌ Test implementation details (private methods)
❌ Use multiple unrelated assertions in one test
❌ Create complex test data (use factories/builders)
❌ Test the framework (Spring, React, etc.) - trust it works
❌ Hardcode test data in test methods
❌ Test all permutations (focus on critical paths)
CI/CD Integration¶
Tests run automatically on: - Push to any branch (via GitHub Actions) - Pull requests (pre-merge validation) - Main branch (pre-deployment)
Failure blocks merging. Ensure all tests pass locally before pushing:
References¶
04 - Documentation Standards¶
Code Documentation¶
Backend: Javadoc & Comments¶
Javadoc on Public Classes & Methods¶
All public classes and methods must have Javadoc:
/**
* Service responsible for creating new bricks.
*
* Validates uniqueness of brick numbers before creation.
*/
@Service
public class BrickCreationService {
private final BrickRepository brickRepository;
/**
* Creates a new brick from the provided form.
*
* @param form the brick creation form containing number, title, and tags
* @return the created brick with generated ID and timestamps
* @throws IllegalArgumentException if number already exists or required fields are empty
*/
@Transactional
public Brick createBrick(BrickCreationForm form) {
// Implementation
}
}
Rules:
- Public classes: brief description + purpose
- Public methods: what, parameters, return value, exceptions
- Use @param, @return, @throws tags
- Keep descriptions concise (one sentence when possible)
Inline Comments¶
Use inline comments to explain why, not what (code shows the what):
// Validate uniqueness: only one brick per number
Optional<BrickEntity> existing = brickRepository.findByNumber(number);
if (existing.isPresent() && !existing.get().getId().equals(excludeId)) {
throw new IllegalArgumentException(\"Le numero de brique est deja utilise: \" + number);
}
// Store tags as CSV to avoid normalization on every fetch
entity.setTags(BrickModelMapper.tagsToCsv(normalizeTags(tags)));
Avoid:
// BAD: Restates obvious code
// Check if number is not empty
if (number != null && !number.trim().isEmpty())
Comments in French¶
Comments and Javadoc descriptions are in French (matching project locale), except for code examples:
/**
* Valide l'unicité du numero de brique dans la base.
*
* @param number le numero a valider
* @param excludeId l'ID a exclure de la verification (pour les updates)
* @throws IllegalArgumentException si le numero est deja utilise
*/
private void validateUniqueNumber(String number, String excludeId) {
// Implementation
}
Frontend: TSDoc & Comments¶
TSDoc on Exported Types & Functions¶
/**
* Represents a brick entity with metadata.
*/
export type Brick = {
/** Unique identifier (UUID) */
id: string;
/** Reference number (e.g., \"60284\") */
number: string;
/** Display title */
title: string;
/** Classification tags */
tags: string[];
/** Base64-encoded image data or URL */
imageBase64?: string;
/** ISO timestamp of creation */
createdAt: Instant;
/** ISO timestamp of last modification */
updatedAt: Instant;
};
/**
* Fetches a list of all bricks from the server.
*
* @returns A promise resolving to the brick list response or rejecting on error
*
* @example
* const { data, loading } = useListBricksQuery();
*/
export const useListBricksQuery = () => {
// Implementation
};
Rules:
- Export types: brief description
- Exported functions: purpose + usage example
- Use @param, @returns tags
- Use @example for complex hooks
Inline Comments in TypeScript¶
// Memoize computed tags to prevent unnecessary re-renders
const computedTags = useMemo(() => {
return brick.tags.map(tag => tag.toLowerCase()).sort();
}, [brick.tags]);
// Mobile-first: stack vertically on small screens
const [isDesktop, setIsDesktop] = useState(false);
useEffect(() => {
const handleResize = () => setIsDesktop(window.innerWidth >= 960);
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
OpenAPI Documentation¶
Schema Annotations¶
All API models must have @Schema annotations with descriptions and examples:
public class Brick {
@Schema(
description = \"Identifiant unique de la brique (UUID v4).\",
example = \"550e8400-e29b-41d4-a716-446655440000\",
requiredMode = Schema.RequiredMode.REQUIRED
)
private String id;
@Schema(
description = \"Numero de reference unique de la brique.\",
example = \"60284\",
requiredMode = Schema.RequiredMode.REQUIRED
)
private String number;
@Schema(
description = \"Titre descriptif de la brique.\",
example = \"Le camion de chantier\",
requiredMode = Schema.RequiredMode.REQUIRED
)
private String title;
@Schema(
description = \"Tags de classification pour recherche et filtrage.\",
example = \"[\\\"city\\\", \\\"truck\\\", \\\"construction\\\"]\",
requiredMode = Schema.RequiredMode.REQUIRED
)
private List<String> tags;
}
Endpoint Documentation¶
All REST endpoints must have @Tag and @Operation:
@RestController
@Tag(name = \"brick\", description = \"Gestion de la collection de briques.\")
public class BrickController {
@GetMapping(\"/brick\")
@Operation(
summary = \"Lister toutes les briques\",
description = \"Retourne la collection complete de briques avec pagination optionnelle.\"
)
public ResponseEntity<BrickListResponse> listBricks() {
// Implementation
}
@PostMapping(\"/brick\")
@Operation(
summary = \"Creer une nouvelle brique\",
description = \"Cree une brique avec numero unique, titre, tags et image optionnelle.\"
)
public ResponseEntity<Brick> createBrick(@RequestBody BrickCreationForm form) {
// Implementation
}
@DeleteMapping(\"/brick/{id}\")
@Operation(
summary = \"Supprimer une brique\",
description = \"Supprime definitivement une brique et tous ses liens externes associes.\"
)
public ResponseEntity<Void> deleteBrick(@PathVariable String id) {
// Implementation
}
}
Rules:
- Descriptions in French
- summary: one-liner
- description: detailed behavior
- Include important side effects (e.g., cascading deletes)
Generating API Specs¶
API specs are auto-generated and served at /v3/api-docs:
# Frontend: Fetch and regenerate client code
pnpm -C data-web run openapi:pull
pnpm -C data-web run rtk:codegen
The OpenAPI spec is the source of truth for the API contract.
User-Facing Documentation (MkDocs)¶
Documentation Structure¶
Features should be documented in docs/1-features/:
docs/1-features/
├── 1.1-management/
│ ├── index.md # Overview
│ ├── creation.md # How to create items
│ ├── editing.md # How to edit items
│ ├── deletion.md # How to delete items
│ ├── search.md # Search & filtering
│ ├── import-export.md # Import/export formats
│ └── screenshots/ # UI screenshots
├── 1.2-business/
│ └── ...
Documentation Template¶
# Feature Name
## Overview
Brief description of what this feature does and its purpose.
## Common Tasks
### Task 1: Create an Item
Step-by-step instructions with screenshots.
1. Navigate to the Create page
2. Fill in required fields
3. Click \"Save\"
4. Confirmation message appears
### Task 2: Edit an Existing Item
Instructions...
### Task 3: Delete an Item
Instructions...
## Keyboard Shortcuts
- `Ctrl+S` — Save
- `Ctrl+N` — New
- `Ctrl+F` — Find
## Troubleshooting
Common issues and solutions.
## See Also
- [Related Feature](../1.2-business/index.md)
Rules: - Clear, non-technical language - Step-by-step instructions - Screenshots for UI features - Include error messages and solutions - Reference related features
Expected Sections for Feature Pages¶
For user-facing feature pages in docs/1-features/, use the template as a guide but adapt it to the actual feature. The expected sections form a checklist, not a rigid skeleton.
Expected sections when applicable: - Objectif et périmètre: what the feature is for, who uses it, and what is in or out of scope - Parcours utilisateur / cas d'usage: main user flows or common operations, described step by step - Règles métier / validations: important constraints, required fields, uniqueness rules, accepted formats, side effects - Responsive / mobile / desktop: differences in behavior or layout between devices when they matter to the user - Imports / exports / formats: supported file types, expected columns or JSON fields, replacement or merge behavior - Dépannage / erreurs fréquentes: common failure cases, error messages, and corrective actions - Glossaire / concepts clés: domain vocabulary needed to use the feature correctly - API / aspects techniques: optional, only when helpful to explain integration points, automation, or known limits - Captures d'écran / exemples visuels: recommended when they make workflows easier to understand
Writing rules: - Do not keep empty headings for non-applicable topics - Group closely related topics when that improves readability - Keep the language user-oriented; move implementation detail to development docs unless it clarifies a real user need - Prefer concrete examples over abstract descriptions for formats, validations, and troubleshooting
Configuration Documentation¶
README Files¶
Each module should have a README.md:
Project Structure¶
src/pages/— Page componentssrc/components/— Reusable componentssrc/features/— Redux slices and domain logicsrc/services/— Generated RTK Query APIs
Development¶
See Development Instructions for patterns and conventions.
Testing¶
### Configuration Files
Document non-obvious configuration in comments:
```yaml
# mkdocs.yml
site_name: Data Platform
site_url: https://data.tiogars.fr
docs_dir: docs
site_dir: site_output
# Theme and plugins
theme:
name: material
features:
- navigation.tabs
- toc.follow
plugins:
- search
- pdf:
enabled: !ENV [ENABLE_PDF_EXPORT, false]
Documentation Maintenance¶
Keep Docs in Sync¶
When updating features: 1. Update code comments/Javadoc 2. Update OpenAPI annotations 3. Update user-facing MkDocs 4. Update architecture/pattern docs if design changed
Review Checklist¶
Before committing: - [ ] All public APIs have Javadoc/TSDoc - [ ] OpenAPI annotations are complete - [ ] User-facing features documented in MkDocs - [ ] Complex logic has inline comments explaining \"why\" - [ ] All links and references are valid
Generating Documentation¶
Backend: OpenAPI Spec¶
# Automatically generated on startup
# Access at: http://localhost:8080/v3/api-docs
# Swagger UI: http://localhost:8080/swagger-ui.html
Frontend: API Documentation¶
MkDocs: User Documentation¶
# Build static HTML
mkdocs build -f docs/settings/mkdocs.yml -d ../site_output
# Serve locally
mkdocs serve -f docs/settings/mkdocs.yml
References¶
05 - Security Practices¶
Overview¶
Security is implemented at multiple layers:
- Gateway: OAuth2 authentication, rate-limiting
- API: Input validation, authorization
- Database: Parameterized queries (via Spring Data JPA)
- Frontend: XSS prevention (React escaping), CSRF tokens
Input Validation¶
Backend Validation (Service Layer)¶
All input must be validated in the service layer before processing:
@Service
public class BrickCreationService {
public Brick createBrick(BrickCreationForm form) {
// 1. Validate input
if (form.getNumber() == null || form.getNumber().trim().isEmpty()) {
throw new IllegalArgumentException(\"Le numero de brique est obligatoire.\");
}
if (form.getNumber().length() > 100) {
throw new IllegalArgumentException(\"Le numero ne peut pas depasser 100 caracteres.\");
}
// 2. Validate uniqueness
brickRepository.findByNumber(form.getNumber()).ifPresent(existing -> {
throw new IllegalArgumentException(\"Le numero \" + form.getNumber() + \" est deja utilise.\");
});
// 3. Sanitize & store
BrickEntity entity = new BrickEntity();
entity.setNumber(form.getNumber().trim()); // Trim whitespace
entity.setTitle(form.getTitle().trim());
entity.setTags(tagsToCsv(form.getTags()));
return BrickModelMapper.toModel(brickRepository.save(entity));
}
}
Rules: - Validate presence (required fields) - Validate format (email, URL, length) - Validate uniqueness (no duplicates where needed) - Validate range (min/max for numbers) - Trim and normalize strings - Never trust user input
Frontend Validation (Form Layer)¶
React Hook Form handles validation:
const {
register,
formState: { errors },
} = useFormContext<BrickFormValues>();
<TextField
label=\"Brick Number\"
fullWidth
error={Boolean(errors.number)}
helperText={errors.number?.message}
{...register('number', {
required: 'Number is required.',
minLength: {
value: 1,
message: 'Number cannot be empty.',
},
maxLength: {
value: 100,
message: 'Number cannot exceed 100 characters.',
},
validate: {
noSpecialChars: (value) =>
/^[a-zA-Z0-9-_]+$/.test(value) || 'Only alphanumeric, hyphens, underscores allowed.',
},
})}
/>
Rules: - Validate on blur (better UX) - Show inline error messages - Client-side is UX; server-side is security (don't rely on client validation)
Authentication & Authorization¶
Gateway: OAuth2¶
The gateway enforces OAuth2 authentication:
# application-docker.yaml (gateway)
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://auth2.tiogars.fr
jwk-set-uri: https://auth2.tiogars.fr/.well-known/jwks.json
Protected Routes: All API requests go through the gateway and require valid JWT token.
Frontend: OIDC Integration¶
Frontend uses OIDC client for authentication:
// auth/OidcAuthProvider.tsx
export const OidcAuthProvider: FC<OidcAuthProviderProps> = ({ children }) => {
const [user, setUser] = useState<User | null>(null);
useEffect(() => {
const userManager = new UserManager({
authority: 'https://auth2.tiogars.fr',
client_id: 'data-web',
redirect_uri: window.location.origin,
response_type: 'code',
scope: 'openid profile email',
});
userManager.getUser().then((user) => {
setUser(user?.profile ?? null);
});
}, []);
return (
<OidcAuthContext.Provider value={{ user, /* ... */ }}>
{children}
</OidcAuthContext.Provider>
);
};
Rules: - Use OIDC for authentication (not custom JWT) - Store tokens in browser securely (HttpOnly cookies when possible) - Validate tokens server-side on every request
Rate Limiting¶
Gateway: Bucket4j¶
Rate-limiting is configured via the gateway (Bucket4j):
# application.yaml (gateway)
data:
gateway:
rate-limit:
capacity: 120 # Max requests
period: PT1M # Time window (1 minute)
tokens: 1 # Cost per request
When limit exceeded: 429 Too Many Requests
Rules:
- Rate-limit per IP (via X-Forwarded-For header)
- Generous limits for internal APIs, stricter for public endpoints
- Document rate limits in API docs
SQL Injection Prevention¶
Use Parameterized Queries¶
Spring Data JPA handles parameterization automatically:
// SAFE: Spring Data uses parameterized queries
Optional<BrickEntity> brick = brickRepository.findByNumber(userInput);
// If custom query needed, use parameterized approach:
@Query(\"SELECT b FROM BrickEntity b WHERE b.number = :number\")
Optional<BrickEntity> findByNumber(@Param(\"number\") String number);
// NEVER do string concatenation:
// NEVER: \"SELECT * FROM brick WHERE number = '\" + userInput + \"'\";
Rules:
- Always use Spring Data JPA methods (no JDBC)
- Use @Param in custom @Query annotations
- Never concatenate SQL strings
Cross-Site Scripting (XSS) Prevention¶
React Escaping¶
React automatically escapes strings in JSX:
// SAFE: React escapes the value
const username = \"<script>alert('xss')</script>\";
<Typography>{username}</Typography>
// Renders as text, not executed
// UNSAFE: Using dangerouslySetInnerHTML
<Typography dangerouslySetInnerHTML={{ __html: userContent }} />
// NEVER use this unless content is verified safe
Rules:
- Use normal JSX (escaped by default)
- Avoid dangerouslySetInnerHTML
- If HTML is necessary, sanitize with DOMPurify
import DOMPurify from 'dompurify';
const sanitized = DOMPurify.sanitize(userContent);
<Typography dangerouslySetInnerHTML={{ __html: sanitized }} />
CSRF Protection¶
Spring Security CSRF Token¶
CSRF protection is handled by Spring Security (enabled by default):
Frontend: Including CSRF Token¶
RTK Query includes CSRF token automatically (via interceptor if configured):
// The gateway/server expects CSRF token in X-CSRF-TOKEN header
// RTK Query can be configured to include it:
// (Already handled by gateway configuration)
Rules: - Ensure CSRF tokens are included in POST/PUT/DELETE requests - Use SameSite cookie policy (default: Lax)
Data Protection¶
Encryption at Rest¶
Database uses PostgreSQL encryption (handled by infrastructure):
# Data at rest: Encrypted by PostgreSQL/container
# Data in transit: HTTPS only (enforced by reverse proxy)
Sensitive Data Handling¶
Never log or store sensitive data unnecessarily:
// BAD: Log password
logger.info(\"User created with password: \" + password);
// GOOD: Log only relevant info
logger.info(\"User created with email: \" + email);
// MASKED: Log sensitive fields partially
logger.info(\"Image data size: \" + imageBase64.length() + \" bytes\");
Dependency Security¶
Java/Maven¶
Keep dependencies updated to patch vulnerabilities:
# Check for known vulnerabilities
mvn org.owasp:dependency-check-maven:check
# Update dependencies
mvn versions:display-dependency-updates
NPM/Frontend¶
Rules: - Review and apply security patches promptly - Keep lock files up-to-date - Use dependency scanning in CI/CD
Logging & Monitoring¶
Structured Logging¶
Log important security events:
logger.info(\"User {} authenticated successfully\", userId);
logger.warn(\"Authentication failed for user {}\", userId);
logger.error(\"Unauthorized access attempt from IP: {}\", clientIp);
// NEVER log:
// - Passwords
// - Tokens
// - Credit card numbers
// - Personal identification numbers
Observability¶
Grafana (LGTM stack) monitors: - Error rates - Request latency - Authentication failures - Rate-limit violations
Access at: http://localhost:3000
Environment-Specific Security¶
Development¶
# application.yml (dev)
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://localhost:8081 # Local test authority
Production¶
# application-prod.yml
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://auth2.tiogars.fr # Real authority
jwk-set-uri: https://auth2.tiogars.fr/.well-known/jwks.json
Rules:
- Different credentials for each environment
- Rotate secrets regularly
- Never commit secrets (use .env or secrets manager)
Security Checklist¶
Before deploying:
- All inputs validated (length, format, uniqueness)
- Parameterized queries used (no string concatenation in SQL)
- Sensitive data not logged
- HTTPS enabled (TLS 1.2+)
- Authentication enforced on all endpoints
- Rate-limiting configured
- Dependencies scanned for vulnerabilities
- Error messages don't leak implementation details
- CORS configured appropriately
- Security headers present (CSP, X-Frame-Options, etc.)
References¶
06 - Improvement Proposals¶
This section documents potential enhancements and evolution opportunities for the project. These are suggestions for future development phases, not requirements for current features.
Frontend Enhancements¶
1. Component Library / Storybook¶
Current State: Components built ad-hoc for each feature.
Proposal: Develop a reusable component library with Storybook documentation.
- Standardize button, form, card, modal components
- Document component variants and props
- Enable design system consistency
- Benefit: Reduce code duplication, faster feature development
Estimation: 1-2 weeks
2. Advanced Form Validation¶
Current State: Client-side validation via React Hook Form.
Proposal: Integrate zod or yup for schema-based validation.
import { z } from 'zod';
const brickSchema = z.object({
number: z.string().min(1).max(100),
title: z.string().min(1),
tags: z.array(z.string()),
imageBase64: z.string().optional(),
});
export const useBrickForm = () => {
const form = useForm({
resolver: zodResolver(brickSchema),
});
};
- Shared validation logic between frontend and backend
- Better error messages
- Easier to maintain and test
Estimation: 3-5 days
3. Offline Support / Service Workers¶
Current State: No offline capability.
Proposal: Add service worker for offline caching and sync.
- Cache API responses for offline viewing
- Queue mutations (create/update/delete) offline
- Sync when reconnected
Technology: Workbox or similar
Benefit: Better UX on unreliable networks
Estimation: 1-2 weeks
4. Internationalization (i18n)¶
Current State: UI text in French and English mixed.
Proposal: Centralize translations with react-i18next.
import { useTranslation } from 'react-i18next';
export const BrickForm = () => {
const { t } = useTranslation();
return <TextField label={t('brick.number')} />;
};
Benefit: Support multiple languages, easier maintenance
Estimation: 1 week
5. Progressive Web App (PWA)¶
Current State: Web app, no offline or install capability.
Proposal: Convert to PWA with manifest, service workers, installability.
- Install app on home screen
- Offline support (see #3)
- Push notifications capability
Benefit: Better mobile experience, app-like feel
Estimation: 1-2 weeks
Backend Enhancements¶
1. Event-Driven Architecture / Event Sourcing¶
Current State: Traditional CRUD operations, limited event history.
Proposal: Implement event sourcing for audit trail and event replay.
// Domain event
public class BrickCreatedEvent {
private String id;
private String number;
private String title;
private Instant timestamp;
}
// Event store
public interface BrickEventRepository extends JpaRepository<BrickEvent, String> {
List<BrickEvent> findByAggregateIdOrderByTimestamp(String aggregateId);
}
// Event handler
@EventListener
public void onBrickCreated(BrickCreatedEvent event) {
// Perform side effects (send email, update cache, etc.)
}
Benefit: - Audit trail of all changes - Temporal queries (state at any point in time) - Event replay for debugging
Estimation: 2-3 weeks
2. Full-Text Search (Elasticsearch)¶
Current State: Simple database queries only.
Proposal: Integrate Elasticsearch for advanced search capabilities.
// Query example
@Query(...)
Page<Brick> searchBricks(
@Param(\"query\") String query,
Pageable pageable
);
Features: - Fuzzy matching - Faceted search - Autocomplete - Performance for large datasets
Estimation: 1-2 weeks
3. GraphQL API¶
Current State: REST API only.
Proposal: Add GraphQL endpoint alongside REST.
Benefits: - Precise data fetching - Reduced over-fetching - Self-documenting API
Estimation: 1-2 weeks
4. Caching Strategy¶
Current State: No explicit caching layer.
Proposal: Implement Redis cache with invalidation strategy.
@Service
public class BrickListService {
@Cacheable(value = \"bricks\", unless = \"#result.items.isEmpty()\")
public BrickListResponse listBricks() {
return new BrickListResponse(brickRepository.findAll());
}
@CacheEvict(value = \"bricks\", allEntries = true)
public Brick createBrick(BrickCreationForm form) {
// Implementation
}
}
Benefit: Reduce database queries, improve response times
Estimation: 3-5 days
5. Batch Operations¶
Current State: Single item operations only.
Proposal: Support bulk create/update/delete operations.
@PostMapping(\"/brick/batch\")
public ResponseEntity<List<Brick>> createBricksBatch(@RequestBody List<BrickCreationForm> forms) {
return ResponseEntity.ok(brickBatchCreationService.createMany(forms));
}
Benefit: Significant performance improvement for large imports
Estimation: 3-5 days
Data & Analytics¶
1. Usage Analytics¶
Current State: No usage tracking.
Proposal: Add analytics to understand user behavior.
- Track feature usage
- Identify performance bottlenecks
- User segment analysis
Tools: Grafana Loki (already available), custom events
Estimation: 1 week
2. Reporting & Dashboards¶
Current State: No reporting.
Proposal: Build admin dashboards.
- Number of bricks created/modified/deleted
- Usage trends
- Error rates by feature
Estimation: 1-2 weeks
Migration History¶
P2 - CSV Support Consolidation (2026-06)¶
Objective: Reduce duplicated CSV parsing/export logic while preserving existing behavior.
Delivered:
- Shared utility introduced in data-server/src/main/java/fr/tiogars/data/common/csv/CsvSupport.java
- GTIN migration completed with dedicated services:
- data-server/src/main/java/fr/tiogars/data/products/gtin/services/GtinExportCsvService.java
- data-server/src/main/java/fr/tiogars/data/products/gtin/services/GtinImportCsvService.java
- Android migration completed with dedicated services:
- data-server/src/main/java/fr/tiogars/data/softwares/android/services/AndroidExportCsvService.java
- data-server/src/main/java/fr/tiogars/data/softwares/android/services/AndroidImportCsvService.java
Android-specific support added:
- Configurable escaping triggers (for |)
- Permissive header normalization for heterogeneous CSV headers
Validation evidence:
- GTIN CSV integration tests passed: data-server/src/test/java/fr/tiogars/data/products/gtin/controllers/GtinImportExportCsvIntegrationTest.java
- Android API integration tests (including CSV flows) passed: data-server/src/test/java/fr/tiogars/data/softwares/android/controllers/AndroidApiIntegrationTest.java
Roadmap status: - CSV migration program: Completed for identified domains - Next step: Apply CsvSupport by default for any new CSV-enabled domain
3. Data Export Enhancements¶
Current State: JSON export only.
Proposal: Support multiple formats and scheduled exports.
- CSV export
- XML export
- Scheduled auto-export to cloud storage
- Data warehouse integration
Estimation: 1-2 weeks
DevOps & Infrastructure¶
1. CI/CD Pipeline¶
Current State: Basic GitHub Actions (likely).
Proposal: Enhanced CI/CD with staged deployments.
- Automated testing on PR
- Staging environment deployment
- Canary deployments to production
- Automated rollback on failure
Estimation: 1-2 weeks
2. Kubernetes Deployment¶
Current State: Docker Compose for local, likely manual deployment.
Proposal: Kubernetes manifests for scalability.
- Auto-scaling based on load
- Service mesh (Istio) for traffic management
- Multi-region deployment
Estimation: 2-3 weeks
3. Disaster Recovery¶
Current State: No documented recovery procedures.
Proposal: Database backup/restore strategy.
- Daily encrypted backups
- Point-in-time recovery capability
- Tested recovery procedures
- DR documentation
Estimation: 1 week planning + ongoing
4. Security Scanning¶
Current State: Basic dependency scanning (likely).
Proposal: Comprehensive security testing.
- SAST (Static Application Security Testing)
- DAST (Dynamic Application Security Testing)
- Container image scanning
- Infrastructure as Code scanning
Tools: SonarQube, Trivy, OWASP ZAP
Estimation: 1 week setup + ongoing
Testing Improvements¶
1. End-to-End Testing (E2E)¶
Current State: Unit + integration tests.
Proposal: Add comprehensive E2E tests with Playwright or Cypress.
test('should create a brick and see it in list', async ({ page }) => {
await page.goto('http://localhost:5173');
await page.click('text=Create Brick');
await page.fill('input[placeholder=\"Number\"]', '60284');
await page.fill('input[placeholder=\"Title\"]', 'Test');
await page.click('button:has-text(\"Save\")');
await expect(page.locator('text=Test')).toBeVisible();
});
Benefit: User-level validation, catch real-world bugs
Estimation: 1-2 weeks
2. Performance Testing / Load Testing¶
Current State: No performance benchmarks.
Proposal: Add load testing and performance baselines.
- k6 or JMeter for load testing
- Performance regression detection
- Latency SLAs
Estimation: 1 week
3. Visual Regression Testing¶
Current State: Manual UI verification.
Proposal: Automated visual testing with Percy or similar.
- Screenshot comparison
- Detect unintended UI changes
- CI integration
Estimation: 3-5 days
Documentation Improvements¶
1. Video Tutorials¶
Current State: Text documentation only.
Proposal: Create short video tutorials for common tasks.
- Setup walkthrough
- Feature walkthroughs
- Troubleshooting videos
Benefit: Lower barrier to entry for new users
Estimation: 1-2 weeks
2. API Client Libraries¶
Current State: JavaScript/TypeScript only.
Proposal: Generate client libraries for other languages.
- Python client
- Java client
- Ruby client
Tools: OpenAPI generators
Benefit: Enable third-party integrations
Estimation: 1-2 weeks per language
3. Architecture Decision Records (ADRs)¶
Current State: Decisions documented ad-hoc.
Proposal: Formalize ADRs for major decisions.
Format: - Title - Status (Proposed, Accepted, Deprecated) - Context - Decision - Consequences - Alternatives considered
Benefit: Knowledge transfer, decision rationale preserved
Estimation: Ongoing (1-2 hours per major decision)
Community & Ecosystem¶
1. Public API & SDK¶
Current State: Internal API.
Proposal: Public API for third-party integrations.
- API versioning
- SDK for popular languages
- Developer portal with docs
- Rate-limiting by tier (free/pro/enterprise)
Estimation: 2-3 weeks
2. Plugin System¶
Current State: Monolithic application.
Proposal: Plugin architecture for extensibility.
- Custom validators
- Custom export formats
- Custom UI components
Benefit: Community contributions, ecosystem growth
Estimation: 3-4 weeks
3. Community Forum / Support¶
Current State: GitHub issues only.
Proposal: Dedicated community forum (Discourse or similar).
- Q&A discussions
- Feature requests voting
- Community best practices
Estimation: 1 week setup + ongoing moderation
Performance Optimizations¶
1. Database Query Optimization¶
Current State: Likely N+1 query issues.
Proposal: Profile and optimize queries.
- Add database indexes
- Implement lazy loading / eager loading appropriately
- Query result caching
Estimation: 1-2 weeks
2. Frontend Bundle Size Reduction¶
Current State: Vite build optimized, but unknown size.
Proposal: Audit and reduce bundle size.
- Code splitting
- Lazy component loading
- Dependency pruning
Estimation: 3-5 days
3. Image Optimization¶
Current State: Large base64 images.
Proposal: Image processing and optimization.
- WebP format support
- Responsive images (srcset)
- Lazy loading images
Benefit: Faster load times, reduced bandwidth
Estimation: 1 week
Accessibility (A11y) Enhancements¶
1. WCAG 2.1 AA Compliance¶
Current State: Basic accessibility (likely A).
Proposal: Achieve WCAG 2.1 AA compliance.
- Color contrast improvements
- Focus management
- Keyboard navigation on all components
- Screen reader testing
Estimation: 2-3 weeks
2. Accessibility Audit¶
Current State: No formal audit.
Proposal: Third-party accessibility audit.
- Manual testing
- Automated scanning
- Recommendations for improvements
Benefit: Inclusive design, legal compliance
Estimation: 1 week (audit) + 2-3 weeks (fixes)
Prioritization Framework¶
When deciding what to implement next, consider:
| Factor | Weight | Scoring |
|---|---|---|
| User demand | 30% | (1=low, 5=high) |
| Business impact | 25% | Feature revenue/cost savings |
| Technical debt | 20% | Prevents future work? |
| Effort | 15% | Days to implement |
| Risk | 10% | Breaking changes / complexity |
Example: Event Sourcing scores high on technical debt but low on immediate user demand.
Next Steps¶
- Quarterly Review: Reassess priorities based on user feedback
- RFC Process: For major proposals, create Request for Comments
- Proof of Concept: For uncertain proposals, build MVP first
- Stakeholder Alignment: Involve users in prioritization
References¶
07 - Mobile Flutter (Android)¶
Objectif¶
Ce guide définit les conventions pour intégrer et maintenir le module mobile flutter_application.
Portée actuelle:
- Android comme cible produit principale
- stockage local SQLite
- synchronisation serveur via gateway
- domaines couverts: GTIN, Car, CarMileage, Android
- parcours additionnels: authentification OIDC, dashboard, paramétrage runtime
- extension desktop existante pour le domaine Winget
Architecture recommandée¶
Organisation Flutter:
flutter_application/lib/
├── app/ # bootstrap, theme, navigation
├── core/
│ ├── api/ # client HTTP, auth JWT, erreurs
│ ├── database/ # SQLite (drift/sqflite) + migrations
│ └── sync/ # queue locale, pull/push, conflits
├── features/
│ ├── auth/ # écran de connexion OIDC
│ ├── dashboard/ # accueil, synchronisations, paramétrage gateway
│ ├── gtin/
│ ├── vehicles/ # car + car mileage
│ ├── android_apps/
│ └── winget_apps/ # exposé surtout sur Windows
└── shared/ # widgets, utils, modèles partagés
Flux de dépendances:
- Presentation -> Domain -> Data
- Data -> Gateway API (
http://localhost:8081en local) - Data -> SQLite (cache local et file de synchro)
Persistance locale¶
Contraintes:
- toute écriture est persistée localement avant envoi réseau
- une
sync_queuestocke les opérations en attente - une table
sync_statestocke les curseurs de synchro par domaine
Tables minimales:
gtincarcar_mileageandroid_appsync_queuesync_state
Synchronisation¶
Politique validée:
- conflit: serveur prioritaire
- en cas de divergence, la copie locale est remplacée par la version serveur
Mécanisme attendu:
- Pull des changements serveur depuis dernier curseur
- Push des opérations locales en attente (ordre FIFO)
- Réconciliation locale + mise à jour curseurs
- Retry avec backoff en cas d'échec réseau
Mécanisme actuellement implémenté:
- push de la file locale vers gateway via
SyncEngine - pull cursor-ready: tentative incremental (curseur) puis fallback refresh complet si le backend ne retourne pas de curseur
- déclenchement automatique au démarrage de l'application
- déclenchement automatique quand la connectivité revient
Intégration API¶
L'application mobile consomme les routes gateway existantes:
/gtin/car/car-mileage/android/winget/api/v1/sync/{domain}/changes
Conventions:
- authentification JWT alignée gateway
- pas de logique métier dans la couche UI
- mapping explicite entre DTO réseau et modèles locaux
Notes de plateforme¶
- Android reste la cible produit principale
- certains écrans Flutter vérifient
Platform.isWindowspour afficher le domaine Winget - la configuration runtime (URL gateway, JWT manuel de debug) est persistée localement
Tests¶
Minimum attendu:
- tests unitaires: repositories et use cases
- tests widget: écrans critiques et formulaires
- tests intégration: scénario offline -> online -> sync
Commandes utiles¶
Push-Location flutter_application
flutter pub get
flutter analyze
flutter test
flutter run -d emulator-5554
flutter build apk --debug
Pop-Location
Documentation liée¶
flutter_application/README.mdflutter_application/DEVELOPMENT.mddocs/1-features/1.3-mobile/index.mddocs/1-features/1.3-mobile/architecture-sync.mddocs/1-features/1.3-mobile/features-android.md
08 - Revue des Alertes IDE¶
Objectif¶
Standardiser la gestion des alertes remontees par l'IDE (VS Code): - erreurs de compilation - avertissements de lint/analyse statique - alertes de dependances/vulnerabilites - faux positifs et diagnostics obsoletes en cache
L'objectif est de corriger ce qui a un impact reel, de reduire le bruit, et de garder un projet compilable et maintenable.
Sources d'alertes a traiter¶
- Problems panel VS Code
- Diagnostics du langage (TypeScript, Dart, Java)
- Lint/format (ESLint, analyse Flutter, checks Maven)
- Alertes de securite dependances (Maven/npm)
Toujours prioriser les alertes visibles dans l'IDE avant les optimisations non demandees.
Priorisation (ordre obligatoire)¶
- Bloquant build/run/test
- Erreurs de compilation
- Imports/modules manquants
-
Incompatibilites de versions qui cassent le build
-
Risque fonctionnel ou securite
- Vulnerabilites de dependances (HIGH/CRITICAL)
-
Warnings indiquant un comportement incorrect probable
-
Qualite et dette technique
- Warnings de style/types non bloquants
- Nettoyage de code sans impact runtime
Workflow de revue¶
- Lister les alertes actives par fichier.
- Grouper par type de probleme (meme cause racine).
- Corriger d'abord les causes racines, pas les symptomes repetes.
- Revalider immediatement:
- diagnostics IDE
- build/test ciblés sur les zones modifiees
- Documenter les exceptions restantes (faux positif, action differree).
Ne pas faire de refactor global non necessaire pour une alerte locale.
Regles par technologie du repo¶
Java / Maven (data-server, data-gateway)¶
- Verifier la compatibilite Spring Boot/Spring Cloud avant modification.
- Preferer upgrade patch/minor compatible plutot que downgrade majeur, sauf contrainte explicite.
- Valider par module:
./data-gateway/mvnw.cmd -f data-gateway/pom.xml -DskipTests compile./data-server/mvnw.cmd -f data-server/pom.xml -DskipTests compile
Si l'IDE affiche encore une ancienne alerte apres correction, considerer un cache de diagnostics et recharger le projet Java.
Frontend TypeScript (data-web)¶
- Corriger d'abord les erreurs type-check avant les warnings ESLint.
- Ne jamais modifier manuellement les fichiers generes
src/services/*Api.ts. - Regenerer si necessaire:
pnpm -C data-web run openapi:pullpnpm -C data-web run rtk:codegen
Flutter (flutter_application)¶
- Prioriser les erreurs d'analyse Dart visibles dans l'IDE.
- Eviter les
constinvalides avec objets non constants (ex:DateTime(...)). - En cas d'erreur outillage Windows (fichier verrouille), distinguer un probleme environnemental d'un vrai echec de code.
Gestion des faux positifs¶
Une alerte peut etre classee faux positif seulement si: - le build cible est vert, - le diagnostic est incoherent avec le code courant, - et la cause probable (cache IDE, index stale, extension) est identifiee.
Dans ce cas: - noter clairement que l'alerte est non bloquante, - proposer une action de rafraichissement (reload projet, reindex, restart language server), - ne pas introduire de changement inutile dans le code.
Critere de fin de tache¶
La revue des alertes IDE est consideree complete quand: - toutes les erreurs bloquantes visibles sont corrigees, - les warnings critiques/securite ont une action appliquee ou planifiee, - les validations ciblées (build/tests) sont executees avec succes, - les alertes restantes sont explicitees (et justifiees si faux positifs).
09 - Écarts Documentation / Code¶
Cette section inventorie les désynchronisations entre le code du dépôt et la documentation existante (docs/, .github/copilot-instructions.md, docs/2-development/instructions/).
1. Chemin de package backend incorrect dans l'architecture¶
Fichier concerné : docs/2-development/instructions/00-architecture.md
La documentation indique :
Ce chemin n'existe pas. Les domaines sont organisés par catégorie métier, pas sous dev/docs. Chemin réel :
| Catégorie package | Domaines |
|---|---|
fr.tiogars.data.games |
brick |
fr.tiogars.data.products |
gtin, brand |
fr.tiogars.data.vehicles |
car, carmileage |
fr.tiogars.data.softwares |
android |
fr.tiogars.data.locations |
continent |
fr.tiogars.data.settings |
footerlink, menuitem, urlmanager, useraccount |
fr.tiogars.data.sync |
sync |
fr.tiogars.data.system |
serverinfo |
fr.tiogars.data.dev |
githubrepository, githubrestconfig, model |
fr.tiogars.data.docs |
section |
Correction attendue : Remplacer la structure de package fictive par le schéma réel basé sur les catégories.
2. Tableau Import/Export incomplet dans la documentation management¶
Fichier concerné : docs/1-features/1.1-management/index.md
Le tableau liste 6 domaines : GTIN, Android, Brand, Model, Brick, UrlManager.
Or les domaines Car et CarMileage disposent également de contrôleurs Import/Export complets dans le code :
CarImportController,CarExportController,CarImportCsvController,CarExportCsvControllerCarMileageImportController,CarMileageExportController,CarMileageImportCsvController,CarMileageExportCsvController
Correction attendue : Ajouter Car et CarMileage au tableau des endpoints import/export.
3. Absence de documentation sur les domaines avec support d'impression¶
Fichier concerné : docs/1-features/1.1-management/index.md et docs/2-development/instructions/02-backend.md
La convention impression est décrite (mode filtered / all, generatedAt, total) mais aucun tableau n'indique quels domaines l'ont implémentée.
Domaines avec un contrôleur Print ou Pdf dans le code :
| Domaine | Contrôleur |
|---|---|
brand |
BrandPrintController |
android |
AndroidPrintController |
gtin |
GtinPrintController |
model |
ModelPrintController |
brick |
BrickPdfController |
Domaines identifiés sans contrôleur d'impression : car, car-mileage, continent, section, url-manager, footer-link, menu-item, user-account.
Correction attendue : Créer un tableau de couverture impression dans la documentation management.
4. Features Flutter manquantes dans la documentation mobile¶
Fichier concerné : docs/2-development/instructions/07-mobile-flutter.md
La documentation liste l'arborescence Flutter suivante :
Or le code (flutter_application/lib/features/) contient également :
auth/(authentification)dashboard/(tableau de bord)
Ces deux features ne sont pas documentées.
Correction attendue : Ajouter auth/ et dashboard/ à l'arborescence et les décrire sommairement.
5. Instruction 07-mobile-flutter absente du fichier copilot-instructions¶
Fichier concerné : .github/copilot-instructions.md
La section Quick Reference liste les fichiers d'instructions 00 à 06 et 08 dans la table de navigation :
Ce lien est absent de l'index copilot-instructions, alors que le fichier 07-mobile-flutter.md existe et contient des conventions actives.
Correction attendue : Ajouter le lien vers 07-mobile-flutter.md dans la liste de navigation du fichier copilot-instructions.
6. Route gateway /api/** non documentée¶
Fichier concerné : docs/2-development/instructions/00-architecture.md, docs/1-features/1.3-mobile/architecture-sync.md
GatewayRoutesConfiguration.java expose une route catch-all /api/** en plus des routes par domaine. L'endpoint de synchronisation incrémentale mobile utilise cette route :
Cette route n'apparaît dans aucune documentation d'architecture du gateway. Elle n'est pas non plus listée dans le tableau des routes de copilot-instructions.md.
Correction attendue : Documenter la route /api/** et son rôle (synchronisation mobile) dans la documentation architecture du gateway.
7. Absence de pages de documentation fonctionnelle par domaine¶
Dossier concerné : docs/1-features/1.2-business/
Le fichier docs/1-features/1.2-business/index.md liste des sujets métier de haut niveau (Achat, Vente, Santé…) qui ne sont pas du tout implémentés. À l'inverse, les domaines implémentés n'ont aucune page fonctionnelle :
| Domaine implémenté | Page docs/1-features/ |
|---|---|
gtin |
❌ absent |
brand |
❌ absent |
brick |
❌ absent |
car |
❌ absent |
car-mileage |
❌ absent |
android |
❌ absent |
continent |
❌ absent |
section |
❌ absent |
url-manager |
❌ absent |
menu-item |
❌ absent |
footer-link |
❌ absent |
user-account |
❌ absent |
Correction attendue : Créer une page de documentation fonctionnelle par domaine implémenté (description, champs, règles métier) dans docs/1-features/1.2-business/.
8. Structure frontend dans l'architecture doc ne reflète pas la réalité¶
Fichier concerné : docs/2-development/instructions/00-architecture.md
La documentation décrit une structure avec un dossier components/<domain>/ séparé des pages, mais le code organise les composants réutilisables directement sous data-web/src/components/ (sans sous-dossier par domaine). Les features/ du frontend ne contiennent que des utilitaires transverses (apiErrorSnackbar, footerLink, menuItem, urlManager) et non des slices Redux par domaine comme suggéré.
Correction attendue : Mettre à jour la structure frontend dans la documentation pour refléter l'organisation réelle (pages/, components/, features/ transversaux, services/ générés).
Résumé des priorités¶
| Priorité | Écart | Effort |
|---|---|---|
| Haute | Chemin de package backend fictif (#1) | Faible |
| Haute | Import/Export manquants pour Car et CarMileage (#2) | Faible |
| Haute | Lien 07-mobile-flutter absent de copilot-instructions (#5) | Faible |
| Moyenne | Tableau couverture impression manquant (#3) | Moyen |
| Moyenne | Features Flutter auth/ et dashboard/ non documentées (#4) |
Moyen |
| Moyenne | Route /api/** gateway non documentée (#6) |
Moyen |
| Basse | Structure frontend à corriger (#8) | Moyen |
| Basse | Pages fonctionnelles par domaine absentes (#7) | Élevé |
3 system
Système¶
Réseau¶
Le système est organisé autour de trois points d'accès principaux:
| Composant | URL locale | Rôle |
|---|---|---|
| data-web | http://localhost:5173 |
interface Web React |
| data-gateway | http://localhost:8081 |
point d'entrée HTTP, auth, rate-limiting, routage |
| data-server | http://localhost:8080 |
API métier Spring Boot |
Surface API¶
Gateway comme point d'entrée unique¶
La gateway publie les routes métiers une à une, puis les relaie vers data-server.
Domaines routés:
/brand/model/car/car-mileage/brick/section/footer-link/menu-item/gtin/android/winget/url-manager/github-repository/continent/github-rest-config/user-account
Chaque domaine expose en pratique:
/domain/domain/**
Route API générique de synchronisation¶
En complément des routes métiers, la gateway publie aussi:
/api/**
Cette route est utilisée par la synchronisation incrémentale Flutter, notamment via:
GET /api/v1/sync/gtin/changesGET /api/v1/sync/car/changesGET /api/v1/sync/car-mileage/changesGET /api/v1/sync/android/changesGET /api/v1/sync/winget/changes
Les paramètres cursor et updatedAfter permettent au client mobile de reprendre une synchronisation différentielle.
Documentation API¶
Les contrats OpenAPI sont exposés par le backend et relayés par la gateway:
http://localhost:8081/v3/api-docshttp://localhost:8081/swagger-ui.htmlhttp://localhost:8081/swagger-ui/
Le frontend Web consomme ensuite les contrats générés via RTK Query.
Sécurité et contrôle d'accès¶
- authentification OAuth2 / JWT portée par la gateway
- rate limiting Bucket4j appliqué sur le point d'entrée HTTP
- le backend reste derrière la gateway pour les clients Web et mobiles
Flux de données¶
Web¶
data-web appelle la gateway pour:
- les listes CRUD
- les imports / exports
- l'impression
- les écrans techniques et de configuration
Mobile Flutter¶
flutter_application utilise la gateway pour:
- les refresh complets (
/gtin,/car,/car-mileage,/android,/winget/list) - les synchronisations incrémentales (
/api/v1/sync/{domain}/changes) - les opérations authentifiées avec JWT ou token OIDC
4 study
Etude¶
Cette section permet d'étudier de nouveaux sujets avec de nouvelles fonctionnalités.
4.1 roblox
Roblox¶
Roblox est une plateforme de création de jeux vidéo en ligne qui permet aux utilisateurs de concevoir, partager et jouer à des jeux créés par d'autres utilisateurs. Elle offre un environnement de développement intégré (IDE) appelé Roblox Studio, qui utilise le langage de programmation Lua pour créer des scripts et des fonctionnalités dans les jeux.
4.1.1 stories
Stories¶
Cettee section permet de lister un certain nombre de problèmes rencontrés ou fonctionnalités à implémenter dans le cadre de l'utilisation de la plateforme Roblox. Chaque problème ou fonctionnalité est présenté sous forme de "story" avec une description, des étapes à suivre et éventuellement des solutions ou des exemples de code.
Stories¶
4.1.1.1 clean friends
Clean friends¶
Cette story décrit comment nettoyer la liste d'amis d'un compte Roblox en supprimant les amis inactifs ou indésirables.
Description¶
Les jeunes utilisateurs de Roblox peuvent accumuler un grand nombre d'amis au fil du temps, mais certains de ces amis peuvent devenir inactifs ou indésirables. Il est donc important de nettoyer régulièrement la liste d'amis pour maintenir une expérience de jeu agréable.
Il faut donc créer un script ou une fonctionnalité qui permet de parcourir la liste d'amis d'un compte Roblox et de supprimer les amis qui répondent à certains critères, tels que l'inactivité prolongée ou le comportement inapproprié.
Étapes à suivre¶
- Se connecter à son compte Roblox.
- Accéder à la liste d'amis.
- Parcourir la liste d'amis et identifier les amis inactifs ou indésirables.
- Parcourir la liste d'amis et identifier les amis à conserver.
- Identifier les nouveaux amis qui nécessites une approbation ou une vérification.
- Supprimer les amis identifiés comme inactifs ou indésirables.
- Se déconnecter de son compte Roblox.
4.1.2 solutions
Solutions¶
Cette section permet de lister les solutions pour résoudre les problèmes ou besoins identifiés.
4.1.2.1 api
Roblox - API¶
La référence de l'API Roblox est disponible à cette adresse :
4.1.2.1.1 login
Roblox API - Authentification par clé API¶
L'accès à l'API Roblox Open Cloud repose sur une clé API associée à un compte Roblox défini. La référence complète est disponible sur https://create.roblox.com/docs/fr-fr/cloud/auth/api-keys.
La clé API est créée sur le Creator Dashboard → Clés API et stockée en base de données côté application. Elle est ensuite injectée dans chaque requête via l'en-tête x-api-key.
Principe¶
La clé est chargée depuis la base de données au moment de l'appel, puis transmise dans l'en-tête HTTP :
Il n'y a pas de flux de connexion préalable : la clé suffit à identifier et authentifier le compte Roblox associé pour chaque requête.
Exemple d'appel authentifié¶
Pour les requêtes avec un corps JSON :
curl --request POST 'https://apis.roblox.com/<endpoint>' \
--header 'x-api-key: <CLE_API>' \
--header 'Content-Type: application/json' \
--data '{ ... }'
Flux applicatif¶
Base de données
└── compte Roblox paramétré (username, universeId, ...)
└── clé API associée (stockée chiffrée)
│
▼
Chargement de la clé au moment de l'action
│
▼
Requête HTTP vers apis.roblox.com
avec en-tête : x-api-key: <CLE_API>
Bonnes pratiques de stockage¶
- Stocker la clé chiffrée en base de données (ne jamais en clair).
- Ne jamais l'exposer dans les logs ni dans les réponses API de l'application.
- Créer une clé par compte paramétré avec les permissions minimales nécessaires.
- La clé expire automatiquement après 60 jours sans utilisation — prévoir une alerte ou un mécanisme de renouvellement.
Dépannage - "token invalid"¶
Si vous obtenez "token invalid" pendant les tests, vérifier en priorité :
- L'en-tête utilisé est bien
x-api-key(et pasAuthorization: Bearer ...). - L'appel cible bien un endpoint Open Cloud compatible clé API (
https://apis.roblox.com/...). - La clé n'est pas expirée, révoquée, ni liée à un autre compte.
- La clé possède les permissions requises pour l'endpoint visé (scope insuffisant => rejet d'authentification).
- Aucune quote parasite, espace en trop, ni retour à la ligne dans la valeur de la clé.
Commande de vérification minimale :
curl --request GET 'https://apis.roblox.com/cloud/v2/universes/<UNIVERSE_ID>' \
--header 'x-api-key: <CLE_API>'
Si cet appel fonctionne mais qu'un autre endpoint échoue, le problème vient généralement des permissions de la clé sur cet endpoint spécifique.
4.1.2.1.2 unfriend
Roblox API unfriend¶
Exemple avec clé API¶
Le retrait d'ami se fait via une requête POST vers l'endpoint Friends Open Cloud. La clé est envoyée dans l'en-tête x-api-key.
Exemple curl :
curl --request POST "https://apis.roblox.com/friends/v1/users/<TARGET_USER_ID>/unfriend" \
--header "x-api-key: <CLE_API>"
Exemple PowerShell (Windows) :
$headers = @{ "x-api-key" = "<CLE_API>" }
Invoke-RestMethod -Method Post \
-Uri "https://apis.roblox.com/friends/v1/users/<TARGET_USER_ID>/unfriend" \
-Headers $headers
Remarques :
- TARGET_USER_ID correspond à l'identifiant Roblox de l'ami à retirer.
- La clé API doit avoir les permissions Friends requises.
- Si vous recevez token invalid, vérifier d'abord que l'en-tête est bien x-api-key (et non Authorization: Bearer).