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:
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
182
api/src/Core/MonitoredDatabase.php
Normal file
182
api/src/Core/MonitoredDatabase.php
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user