đ± UrbaFix
Documentation Technique ComplĂšte
1. Vue d'Ensemble
Qu'est-ce que UrbaFix ?
UrbaFix est une application mobile native Android permettant aux citoyens de signaler des incidents urbains (nids de poule, déchets, éclairage défectueux, etc.) à leur municipalité de maniÚre simple et rapide.
30 secondes via smartphone
Localisation précise
Preuve visuelle (vidéo 5s max)
Sync automatique
Technologies Utilisées
Android
- Kotlin 1.9.20
- Jetpack Compose
- Room Database
- Retrofit + OkHttp
- WorkManager
Backend
- PHP 8.1+
- MySQL 8.0
- Nginx
- Docker Swarm
- Let's Encrypt
Sécurité
- AES-256-GCM
- HKDF
- SHA-256
- TLS 1.3
- Docker Secrets
- Temps de signalement : 30 secondes
- Types d'incidents : 8 catégories par défaut
- Zone géographique : France + Monaco
- Android minimum : Android 8.0 (API 26)
2. Contexte et Objectifs
ProblÚme Résolu
â Avant UrbaFix
- Communication difficile citoyen â mairie
- Délais de signalement longs (appels, courriers)
- Absence de suivi des demandes
- Pas de preuve photographique
â Avec UrbaFix
- Signalement instantané via app mobile
- Géolocalisation GPS automatique
- Suivi en temps réel de l'état
- Photos attachées pour preuve
Objectifs Stratégiques
-
Améliorer la réactivité municipale
Réduire le délai moyen de prise en charge de 7 jours à 48h
-
Augmenter la participation citoyenne
Objectif : 500 signalements/mois pour une ville de 30 000 habitants
-
Digitaliser la relation citoyen-mairie
80% des signalements via app d'ici 2025
3. Acteurs et Utilisateurs
Sophie, 34 ans
Citoyenne Active
Profil : MÚre de famille, travaille en télétravail, utilise smartphone quotidiennement
Besoins :
- Signaler rapidement sans compte
- Voir l'état d'avancement
- Fonctionnement offline (zones blanches)
Marc, 45 ans
Agent Municipal
Profil : Responsable service voirie, gĂšre 50 incidents/semaine
Besoins :
- Dashboard centralisé
- Filtrage par type/statut/date
- Export pour reporting
Julie, 28 ans
Développeuse
Profil : Développeuse full-stack, maintient l'application
Besoins :
- Documentation technique complĂšte
- API REST bien documentée
- Guides de déploiement
4. Besoins Fonctionnels
Signalement d'Incidents
Description : Un citoyen doit pouvoir signaler un incident urbain en moins de 60 secondes.
CritĂšres d'acceptation :
- â GPS dĂ©tecte position automatiquement
- â SĂ©lection type incident via liste prĂ©dĂ©finie
- â Ajout de 0 Ă 3 photos
- â Description optionnelle (500 caractĂšres max)
- â Fonctionne offline avec sync diffĂ©rĂ©e
Visualisation sur Carte
Description : Les incidents doivent ĂȘtre affichĂ©s sur une carte interactive (OpenStreetMap).
CritĂšres d'acceptation :
- â Carte centrĂ©e sur position utilisateur
- â Markers colorĂ©s par type d'incident
- â Tap sur marker affiche dĂ©tails
- â Filtrage par type/statut
Suivi des Incidents
Description : Un citoyen peut consulter l'historique de ses signalements.
CritĂšres d'acceptation :
- â Liste incidents triĂ©e par date
- â Statuts : En attente / En cours / RĂ©solu / RefusĂ©
- â DĂ©tails : photos, description, position
- â Anonymat prĂ©servĂ© (pas de compte obligatoire)
Dashboard Mairie
Description : Les mairies disposent d'une interface web pour gérer les incidents.
CritĂšres d'acceptation :
- â Authentification JWT
- â Filtrage par statut/type/date
- â Changement statut incident
- â Export CSV pour statistiques
Gestion RGPD
Description : Conformité RGPD complÚte (anonymat, droit à l'oubli, portabilité).
CritĂšres d'acceptation :
- â Anonymat par dĂ©faut (fingerprinting)
- â Droit d'accĂšs (GET /get_citoyen.php)
- â Droit Ă l'oubli (DELETE /delete_citoyen.php)
- â PortabilitĂ© (export JSON)
- â Chiffrement AES-256-GCM
5. Besoins Non-Fonctionnels
⥠Performance
| Métrique | Objectif |
|---|---|
| Temps de chargement app | < 2 secondes |
| Temps d'envoi incident | < 3 secondes (réseau 4G) |
| Affichage carte (100 incidents) | < 1 seconde |
| Compression photo | < 1 seconde par photo |
đ SĂ©curitĂ©
- Chiffrement AES-256-GCM pour données sensibles
- HTTPS uniquement (TLS 1.3)
- Protection SQL injection (requĂȘtes prĂ©parĂ©es)
- Protection XSS (échappement HTML)
- Rate limiting (10 req/s par IP)
- Master key en Docker Secret (RAM only)
đ DisponibilitĂ©
- SLA : 99.5% (43h downtime/an max)
- Backup BDD : Quotidien (rétention 30j)
- Mode dégradé : Offline-first (app fonctionne sans réseau)
- Monitoring : Nginx logs + alertes email
đ± CompatibilitĂ©
| Plateforme | Version minimale |
|---|---|
| Android | 8.0 (API 26) |
| PHP | 8.1 |
| MySQL | 8.0 |
| Navigateurs (dashboard) | Chrome 90+, Firefox 88+ |
6. Contraintes
đ§ Contraintes Techniques
- Pas d'identifiants matériels interdits : IMEI, MAC address (RGPD)
- Stockage limité : Max 3 photos par incident, compression JPEG 85%
- Offline-first obligatoire : Zones rurales sans réseau
- OpenStreetMap uniquement : Pas de Google Maps (coûts)
âïž Contraintes RĂ©glementaires
- RGPD (EU 2016/679) : Conformité complÚte obligatoire
- ePrivacy : Pas de tracking sans consentement
- Hébergement France : Données sensibles sur territoire UE
- Accessibilité : WCAG 2.1 niveau AA (dashboard)
đ° Contraintes BudgĂ©taires
- HĂ©bergement : < 50âŹ/mois (VPS OVH)
- Pas de licences payantes : Open-source uniquement
- Bande passante : Optimiser taille photos
7. Parcours Utilisateur Citoyen
Premier Lancement
- Affichage politique confidentialité
- Demande permissions GPS + Photos
- Génération fingerprint (SHA-256)
- Ouverture sur onglet "Accueil"
Signalement Incident
- Tap onglet "Signaler"
- GPS détecte position automatiquement
- Sélection type (8 catégories)
- Prise de photos (0 Ă 3)
- Description facultative
- Tap "Envoyer"
Confirmation
- Message "Signalement envoyé avec succÚs"
- Si offline : "Sera envoyé quand réseau disponible"
- Retour automatique Ă l'onglet "Accueil"
- Incident visible sur carte
Suivi
- Onglet "Mes incidents"
- Liste avec statuts colorés
- Tap pour voir détails
- Notification si statut change (optionnel)
8. Parcours Utilisateur Mairie
AccĂšs Dashboard
- Navigation vers
https://urbafix.fr/dashboard/ - Connexion JWT (email + mot de passe)
- Affichage liste incidents avec filtres
Traitement Incident
â Agent consulte dĂ©tails + photos
â Clic "Marquer en cours"
â Intervention terrain
â Clic "Marquer rĂ©solu"
â ArchivĂ© aprĂšs 2 ans
9. Cas d'Usage Détaillés
Signalement Basique (Mode Online)
Acteur principal : Sophie (citoyenne)
Préconditions : App installée, permissions GPS accordées, réseau 4G disponible
Scénario principal :
- Sophie constate un nid de poule devant chez elle
- Elle ouvre UrbaFix et tap "Signaler"
- GPS détecte automatiquement : 43.7737, 7.4951
- Elle sélectionne type "Voirie"
- Elle prend 2 photos du trou
- Elle écrit "Trou dangereux 30cm, voie de droite"
- Elle tap "Envoyer"
- App compresse photos â 800 KB total
- App envoie POST /signaler_mairie_avec_compte.php
- API retourne :
{"success": true, "incident_id": 1234} - App affiche "Signalement envoyé avec succÚs"
- Incident visible dans "Mes incidents" avec statut "En attente"
Postconditions : Incident stocké en base MySQL, visible dans dashboard mairie
Signalement en Mode Offline
Acteur principal : Sophie
Préconditions : App installée, aucun réseau disponible
Scénario principal :
- Sophie est en zone blanche (pas de 4G)
- Elle constate un dépÎt sauvage
- Elle ouvre UrbaFix â indicateur "Offline" affichĂ©
- Elle tap "Signaler" (fonctionne quand mĂȘme)
- GPS détecte position via satellite (pas de réseau nécessaire)
- Elle sélectionne type "Déchets"
- Elle prend 1 photo
- Elle tap "Envoyer"
- App sauvegarde en Room Database avec
syncStatus = PENDING - Message : "Signalement sauvegardé. Sera envoyé quand réseau disponible."
- 15 minutes plus tard, Sophie rentre chez elle (WiFi)
- WorkManager détecte réseau disponible
- SyncIncidentsWorker démarre automatiquement
- Incident envoyé à l'API
syncStatuspasse ĂSYNCED
Postconditions : Incident synchronisé, statut "Synced" dans l'app
10. Architecture Globale
Schéma d'Architecture
âââââââââââââââââââââââââââââââââââââââââââââââââââ
â APPLICATION ANDROID â
â ââââââââââââ ââââââââââââ ââââââââââââ â
â â UI â âViewModel â âRepositoryâ â
â â (Compose)ââââ (MVVM) ââââ (Mediator) â
â ââââââââââââ ââââââââââââ ââââââŹââââââ â
â â â
â ââââââââââââââââââââââŒââââââââââ â
â ⌠⌠â â
â ââââââââââââ ââââââââââââ â â
â â Room â â Retrofit â â â
â â Database â â API â â â
â ââââââââââââ âââââââŹâââââ â â
â (SQLite local) â â â
âââââââââââââââââââââââââââââââââââââŒââââââââââ â
â HTTPS â
⌠â
âââââââââââââââââââââââââââââââ
â BACKEND PHP ââ
â ââââââââââââââââââââââ ââ
â â Nginx + PHP-FPM â ââ
â â - Endpoints REST â ââ
â â - CryptoManager â ââ
â â - Repositories â ââ
â âââââââââââŹâââââââââââ ââ
â â ââ
â ⌠ââ
â ââââââââââââââââââââââ ââ
â â MySQL Database â ââ
â â - incidents â ââ
â â - citoyens (chiffrĂ©) ââ
â â - mairies â ââ
â â - photos â ââ
â ââââââââââââââââââââââ ââ
â ââ
â Docker Secret: ââ
â /run/secrets/master_key ââ
âââââââââââââââââââââââââââââââ
â
Flux de Données
- Création incident (online) :
UI â ViewModel â Repository â Retrofit â API â MySQL
- Création incident (offline) :
UI â ViewModel â Repository â Room â (WorkManager) â Retrofit â API
- Lecture incidents :
UI â ViewModel â Repository â Room (source unique de vĂ©ritĂ©)
- Synchronisation :
WorkManager â Repository â (SELECT PENDING) â Retrofit â API â (UPDATE SYNCED)
11. Architecture Android (MVVM)
Pattern MVVM
Composables Jetpack Compose
ReportScreen.kt
- Affichage UI
- Gestion interactions
- collectAsState()
Logique de présentation
ReportViewModel.kt
- StateFlow<UiState>
- Gestion état UI
- Appels Repository
Logique métier + données
IncidentRepository.kt
- AccĂšs Room + Retrofit
- Logique offline-first
- Entités métier
Structure des Packages
app/src/main/java/com/example/monquartierkotlin/ âââ MainActivity.kt âââ UrbafixApplication.kt â âââ data/ â âââ local/ â â âââ AppDatabase.kt â â âââ Converters.kt â â âââ entities/ â â â âââ IncidentEntity.kt â â â âââ PhotoEntity.kt â â â âââ MairieEntity.kt â â âââ dao/ â â âââ IncidentDao.kt â â âââ PhotoDao.kt â â â âââ remote/ â â âââ UrbafixApi.kt â â âââ RetrofitClient.kt â â âââ dto/ â â âââ IncidentDto.kt â â âââ DtoMappers.kt â â â âââ repository/ â âââ IncidentRepository.kt â âââ ui/ â âââ screens/ â â âââ home/ â â â âââ HomeScreen.kt â â â âââ HomeViewModel.kt â â âââ report/ â â â âââ ReportScreen.kt â â â âââ ReportViewModel.kt â â âââ ... â â â âââ components/ â âââ MapView.kt â âââ IncidentCard.kt â âââ workers/ â âââ SyncIncidentsWorker.kt â âââ utils/ âââ FingerprintManager.kt âââ DeviceIdManager.kt âââ ImageCompressor.kt
12. Architecture Backend (PHP)
Structure Backend
www/ âââ index.php # Router principal âââ composer.json â âââ src/ â âââ Database.php # Singleton PDO â â â âââ Crypto/ â â âââ CryptoManager.php # AES-256-GCM â â â âââ Utils/ â â âââ FingerprintValidator.php â â âââ AuditLogger.php â â â âââ Repositories/ â âââ IncidentRepository.php â âââ CitoyenRepository.php â âââ ââ Endpoints API ââ âââ check_mairie.php âââ signaler_mairie_avec_compte.php âââ get_incidents.php âââ update_citoyen.php âââ ...
Pattern Repository
// src/Repositories/IncidentRepository.php class IncidentRepository { private $db; public function __construct() { $this->db = Database::getInstance(); } public function create($data): int { return $this->db->execute( "INSERT INTO incidents (...) VALUES (...)", [$data] ); } public function findByCitoyen(int $citoyenId): array { return $this->db->query( "SELECT * FROM incidents WHERE citoyen_id = ?", [$citoyenId] ); } }
13. Base de Données
Schéma Complet
-- Table citoyens (donnĂ©es CHIFFRĂES) CREATE TABLE citoyens ( id INT AUTO_INCREMENT PRIMARY KEY, fingerprint VARCHAR(64) NOT NULL UNIQUE, salt VARCHAR(44) NOT NULL, -- ChiffrĂ©es AES-256-GCM (base64) nom TEXT NULL, prenom TEXT NULL, email TEXT NULL, code_postal VARCHAR(5) NOT NULL, created_at DATETIME NOT NULL ); -- Table incidents CREATE TABLE incidents ( id INT AUTO_INCREMENT PRIMARY KEY, citoyen_id INT NULL, -- NULL si citoyen supprimĂ© mairie_id INT NOT NULL, type_id INT NOT NULL, description TEXT, latitude DECIMAL(10,8) NOT NULL, longitude DECIMAL(11,8) NOT NULL, code_insee VARCHAR(5), status ENUM('en_attente', 'en_cours', 'resolu', 'refuse'), created_at DATETIME NOT NULL, updated_at DATETIME, resolved_at DATETIME, FOREIGN KEY (citoyen_id) REFERENCES citoyens(id) ON DELETE SET NULL, -- PrĂ©serve incidents FOREIGN KEY (mairie_id) REFERENCES mairies(id), FOREIGN KEY (type_id) REFERENCES types(id) ); -- Index pour performances CREATE INDEX idx_incidents_mairie_status ON incidents(mairie_id, status, created_at DESC); CREATE INDEX idx_incidents_gps ON incidents(latitude, longitude);
- Données chiffrées : nom, prenom, email (AES-256-GCM)
- Master key : Stockée en Docker Secret (RAM uniquement)
- PrĂ©servation statistiques : Suppression citoyen â incidents prĂ©servĂ©s avec citoyen_id=NULL
- Fingerprint : SHA-256, 64 caractÚres hexadécimaux
- GPS sans défaut : Application mobile sans position GPS par défaut (position: {lat: null, lng: null})
14. API REST - Endpoints
/check_mairie.php
Description : Vérifie si une mairie existe et possÚde un compte actif.
ParamĂštres :
"code_postal": "06500"
Réponse 200 :
{
"success": true,
"has_account": true,
"mairie_id": 42,
"nom": "Menton"
}
/signaler_mairie_avec_compte.php
Description : Envoie un incident vers une mairie ayant un compte.
Content-Type : multipart/form-data
ParamĂštres :
citoyen_fingerprint: String (64 chars)mairie_id: Integertype_id: Integer (1-8)description: String (max 500)latitude: Doublelongitude: Doublephoto1, photo2, photo3: File (JPEG, max 5MB)
Réponse 201 :
{
"success": true,
"incident_id": 1234,
"message": "Signalement enregistré avec succÚs"
}
/get_incidents.php
Description : RécupÚre la liste des incidents d'un citoyen.
ParamĂštres :
citoyen_fingerprint: Stringlimit: Integer (défaut: 50)offset: Integer (défaut: 0)
Réponse 200 :
{
"success": true,
"total": 127,
"incidents": [
{
"id": 1234,
"type_nom": "Voirie",
"description": "Nid de poule",
"status": "en_cours",
"created_at": "2024-12-15T10:30:00Z",
"photos": [...]
}
]
}
Codes d'Erreur HTTP
| Code | Signification | Exemple |
|---|---|---|
| 200 | OK | RequĂȘte rĂ©ussie |
| 201 | Created | Incident créé avec succÚs |
| 400 | Bad Request | ParamĂštre manquant ou invalide |
| 401 | Unauthorized | Fingerprint invalide |
| 404 | Not Found | Ressource non trouvée |
| 500 | Internal Server Error | Erreur serveur |
15. Format des Données
DTOs (Data Transfer Objects)
Les DTOs servent à transférer des données entre l'application Android et l'API backend.
IncidentDto (Android â API)
data class IncidentDto( @SerializedName("citoyen_fingerprint") val citoyenFingerprint: String, @SerializedName("type_id") val typeId: Int, @SerializedName("latitude") val latitude: Double, @SerializedName("longitude") val longitude: Double )
Format des Dates
ISO 8601 utilisé partout : 2024-12-15T10:30:00Z
Kotlin
val timestamp = Instant.parse( "2024-12-15T10:30:00Z" ).toEpochMilli()
PHP
$datetime = new DateTime( '2024-12-15T10:30:00Z' );
Enums et Constantes
SyncStatus (Android)
enum class SyncStatus { PENDING, // En attente de sync SYNCING, // En cours de synchronisation SYNCED, // Synchronisé avec succÚs ERROR // Erreur lors de la sync }
IncidentStatus (Backend)
ENUM('en_attente', 'en_cours', 'resolu', 'refuse')
16. Authentification et Sécurité
Device Fingerprinting
Principe : Chaque appareil Android génÚre un identifiant unique basé sur ses caractéristiques matérielles.
Unique par appareil
Manufacturer, Model, Hardware
Liste capteurs (nom, vendor)
Hash de tous les composants
Résultat :
a7b3c9e4f1d2e8a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9a8b7c6d5e4f3a2
Sécurité HTTPS
- Certificat : Let's Encrypt (renouvellement auto)
- Protocoles : TLS 1.2, TLS 1.3 uniquement
- HSTS : Force HTTPS pendant 2 ans
- Headers : X-Frame-Options, X-Content-Type-Options, X-XSS-Protection
Toujours utiliser des requĂȘtes prĂ©parĂ©es (PDO) :
// â BON (sĂ©curisĂ©) $result = $db->query( "SELECT * FROM citoyens WHERE id = ?", [$userId] ); // â MAUVAIS (vulnĂ©rable) $result = $db->query( "SELECT * FROM citoyens WHERE id = $userId" );
17. SystĂšme de Signalement
Flux Complet
Ouvre app, tap "Signaler"
Affiche carte + formulaire
Détecte coordonnées
Compression JPEG 85%
Save local + API
INSERT MySQL
Types d'Incidents
Nids de poule, chaussée dégradée
Lampadaire défectueux
DépÎt sauvage, poubelle
Panneau manquant, marquage
Arbre dangereux, entretien
Banc cassé, abribus
Tag, dégradation
Autre type d'incident
18. Géolocalisation et Cartographie
Détection Position GPS
Stratégie de fallback pour maximiser la précision :
- FusedLocationProvider (Google Play Services) : Précision optimale
- GPS Provider : Si Play Services indisponible
- Network Provider : Si GPS désactivé
- Position par défaut : Menton (43.7737, 7.4951)
Intégration OpenStreetMap
BibliothĂšque : osmdroid 6.1.18
Avantages :
- â Gratuit (pas d'API key)
- â DonnĂ©es ouvertes (OpenStreetMap)
- â Offline tiles possible
- â Pas de limite de requĂȘtes
Géocodage Inverse (Code INSEE)
API : geo.gouv.fr
Endpoint : GET /communes?lat=43.7737&lon=7.4951
Réponse :
{
"nom": "Menton",
"code": "06083",
"codesPostaux": ["06500"]
}
Utilité : Identifier la commune précise (plusieurs communes peuvent partager un code postal).
19. Gestion des Photos & Vidéos
đ· Compression d'Images
Objectif : Réduire la taille des photos pour optimiser upload et stockage.
ParamĂštres :
- Max Width : 1920px
- Max Height : 1080px
- JPEG Quality : 85%
- Format de sortie : Base64 (pour stockage Room)
RĂ©sultat typique : Photo 4MB â 400KB (-90%)
đ„ Support VidĂ©o (v1.5.0)
Caractéristiques Vidéo
- Durée maximale : 5 secondes (troncature automatique si dépassement)
- Taille maximale : 5 MB
- Format : MP4 (conversion automatique via MediaMuxer)
- Upload : Base64 (compatible infrastructure existante)
Traitement Vidéo (VideoCompressor.kt)
suspend fun getVideoInfo(context: Context, uri: Uri): VideoInfo suspend fun compressVideo(context: Context, uri: Uri): File? suspend fun trimVideoTo5Seconds(context: Context, uri: Uri): File?
Interface Utilisateur
- 4 boutons de capture : Photo, Galerie, VidĂ©o 5s, Galerie â¶
- Aperçu vidéos avec indicateurs visuels :
- Bordure bleue (2dp) pour distinguer des photos
- IcÎne ⶠcentrée
- Badge durée en bas à droite (ex: "3s")
- Compteur séparé : "2 photo(s) ⹠1 vidéo(s)"
â ïž Avertissement RGPD Obligatoire
Contrairement aux photos, le floutage automatique des visages n'est pas disponible pour les vidéos.
- Ăvitez de filmer des personnes identifiables
- Concentrez-vous sur le problĂšme Ă signaler
- Respectez la vie privée des passants
En continuant, l'utilisateur confirme avoir pris connaissance de ces recommandations et s'engage à respecter la protection des données personnelles.
Base de Données
@Entity(tableName = "photos_incident")
data class PhotoEntity(
val mediaType: MediaType = MediaType.PHOTO, // PHOTO ou VIDEO
val durationMs: Long? = null, // Durée vidéo en ms
val photoData: String? = null, // Base64
// ... autres champs
)
enum class MediaType { PHOTO, VIDEO }
đ API Backend - Upload VidĂ©os
Format JSON (nouveau en v1.5.0) :
// Structure DTO
data class VideoData(
val video: String, // "data:video/mp4;base64,..."
val latitude: Double?, // Coordonnées GPS
val longitude: Double?
)
data class SubmitIncidentRequest(
val mairieId: Long,
val typeId: Long,
val adresse: String,
val latitude: Double,
val longitude: Double,
val description: String,
val deviceId: String,
val photos: List<String>?, // Base64 avec prefix
val videos: List<VideoData>? // Objets avec GPS
)
Endpoint API :
POST /api/submit_incident.php- Content-Type:
application/json - Méthode Retrofit :
submitIncidentJson(@Body request)
Traitement PHP (submit_incident.php:176-241) :
// Extraction tableau videos depuis JSON
foreach ($data['videos'] as $index => $videoData) {
$videoBase64 = $videoData['video']; // "data:video/mp4;base64,..."
$videoLat = $videoData['latitude'];
$videoLng = $videoData['longitude'];
// Décodage et sauvegarde dans /uploads/videos/
$filename = $incidentId . '_video_' . time() . '_' . $index . '.mp4';
// Insertion dans table videos_incident
INSERT INTO videos_incident (
incident_id, type, filepath, filename,
latitude, longitude, filesize, mime_type
) VALUES (...)
}
Table videos_incident (serveur MySQL) :
- Colonnes principales : id, incident_id, type, filename, filepath, description
- Métadonnées : duration (secondes), filesize (octets), mime_type
- GPS : latitude, longitude (DECIMAL 10,8 et 11,8)
- Sécurité : encrypted (TINYINT), created_at (TIMESTAMP)
- Contrainte : FK incident_id â incidents(id) ON DELETE CASCADE
- Index : incident_id, type, encrypted, GPS (lat+lng)
Stockage serveur :
- Répertoire :
/uploads/videos/ - Format fichier :
123_video_1735917234_0.mp4 - Format vidéo : MP4 (H.264 + AAC)
- Durée max : 5 secondes
- Taille max : 5 MB
Stockage
đ± Local (Android)
- Room Database
- Table
photos - Colonne
photo_data: TEXT (Base64) - Compression avant insert
âïž Serveur (Backend)
- Répertoire
uploads/incidents/ - Format :
incident_1234_1.jpg - Thumbnails :
thumbs/ - Permissions : 0644 (lecture publique)
Upload Multipart
// Kotlin - Préparation upload val file = File(photoPath) val requestBody = file.asRequestBody( "image/jpeg".toMediaTypeOrNull() ) val part = MultipartBody.Part.createFormData( "photo1", file.name, requestBody )
20. Synchronisation Offline-First
Architecture Offline-First
Principe : L'application fonctionne d'abord en mode local, puis synchronise quand le réseau est disponible.
INSERT Room Database
syncStatus = PENDING
Planifie sync (15 min)
Contrainte: réseau
Worker démarre
SELECT WHERE PENDING
POST /signaler_*.php
multipart/form-data
UPDATE Room
syncStatus = SYNCED
SyncIncidentsWorker
Déclenchement :
- Périodicité : 15 minutes
- Contrainte : Réseau disponible (CONNECTED)
- Backoff : Exponentiel si échec
Logique :
- SELECT incidents WHERE syncStatus = PENDING
- Pour chaque incident :
- UPDATE syncStatus = SYNCING
- Appel API avec photos
- Si succĂšs : syncStatus = SYNCED + serverId
- Si échec : syncStatus = ERROR
- Retourner Result.success() ou Result.retry()
- Fonctionne en zones sans réseau (rural, tunnel)
- UX immédiate (pas d'attente réseau)
- Résilience aux coupures réseau
- Ăconomie de batterie (moins de requĂȘtes)
21. Chiffrement des Données
Le chiffrement AES-256-GCM a été déployé avec succÚs sur le serveur de production.
Architecture de Chiffrement Implémentée
Fichier sécurisé
32 bytes random (256 bits)
/run/secrets/app_encryption_key
Par opération
12 bytes random (96 bits)
Généré à chaque chiffrement
Base64 encoded
IV || ciphertext || tag
Stocké en TEXT (MySQL)
- IV: 12 bytes (96 bits) - Vecteur d'initialisation unique
- Ciphertext: Taille variable selon donnée en clair
- Tag: 16 bytes (128 bits) - Tag d'authentification GCM
- Total: Encodé en Base64 et stocké en TEXT
Données Chiffrées (Table citoyens)
| Champ | Type BDD | Chiffré ? | Raison |
|---|---|---|---|
nom |
TEXT | â OUI | DonnĂ©es personnelles (RGPD Art. 32) |
prenom |
TEXT | â OUI | DonnĂ©es personnelles (RGPD Art. 32) |
email |
TEXT | â OUI | DonnĂ©es personnelles (RGPD Art. 32) |
telephone |
TEXT | â OUI | DonnĂ©es personnelles (RGPD Art. 32) |
device_id |
VARCHAR(255) | â NON | Hash SHA-256 anonyme (fingerprint) |
| Description incidents | TEXT | â NON | NĂ©cessaire pour recherche full-text |
| GPS (lat/lng) | DECIMAL | â NON | NĂ©cessaire pour clustering carte |
Implémentation Technique
Classe EncryptionManager (PHP)
class EncryptionManager {
private const SECRET_PATH = '/run/secrets/app_encryption_key';
private const CIPHER_METHOD = 'aes-256-gcm';
private const IV_LENGTH = 12; // 96 bits
private const TAG_LENGTH = 16; // 128 bits
public function encrypt(?string $plaintext): ?string {
if ($plaintext === null || $plaintext === '') {
return null;
}
// Générer IV unique
$iv = random_bytes(self::IV_LENGTH);
$tag = '';
// Chiffrer avec AES-256-GCM
$ciphertext = openssl_encrypt(
$plaintext,
self::CIPHER_METHOD,
$this->key,
OPENSSL_RAW_DATA,
$iv,
$tag
);
// Retourner: base64(IV || ciphertext || tag)
return base64_encode($iv . $ciphertext . $tag);
}
}
Hook beforeSave (update_profile.php)
$encryption = EncryptionManager::getInstance();
// Chiffrer avant sauvegarde
$nomChiffre = $encryption->encrypt($nom);
$prenomChiffre = $encryption->encrypt($prenom);
$emailChiffre = $encryption->encrypt($email);
$telephoneChiffre = $encryption->encrypt($telephone);
// UPDATE avec données chiffrées
$db->execute(
"UPDATE citoyens SET nom = ?, prenom = ?, email = ?, telephone = ? WHERE id = ?",
[$nomChiffre, $prenomChiffre, $emailChiffre, $telephoneChiffre, $id]
);
Hook afterLoad (get_profile.php)
$encryption = EncryptionManager::getInstance();
// Récupérer depuis BDD (chiffré)
$citoyen = $db->fetchOne("SELECT * FROM citoyens WHERE device_id = ?", [$deviceId]);
// Déchiffrer aprÚs lecture
$nomDechiffre = $encryption->decrypt($citoyen['nom']);
$prenomDechiffre = $encryption->decrypt($citoyen['prenom']);
$emailDechiffre = $encryption->decrypt($citoyen['email']);
$telephoneDechiffre = $encryption->decrypt($citoyen['telephone']);
Stockage de la Clé
La clé de chiffrement est stockée dans un fichier sécurisé sur le serveur avec permissions restrictives.
| Aspect | Implémentation |
|---|---|
| Emplacement serveur | /srv/urbafix/secrets/app_encryption_key |
| Emplacement conteneur | /run/secrets/app_encryption_key |
| Permissions fichier | 600 (lecture/écriture propriétaire uniquement) |
| Permissions dossier | 700 (accÚs propriétaire uniquement) |
| Montage Docker | Volume en lecture seule (:ro) |
| Backup | Sauvegardé hors serveur (gestionnaire de secrets) |
Configuration docker-compose.yml
services:
web:
volumes:
- ./:/var/www/html
- ./secrets/app_encryption_key:/run/secrets/app_encryption_key:ro
Architecture Mobile vs Dashboard
- Ne peut PAS déchiffrer les données du serveur
- Raison: Pas d'accÚs à la clé de chiffrement (sécurité serveur)
- Source de vérité: SharedPreferences local (données en clair)
- RÎle API: Backup chiffré uniquement (write-only)
- Peut déchiffrer les données du serveur
- Raison: AccÚs à la clé via le conteneur Docker
- Endpoint:
get_profile.phpavec déchiffrement automatique - Usage: Visualisation et gestion des profils citoyens
Android App
â (donnĂ©es en clair)
SharedPreferences (local, source de vérité)
â (sync via update_profile.php)
API Backend (chiffrement AES-256-GCM)
â (stockage)
Base de données MySQL (données chiffrées en TEXT)
â (lecture via get_profile.php)
Dashboard Web (déchiffrement automatique)
- Protection contre vol de BDD: Dump SQL inutilisable sans la clé
- Conformité RGPD Article 32: Mesures techniques appropriées
- Confidentialité + Intégrité: GCM garantit authentification
- Séparation clé/données: Master key hors de la base de données
- IV unique: Pas de réutilisation, sécurité maximale
- Migration transparente: Données existantes converties progressivement
Documentation ComplĂšte
Pour plus d'informations sur le déploiement, la sécurité et le dépannage :
- đ README_ENCRYPTION.md - Guide complet de dĂ©ploiement
- đ DEPLOYMENT_SUCCESS.md - Rapport de dĂ©ploiement production
- đ migration_encrypt_citoyens.sql - Script de migration SQL
- đ EncryptionManager.php - Classe de chiffrement (src/)
22. Gestion des Identités
Anonymat par Défaut
Principe : L'utilisateur est anonyme jusqu'à ce qu'il décide de renseigner ses informations.
Génération fingerprint unique
a7b3c9e4f1d2...
Nom, prénom, email : NULL
10 incidents créés
Utilisateur remplit profil
nom, prenom, email chiffrés
10 incidents existants liés à l'identité
Via fingerprint unique
Avantages
- BarriÚre d'entrée minimale (pas de compte obligatoire)
- Conformité RGPD (minimisation des données)
- ContrÎle utilisateur sur son identité
- Persistance entre réinstallations (fingerprint stable)
23. Conformité RGPD
Principes RGPD Appliqués
| Principe | Article | Implémentation UrbaFix |
|---|---|---|
| Minimisation des données | Art. 5.1.c | Nom/prénom/email facultatifs, anonymat par défaut |
| Limitation conservation | Art. 5.1.e | Incidents résolus supprimés aprÚs 2 ans |
| Intégrité et confidentialité | Art. 5.1.f | Chiffrement AES-256-GCM, HTTPS, fingerprinting |
| Droit d'accÚs | Art. 15 | GET /get_citoyen.php retourne toutes les données |
| Droit Ă l'oubli | Art. 17 | DELETE /delete_citoyen.php supprime tout |
| Portabilité | Art. 20 | GET /export_data.php export JSON complet |
| Privacy by Design | Art. 25 | Fingerprinting au lieu de compte obligatoire |
Droits des Utilisateurs
đ Droit d'accĂšs
Consulter ses données dans l'onglet "Profil"
GET /get_citoyen.php
đïž Droit Ă l'oubli
Supprimer son compte Ă tout moment
DELETE /delete_citoyen.php
đŠ PortabilitĂ©
Exporter ses données au format JSON
GET /export_data.php
Article 6.1.a (Consentement) : Pour le stockage du nom/prénom/email (facultatif)
Article 6.1.f (IntĂ©rĂȘt lĂ©gitime) : Pour la gestion des incidents (intĂ©rĂȘt public local)
24. Guide Développeur Android
Setup Environnement
Prérequis :
- Android Studio Hedgehog (2023.1.1+)
- JDK 17
- Android SDK 34
- Ămulateur Android 8.0+ (API 26)
Installation :
git clone <repo_url> UrbafixKotlin cd UrbafixKotlin ./gradlew build
Commandes Utiles
./gradlew clean assembleDebug
Build APK debug
./gradlew installDebug
Installer sur appareil
./gradlew test
Lancer tests unitaires
adb logcat | grep ViewModel
Voir logs en temps réel
Bonnes Pratiques
- â Toujours passer par ViewModel â Repository â DAO
- â Utiliser
viewModelScopepour coroutines - â
Dispatchers.IOpour base de donnĂ©es/rĂ©seau - â
StateFlowpour exposer l'Ă©tat UI - â Pattern
sealed classpour Ă©tats (Loading, Success, Error) - â Sauvegarder en local d'abord (offline-first)
25. Guide Développeur Backend
Setup Local
cd /home/franck/AndroidStudioProjects/UrbaFix/www
composer install
cp config/database.example.php config/database.php
# Créer base de données
mysql -u root -p < database/schema.sql
Le répertoire www/ est un montage SSHFS vers la production.
Toute modification est LIVE immédiatement !
OBLIGATOIRE avant modification :
./backup-before-deploy.sh "Description changement"
Rollback si problĂšme :
LATEST=$(ls -t backups/ | head -1) cp -r backups/$LATEST/* www/
Créer un Endpoint
- Créer fichier
www/mon_endpoint.php - Inclure
Database.phpet utilitaires nécessaires - Valider paramÚtres d'entrée
- Utiliser requĂȘtes prĂ©parĂ©es (PDO)
- Retourner JSON avec header
Content-Type: application/json - Gérer codes HTTP appropriés (200, 400, 404, 500)
26. Guide Déploiement
Déploiement Android (Google Play)
- Build release :
./gradlew clean assembleRelease
- Signer APK :
jarsigner -keystore urbafix-release.keystore app-release.apk
- Upload : Google Play Console â Production
- Review : Attendre validation (1-3 jours)
Déploiement Backend (Production)
Architecture :
- VPS OVH (Debian 12)
- Nginx + PHP 8.1-FPM
- MySQL 8.0
- Let's Encrypt SSL
Déploiement :
ssh root@urbafix.fr cd /var/www/urbafix git pull origin main composer install --no-dev systemctl restart php8.1-fpm nginx
Docker (Recommandé)
# Générer master key openssl rand -base64 32 | docker secret create urbafix_master_key - # Démarrer stack docker-compose up -d # Vérifier logs docker-compose logs -f php
Docker Hardened Images (DHI)
Ătat : En Ăvaluation
Docker Hardened Images sont des images sécurisées rendues gratuites depuis le 17 décembre 2025.
Authentification :
# Se connecter avec PAT Docker Hub
docker login dhi.io -u username -p YOUR_PAT
Disponibilité pour UrbaFix :
- â PHP 8.2 : Disponible mais CLI uniquement (pas FPM)
- â MariaDB : Non disponible dans le catalogue
- â MySQL : Erreurs lors du pull
Images actuelles (Production) :
# Dockerfile FROM php:8.2-fpm # docker-compose.yml db: image: mariadb:10.11 phpmyadmin: image: phpmyadmin:latest
CritĂšres pour adoption DHI :
- Disponibilité de
dhi.io/php:8.2-fpm - Support de MariaDB ou MySQL stable
- Tests de compatibilité réussis
Alternative sécurisée immédiate : Utiliser les variantes Alpine des images officielles pour réduire la surface d'attaque.
Ressources :
27. Glossaire
Advanced Encryption Standard avec Galois/Counter Mode. Chiffrement symétrique avec authentification intégrée (AEAD).
Data Access Object. Pattern pour abstraire l'accÚs à la base de données.
Images Docker sécurisées avec surface d'attaque réduite, SBOM intégré, et mises à jour de sécurité réguliÚres. Disponibles sur dhi.io depuis décembre 2025.
Mécanisme Docker pour stocker des données sensibles de maniÚre sécurisée (RAM uniquement, jamais écrit sur disque).
Data Transfer Object. Objet utilisĂ© pour transfĂ©rer des donnĂ©es entre couches (API â App).
Empreinte unique d'un appareil basée sur ses caractéristiques matérielles et logicielles (SHA-256).
Hash-based Key Derivation Function (RFC 5869). Dérive des clés cryptographiques à partir d'une clé maßtre.
Model-View-ViewModel. Pattern architectural séparant logique métier, état et interface.
Architecture oĂč l'application fonctionne d'abord en mode local, puis synchronise quand rĂ©seau disponible.
BibliothĂšque Android ORM (Object-Relational Mapping) pour SQLite.
API Android pour planifier des tùches asynchrones avec contraintes (réseau, batterie).
28. FAQ Technique
Q : Pourquoi un fingerprint au lieu de login/mot de passe ?
R : Pour réduire la friction et maximiser l'adoption. Créer un compte est une barriÚre psychologique. Le fingerprinting permet :
- â Utilisation immĂ©diate (0 friction)
- â Pas de mot de passe Ă retenir
- â Anonymat par dĂ©faut (conformitĂ© RGPD)
- â Persistance entre rĂ©installations
Q : Que se passe-t-il si l'utilisateur change de téléphone ?
R : Le nouveau téléphone aura un nouveau fingerprint, donc l'historique des anciens incidents n'est pas accessible. L'utilisateur peut recommencer à signaler normalement. Une solution future serait d'implémenter un transfert de compte via email.
Q : Comment gérer les incidents en doublon ?
R : ImplĂ©menter une dĂ©tection de proximitĂ© cĂŽtĂ© API : avant insertion, vĂ©rifier s'il existe dĂ©jĂ un incident du mĂȘme type dans un rayon de 50m créé dans les 7 derniers jours. Si oui, retourner HTTP 409 (Conflict) avec l'ID de l'incident existant.
Q : Est-ce que le chiffrement ralentit l'application ?
R : Impact négligeable. AES-256-GCM prend ~1-2ms pour 500 caractÚres. Le bottleneck est le réseau (50-200ms), pas le chiffrement.
29. Roadmap et Ăvolutions
Phase 1 : MVP
- â Signalement avec GPS + photos
- â Synchronisation offline-first
- â Carte OpenStreetMap
- â Fingerprinting device
- â Chiffrement AES-256-GCM
Phase 2 : Améliorations UX (3 mois)
- Notifications push (Firebase)
- Détection doublons radius-based
- Filtres avancés sur carte
- Mode sombre
- Tutorial onboarding
Phase 3 : Fonctionnalités (6 mois)
- Commentaires sur incidents
- SystĂšme de votes (upvote)
- Catégories personnalisées mairies
- Zones de travaux
- Export PDF rapports
Phase 4 : Intelligence (12 mois)
- ML prédictif (délai résolution)
- Classification auto via IA
- Heatmap incidents
- Analyse tendances
- Recommandations citoyens