feat: Release version 3.1.4 - Mode terrain et génération PDF

 Nouvelles fonctionnalités:
- Ajout du mode terrain pour utilisation mobile hors connexion
- Génération automatique de reçus PDF avec template personnalisé
- Révision complète du système de cartes avec amélioration des performances

🔧 Améliorations techniques:
- Refactoring du module chat avec architecture simplifiée
- Optimisation du système de sécurité NIST SP 800-63B
- Amélioration de la gestion des secteurs géographiques
- Support UTF-8 étendu pour les noms d'utilisateurs

📱 Application mobile:
- Nouveau mode terrain dans user_field_mode_page
- Interface utilisateur adaptative pour conditions difficiles
- Synchronisation offline améliorée

🗺️ Cartographie:
- Optimisation des performances MapBox
- Meilleure gestion des tuiles hors ligne
- Amélioration de l'affichage des secteurs

📄 Documentation:
- Ajout guide Android (ANDROID-GUIDE.md)
- Documentation sécurité API (API-SECURITY.md)
- Guide module chat (CHAT_MODULE.md)

🐛 Corrections:
- Résolution des erreurs 400 lors de la création d'utilisateurs
- Correction de la validation des noms d'utilisateurs
- Fix des problèmes de synchronisation chat

🤖 Generated with Claude Code (https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-08-19 19:38:03 +02:00
parent c1f23c4345
commit 5ab03751e1
1823 changed files with 272663 additions and 198438 deletions

View File

@@ -1,6 +1,11 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/MonitoredDatabase.php';
require_once __DIR__ . '/../Services/Security/AlertService.php';
use App\Services\Security\AlertService;
class Database {
private static ?PDO $instance = null;
private static array $config;
@@ -23,13 +28,22 @@ class Database {
PDO::ATTR_EMULATE_PREPARES => false,
];
self::$instance = new PDO(
// Utiliser MonitoredDatabase pour le monitoring
self::$instance = new MonitoredDatabase(
$dsn,
self::$config['username'],
self::$config['password'],
$options
);
} catch (PDOException $e) {
// Créer une alerte pour la connexion échouée
AlertService::trigger('DB_CONNECTION', [
'error' => $e->getMessage(),
'host' => self::$config['host'],
'database' => self::$config['name'],
'message' => 'Échec de connexion à la base de données'
], 'CRITICAL');
throw new RuntimeException("Database connection failed: " . $e->getMessage());
}
}

View File

@@ -0,0 +1,182 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/../Services/Security/PerformanceMonitor.php';
require_once __DIR__ . '/../Services/Security/SecurityMonitor.php';
use App\Services\Security\PerformanceMonitor;
use App\Services\Security\SecurityMonitor;
/**
* Classe PDO étendue avec monitoring de sécurité et performance
*/
class MonitoredDatabase extends PDO {
/**
* Préparer une requête avec monitoring
*/
public function prepare($statement, $options = []): PDOStatement|false {
// Démarrer le chronométrage
PerformanceMonitor::startDbQuery($statement);
try {
$stmt = parent::prepare($statement, $options);
// Retourner un statement monitored
if ($stmt !== false) {
return new MonitoredStatement($stmt, $statement);
}
return false;
} catch (PDOException $e) {
// Terminer le chronométrage
PerformanceMonitor::endDbQuery();
// Analyser l'erreur SQL
SecurityMonitor::analyzeSQLError($e, $statement);
// Re-lancer l'exception
throw $e;
}
}
/**
* Exécuter une requête directement avec monitoring
*/
public function exec($statement): int|false {
// Démarrer le chronométrage
PerformanceMonitor::startDbQuery($statement);
try {
$result = parent::exec($statement);
// Terminer le chronométrage
PerformanceMonitor::endDbQuery();
return $result;
} catch (PDOException $e) {
// Terminer le chronométrage
PerformanceMonitor::endDbQuery();
// Analyser l'erreur SQL
SecurityMonitor::analyzeSQLError($e, $statement);
// Re-lancer l'exception
throw $e;
}
}
/**
* Query avec monitoring
*/
public function query($statement, $mode = PDO::ATTR_DEFAULT_FETCH_MODE, ...$args): PDOStatement|false {
// Démarrer le chronométrage
PerformanceMonitor::startDbQuery($statement);
try {
$result = parent::query($statement, $mode, ...$args);
// Terminer le chronométrage
PerformanceMonitor::endDbQuery();
return $result;
} catch (PDOException $e) {
// Terminer le chronométrage
PerformanceMonitor::endDbQuery();
// Analyser l'erreur SQL
SecurityMonitor::analyzeSQLError($e, $statement);
// Re-lancer l'exception
throw $e;
}
}
}
/**
* PDOStatement étendu avec monitoring
*/
class MonitoredStatement extends PDOStatement {
private PDOStatement $stmt;
private string $query;
public function __construct(PDOStatement $stmt, string $query) {
$this->stmt = $stmt;
$this->query = $query;
}
/**
* Exécuter avec monitoring
*/
public function execute($params = null): bool {
// Démarrer le chronométrage (si pas déjà fait dans prepare)
PerformanceMonitor::startDbQuery($this->query);
try {
$result = $this->stmt->execute($params);
// Terminer le chronométrage
PerformanceMonitor::endDbQuery();
return $result;
} catch (PDOException $e) {
// Terminer le chronométrage
PerformanceMonitor::endDbQuery();
// Analyser l'erreur SQL
SecurityMonitor::analyzeSQLError($e, $this->query);
// Re-lancer l'exception
throw $e;
}
}
/**
* Proxy vers le statement original pour toutes les autres méthodes
*/
public function __call($method, $args) {
return call_user_func_array([$this->stmt, $method], $args);
}
public function fetch($mode = PDO::FETCH_DEFAULT, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0): mixed {
return $this->stmt->fetch($mode, $cursorOrientation, $cursorOffset);
}
public function fetchAll($mode = PDO::FETCH_DEFAULT, ...$args): array {
return $this->stmt->fetchAll($mode, ...$args);
}
public function fetchColumn($column = 0): mixed {
return $this->stmt->fetchColumn($column);
}
public function rowCount(): int {
return $this->stmt->rowCount();
}
public function bindParam($param, &$var, $type = PDO::PARAM_STR, $maxLength = null, $driverOptions = null): bool {
return $this->stmt->bindParam($param, $var, $type, $maxLength, $driverOptions);
}
public function bindValue($param, $value, $type = PDO::PARAM_STR): bool {
return $this->stmt->bindValue($param, $value, $type);
}
public function closeCursor(): bool {
return $this->stmt->closeCursor();
}
public function errorCode(): ?string {
return $this->stmt->errorCode();
}
public function errorInfo(): array {
return $this->stmt->errorInfo();
}
}

View File

@@ -34,13 +34,14 @@ class Router {
$this->post('log', ['LogController', 'index']);
// Routes privées utilisateurs
// IMPORTANT: Les routes spécifiques doivent être déclarées AVANT les routes avec paramètres
$this->post('users/check-username', ['UserController', 'checkUsername']); // Déplacé avant les routes avec :id
$this->get('users', ['UserController', 'getUsers']);
$this->get('users/:id', ['UserController', 'getUserById']);
$this->post('users', ['UserController', 'createUser']);
$this->put('users/:id', ['UserController', 'updateUser']);
$this->delete('users/:id', ['UserController', 'deleteUser']);
$this->post('users/:id/reset-password', ['UserController', 'resetPassword']);
$this->post('users/check-username', ['UserController', 'checkUsername']);
$this->post('logout', ['LoginController', 'logout']);
// Routes entités
@@ -69,6 +70,7 @@ class Router {
// Routes passages
$this->get('passages', ['PassageController', 'getPassages']);
$this->get('passages/:id', ['PassageController', 'getPassageById']);
$this->get('passages/:id/receipt', ['PassageController', 'getReceipt']);
$this->get('passages/operation/:operation_id', ['PassageController', 'getPassagesByOperation']);
$this->post('passages', ['PassageController', 'createPassage']);
$this->put('passages/:id', ['PassageController', 'updatePassage']);
@@ -97,6 +99,25 @@ class Router {
$this->post('password/check', ['PasswordController', 'checkStrength']);
$this->post('password/compromised', ['PasswordController', 'checkCompromised']);
$this->get('password/generate', ['PasswordController', 'generate']);
// Routes du module Chat
$this->get('chat/rooms', ['ChatController', 'getRooms']);
$this->post('chat/rooms', ['ChatController', 'createRoom']);
$this->get('chat/rooms/:id/messages', ['ChatController', 'getRoomMessages']);
$this->post('chat/rooms/:id/messages', ['ChatController', 'sendMessage']);
$this->post('chat/rooms/:id/read', ['ChatController', 'markAsRead']);
$this->get('chat/recipients', ['ChatController', 'getRecipients']);
// Routes du module Sécurité (Admin uniquement)
$this->get('admin/metrics', ['SecurityController', 'getMetrics']);
$this->get('admin/alerts', ['SecurityController', 'getAlerts']);
$this->post('admin/alerts/:id/resolve', ['SecurityController', 'resolveAlert']);
$this->get('admin/blocked-ips', ['SecurityController', 'getBlockedIPs']);
$this->post('admin/unblock-ip', ['SecurityController', 'unblockIP']);
$this->post('admin/block-ip', ['SecurityController', 'blockIP']);
$this->get('admin/security-report', ['SecurityController', 'getSecurityReport']);
$this->post('admin/cleanup', ['SecurityController', 'cleanup']);
$this->post('admin/test-alert', ['SecurityController', 'testAlert']);
}
public function handle(): void {