Authentification de l'api (TODO : Lier voiture à un user)
This commit is contained in:
parent
24c432f325
commit
566a7ba794
3 changed files with 319 additions and 36 deletions
|
|
@ -3,6 +3,10 @@
|
||||||
RewriteEngine On
|
RewriteEngine On
|
||||||
RewriteBase /voitureAPI/
|
RewriteBase /voitureAPI/
|
||||||
|
|
||||||
|
# Transmettre l'en-tête HTTP Authorization à PHP sous Apache / WampServer
|
||||||
|
RewriteCond %{HTTP:Authorization} .
|
||||||
|
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
|
||||||
|
|
||||||
# Ne pas réécrire si le fichier ou dossier existe déjà
|
# Ne pas réécrire si le fichier ou dossier existe déjà
|
||||||
RewriteCond %{REQUEST_FILENAME} !-f
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
RewriteCond %{REQUEST_FILENAME} !-d
|
RewriteCond %{REQUEST_FILENAME} !-d
|
||||||
|
|
@ -15,5 +19,5 @@
|
||||||
<IfModule mod_headers.c>
|
<IfModule mod_headers.c>
|
||||||
Header set Access-Control-Allow-Origin "*"
|
Header set Access-Control-Allow-Origin "*"
|
||||||
Header set Access-Control-Allow-Methods "GET, POST, PUT, PATCH, DELETE, OPTIONS"
|
Header set Access-Control-Allow-Methods "GET, POST, PUT, PATCH, DELETE, OPTIONS"
|
||||||
Header set Access-Control-Allow-Headers "Content-Type, Authorization, X-Requested-With"
|
Header set Access-Control-Allow-Headers "Content-Type, Authorization, X-Requested-With, X-API-KEY"
|
||||||
</IfModule>
|
</IfModule>
|
||||||
|
|
|
||||||
321
index.php
321
index.php
|
|
@ -7,7 +7,7 @@ require_once __DIR__ . '/config.php';
|
||||||
header('Content-Type: application/json; charset=UTF-8');
|
header('Content-Type: application/json; charset=UTF-8');
|
||||||
header('Access-Control-Allow-Origin: *');
|
header('Access-Control-Allow-Origin: *');
|
||||||
header('Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS');
|
header('Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS');
|
||||||
header('Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With');
|
header('Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With, X-API-KEY');
|
||||||
|
|
||||||
// Gestion des requêtes de pré-vérification OPTIONS (CORS)
|
// Gestion des requêtes de pré-vérification OPTIONS (CORS)
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||||
|
|
@ -72,7 +72,96 @@ function getPathSegments(): array
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recherche d'une voiture par sa plaque d'immatriculation (exacte ou normalisée)
|
* Récupère le jeton Bearer depuis les en-têtes HTTP ou paramètre d'URL
|
||||||
|
*/
|
||||||
|
function getBearerToken(): ?string
|
||||||
|
{
|
||||||
|
$authHeader = null;
|
||||||
|
|
||||||
|
if (!empty($_SERVER['HTTP_AUTHORIZATION'])) {
|
||||||
|
$authHeader = trim($_SERVER['HTTP_AUTHORIZATION']);
|
||||||
|
} elseif (!empty($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) {
|
||||||
|
$authHeader = trim($_SERVER['REDIRECT_HTTP_AUTHORIZATION']);
|
||||||
|
} elseif (!empty($_SERVER['HTTP_X_API_KEY'])) {
|
||||||
|
return trim($_SERVER['HTTP_X_API_KEY']);
|
||||||
|
} elseif (function_exists('apache_request_headers')) {
|
||||||
|
$headers = apache_request_headers();
|
||||||
|
foreach ($headers as $key => $val) {
|
||||||
|
if (strcasecmp($key, 'Authorization') === 0) {
|
||||||
|
$authHeader = trim($val);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (strcasecmp($key, 'X-API-KEY') === 0) {
|
||||||
|
return trim($val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($authHeader !== null && preg_match('/Bearer\s+(\S+)/i', $authHeader, $matches)) {
|
||||||
|
return $matches[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($_GET['token'])) {
|
||||||
|
return trim((string)$_GET['token']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vérifie l'authentification et les droits de l'utilisateur
|
||||||
|
*/
|
||||||
|
function authenticate(PDO $db, bool $required = true, ?string $requiredRole = null): ?array
|
||||||
|
{
|
||||||
|
$token = getBearerToken();
|
||||||
|
|
||||||
|
if ($token === null) {
|
||||||
|
if ($required) {
|
||||||
|
sendJson(401, [
|
||||||
|
'success' => false,
|
||||||
|
'error' => "Authentification requise. Veuillez fournir un jeton 'Authorization: Bearer <token>' ou 'X-API-KEY'."
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $db->prepare('
|
||||||
|
SELECT id, username, nom, role, api_token, token_expires_at
|
||||||
|
FROM users
|
||||||
|
WHERE api_token = ?
|
||||||
|
LIMIT 1
|
||||||
|
');
|
||||||
|
$stmt->execute([$token]);
|
||||||
|
$user = $stmt->fetch();
|
||||||
|
|
||||||
|
if (!$user) {
|
||||||
|
sendJson(401, [
|
||||||
|
'success' => false,
|
||||||
|
'error' => "Jeton d'authentification invalide. Veuillez vous reconnecter via POST /auth/login."
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vérifier l'expiration du jeton
|
||||||
|
if (!empty($user['token_expires_at']) && strtotime($user['token_expires_at']) < time()) {
|
||||||
|
sendJson(401, [
|
||||||
|
'success' => false,
|
||||||
|
'error' => "Jeton d'authentification expiré. Veuillez vous reconnecter via POST /auth/login."
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vérifier le rôle requis (ex: 'admin')
|
||||||
|
if ($requiredRole !== null && strtolower((string)$user['role']) !== strtolower($requiredRole)) {
|
||||||
|
sendJson(403, [
|
||||||
|
'success' => false,
|
||||||
|
'error' => "Accès refusé. Privilèges insuffisants (rôle '$requiredRole' requis, rôle actuel : '{$user['role']}')."
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $user;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recherche d'une voiture par sa plaque d'immatriculation
|
||||||
*/
|
*/
|
||||||
function findCarByPlate(PDO $db, string $plaque): void
|
function findCarByPlate(PDO $db, string $plaque): void
|
||||||
{
|
{
|
||||||
|
|
@ -112,34 +201,45 @@ try {
|
||||||
if (empty($segments)) {
|
if (empty($segments)) {
|
||||||
sendJson(200, [
|
sendJson(200, [
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'message' => 'API Voitures opérationnelle',
|
'message' => 'API Voitures avec Authentification opérationnelle',
|
||||||
'database' => DB_NAME,
|
'database' => DB_NAME,
|
||||||
|
'comptes_demo' => [
|
||||||
|
'admin' => ['username' => 'admin', 'password' => 'adminpassword', 'token_fixe' => 'admin-token-secret-12345'],
|
||||||
|
'user' => ['username' => 'user', 'password' => 'userpassword', 'token_fixe' => 'user-token-secret-67890']
|
||||||
|
],
|
||||||
'endpoints' => [
|
'endpoints' => [
|
||||||
|
'Authentification' => [
|
||||||
|
'POST /auth/login' => '[PUBLIC] Se connecter et obtenir un jeton (username, password)',
|
||||||
|
'POST /auth/register' => '[PUBLIC] Créer un compte utilisateur (username, password, nom)',
|
||||||
|
'GET /auth/me' => '[AUTHENTIFIÉ] Obtenir le profil de l\'utilisateur connecté',
|
||||||
|
'POST /auth/logout' => '[AUTHENTIFIÉ] Révoquer le jeton de session'
|
||||||
|
],
|
||||||
'Voitures' => [
|
'Voitures' => [
|
||||||
'GET /cars' => 'Liste toutes les voitures (filtrable par ?immatriculation=...)',
|
'GET /cars' => '[PUBLIC] Liste des voitures (filtrable par ?immatriculation=...)',
|
||||||
'GET /cars/{id}' => 'Détails d\'une voiture par ID',
|
'GET /cars/{id}' => '[PUBLIC] Détails d\'une voiture',
|
||||||
'GET /cars/immatriculation/{plaque}' => 'Identifier une voiture par sa plaque d\'immatriculation',
|
'GET /cars/immatriculation/{plaque}' => '[PUBLIC] Identifier une voiture par sa plaque',
|
||||||
'GET /cars/{id}/etat' => 'État complet (voiture, dernier km, entretiens, notes)',
|
'GET /cars/{id}/etat' => '[AUTHENTIFIÉ] État complet (voiture, dernier km, entretiens, notes)',
|
||||||
'POST /cars' => 'Créer une voiture (marque, modele, annee, dateAchat, VIN, immatriculation)',
|
'POST /cars' => '[AUTHENTIFIÉ] Créer une voiture (marque, modele, annee, dateAchat, VIN, immatriculation)',
|
||||||
'PUT /cars/{id}' => 'Modifier les paramètres d\'une voiture (y compris immatriculation)',
|
'PUT /cars/{id}' => '[AUTHENTIFIÉ] Modifier les paramètres d\'une voiture',
|
||||||
'DELETE /cars/{id}' => 'Supprimer une voiture (et ses données associées)'
|
'DELETE /cars/{id}' => '[ADMIN] Supprimer une voiture et toutes ses données associées'
|
||||||
],
|
],
|
||||||
'Kilométrage' => [
|
'Kilométrage' => [
|
||||||
'GET /cars/{id}/kilometrage' => 'Historique des relevés kilométriques',
|
'GET /cars/{id}/kilometrage' => '[PUBLIC] Historique des relevés kilométriques',
|
||||||
'POST /cars/{id}/kilometrage' => 'Ajouter un relevé kilométrique (valeur, date_releve)',
|
'POST /cars/{id}/kilometrage' => '[AUTHENTIFIÉ] Ajouter un relevé kilométrique (valeur, date_releve)',
|
||||||
'DELETE /kilometrage/{id}' => 'Supprimer un relevé kilométrique'
|
'DELETE /kilometrage/{id}' => '[ADMIN] Supprimer un relevé kilométrique'
|
||||||
],
|
],
|
||||||
'Entretiens' => [
|
'Entretiens' => [
|
||||||
'GET /cars/{id}/maintenance' => 'Historique des entretiens d\'une voiture',
|
'GET /cars/{id}/maintenance' => '[PUBLIC] Historique des entretiens d\'une voiture',
|
||||||
'POST /cars/{id}/maintenance' => 'Ajouter un entretien (type_entretien, date_evenement, kilometrage, description, prix)',
|
'GET /maintenance/{id}' => '[PUBLIC] Détails d\'un entretien',
|
||||||
'PUT /maintenance/{id}' => 'Modifier un entretien',
|
'POST /cars/{id}/maintenance' => '[AUTHENTIFIÉ] Ajouter un entretien (type_entretien, date_evenement, kilometrage, ...)',
|
||||||
'DELETE /maintenance/{id}' => 'Supprimer un entretien'
|
'PUT /maintenance/{id}' => '[AUTHENTIFIÉ] Modifier un entretien',
|
||||||
|
'DELETE /maintenance/{id}' => '[ADMIN] Supprimer un entretien'
|
||||||
],
|
],
|
||||||
'Notes' => [
|
'Notes (Contenu interne)' => [
|
||||||
'GET /cars/{id}/notes' => 'Notes associées à une voiture',
|
'GET /cars/{id}/notes' => '[AUTHENTIFIÉ] Consulter les notes d\'une voiture',
|
||||||
'POST /cars/{id}/notes' => 'Ajouter une note (titre, contenu)',
|
'POST /cars/{id}/notes' => '[AUTHENTIFIÉ] Ajouter une note (titre, contenu)',
|
||||||
'PUT /notes/{id}' => 'Modifier une note',
|
'PUT /notes/{id}' => '[AUTHENTIFIÉ] Modifier une note',
|
||||||
'DELETE /notes/{id}' => 'Supprimer une note'
|
'DELETE /notes/{id}' => '[AUTHENTIFIÉ] Supprimer une note'
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
|
|
@ -147,6 +247,117 @@ try {
|
||||||
|
|
||||||
$resource = strtolower($segments[0]);
|
$resource = strtolower($segments[0]);
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// RESSOURCE: AUTHENTIFICATION (/auth ...)
|
||||||
|
// =========================================================================
|
||||||
|
if ($resource === 'auth') {
|
||||||
|
$action = strtolower($segments[1] ?? '');
|
||||||
|
|
||||||
|
// POST /auth/login
|
||||||
|
if ($action === 'login' && $method === 'POST') {
|
||||||
|
$body = getBody();
|
||||||
|
if (empty($body['username']) || empty($body['password'])) {
|
||||||
|
sendJson(400, ['success' => false, 'error' => 'Champs username et password obligatoires']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $db->prepare('SELECT * FROM users WHERE username = ? LIMIT 1');
|
||||||
|
$stmt->execute([trim((string)$body['username'])]);
|
||||||
|
$user = $stmt->fetch();
|
||||||
|
|
||||||
|
if (!$user || !password_verify((string)$body['password'], $user['password'])) {
|
||||||
|
sendJson(401, ['success' => false, 'error' => 'Identifiants invalides (nom d\'utilisateur ou mot de passe incorrect)']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Génération d'un nouveau jeton valide 7 jours
|
||||||
|
$token = bin2hex(random_bytes(32));
|
||||||
|
$expiresAt = date('Y-m-d H:i:s', time() + (7 * 86400));
|
||||||
|
|
||||||
|
$stmtUpdate = $db->prepare('UPDATE users SET api_token = ?, token_expires_at = ? WHERE id = ?');
|
||||||
|
$stmtUpdate->execute([$token, $expiresAt, $user['id']]);
|
||||||
|
|
||||||
|
sendJson(200, [
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Connexion réussie',
|
||||||
|
'data' => [
|
||||||
|
'user' => [
|
||||||
|
'id' => (int)$user['id'],
|
||||||
|
'username' => $user['username'],
|
||||||
|
'nom' => $user['nom'],
|
||||||
|
'role' => $user['role']
|
||||||
|
],
|
||||||
|
'token' => $token,
|
||||||
|
'expires_at' => $expiresAt
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST /auth/register
|
||||||
|
if ($action === 'register' && $method === 'POST') {
|
||||||
|
$body = getBody();
|
||||||
|
if (empty($body['username']) || empty($body['password'])) {
|
||||||
|
sendJson(400, ['success' => false, 'error' => 'Champs username et password obligatoires']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$username = trim((string)$body['username']);
|
||||||
|
$nom = !empty($body['nom']) ? trim((string)$body['nom']) : null;
|
||||||
|
$password = password_hash((string)$body['password'], PASSWORD_DEFAULT);
|
||||||
|
|
||||||
|
// Vérifier existence
|
||||||
|
$stmtCheck = $db->prepare('SELECT id FROM users WHERE username = ?');
|
||||||
|
$stmtCheck->execute([$username]);
|
||||||
|
if ($stmtCheck->fetch()) {
|
||||||
|
sendJson(409, ['success' => false, 'error' => "Le nom d'utilisateur '$username' est déjà utilisé"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$token = bin2hex(random_bytes(32));
|
||||||
|
$expiresAt = date('Y-m-d H:i:s', time() + (7 * 86400));
|
||||||
|
|
||||||
|
$stmt = $db->prepare('INSERT INTO users (username, password, nom, role, api_token, token_expires_at) VALUES (?, ?, ?, ?, ?, ?)');
|
||||||
|
$stmt->execute([$username, $password, $nom, 'user', $token, $expiresAt]);
|
||||||
|
$newId = (int)$db->lastInsertId();
|
||||||
|
|
||||||
|
sendJson(201, [
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Compte créé avec succès',
|
||||||
|
'data' => [
|
||||||
|
'user' => [
|
||||||
|
'id' => $newId,
|
||||||
|
'username' => $username,
|
||||||
|
'nom' => $nom,
|
||||||
|
'role' => 'user'
|
||||||
|
],
|
||||||
|
'token' => $token,
|
||||||
|
'expires_at' => $expiresAt
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET /auth/me (Profil de l'utilisateur connecté)
|
||||||
|
if ($action === 'me' && $method === 'GET') {
|
||||||
|
$user = authenticate($db, true);
|
||||||
|
sendJson(200, [
|
||||||
|
'success' => true,
|
||||||
|
'data' => [
|
||||||
|
'id' => (int)$user['id'],
|
||||||
|
'username' => $user['username'],
|
||||||
|
'nom' => $user['nom'],
|
||||||
|
'role' => $user['role'],
|
||||||
|
'token_expires_at' => $user['token_expires_at']
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST /auth/logout (Révocation du jeton)
|
||||||
|
if ($action === 'logout' && $method === 'POST') {
|
||||||
|
$user = authenticate($db, true);
|
||||||
|
$stmt = $db->prepare('UPDATE users SET api_token = NULL, token_expires_at = NULL WHERE id = ?');
|
||||||
|
$stmt->execute([$user['id']]);
|
||||||
|
sendJson(200, ['success' => true, 'message' => 'Déconnexion réussie, le jeton a été révoqué']);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendJson(404, ['success' => false, 'error' => "Action d'authentification '$action' non reconnue"]);
|
||||||
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// ENDPOINT DIRECT: /immatriculation/{plaque}
|
// ENDPOINT DIRECT: /immatriculation/{plaque}
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
@ -162,7 +373,7 @@ try {
|
||||||
// RESSOURCE: CARS (/cars ...)
|
// RESSOURCE: CARS (/cars ...)
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
if ($resource === 'cars' || $resource === 'voitures') {
|
if ($resource === 'cars' || $resource === 'voitures') {
|
||||||
// NOUVEAU ENDPOINT: GET /cars/immatriculation/{plaque}
|
// GET /cars/immatriculation/{plaque} (Public)
|
||||||
if (isset($segments[1]) && strtolower($segments[1]) === 'immatriculation' && $method === 'GET') {
|
if (isset($segments[1]) && strtolower($segments[1]) === 'immatriculation' && $method === 'GET') {
|
||||||
$plaque = $segments[2] ?? null;
|
$plaque = $segments[2] ?? null;
|
||||||
if (empty($plaque)) {
|
if (empty($plaque)) {
|
||||||
|
|
@ -174,8 +385,10 @@ try {
|
||||||
$carId = isset($segments[1]) && is_numeric($segments[1]) ? (int)$segments[1] : null;
|
$carId = isset($segments[1]) && is_numeric($segments[1]) ? (int)$segments[1] : null;
|
||||||
$subResource = $segments[2] ?? null;
|
$subResource = $segments[2] ?? null;
|
||||||
|
|
||||||
// 1. GET /cars/{id}/etat (État complet via la procédure sp_getEtatVoiture)
|
// 1. GET /cars/{id}/etat [CONTENU RESTREINT - AUTHENTIFICATION REQUISE]
|
||||||
if ($carId && $subResource === 'etat' && $method === 'GET') {
|
if ($carId && $subResource === 'etat' && $method === 'GET') {
|
||||||
|
$currentUser = authenticate($db, true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$stmt = $db->prepare('CALL sp_getEtatVoiture(:id)');
|
$stmt = $db->prepare('CALL sp_getEtatVoiture(:id)');
|
||||||
$stmt->execute([':id' => $carId]);
|
$stmt->execute([':id' => $carId]);
|
||||||
|
|
@ -198,6 +411,7 @@ try {
|
||||||
|
|
||||||
sendJson(200, [
|
sendJson(200, [
|
||||||
'success' => true,
|
'success' => true,
|
||||||
|
'consulted_by' => $currentUser['username'],
|
||||||
'data' => [
|
'data' => [
|
||||||
'voiture' => $car,
|
'voiture' => $car,
|
||||||
'maintenances' => $maintenances,
|
'maintenances' => $maintenances,
|
||||||
|
|
@ -224,6 +438,7 @@ try {
|
||||||
|
|
||||||
sendJson(200, [
|
sendJson(200, [
|
||||||
'success' => true,
|
'success' => true,
|
||||||
|
'consulted_by' => $currentUser['username'],
|
||||||
'data' => [
|
'data' => [
|
||||||
'voiture' => $car,
|
'voiture' => $car,
|
||||||
'maintenances' => $stmtMaint->fetchAll(),
|
'maintenances' => $stmtMaint->fetchAll(),
|
||||||
|
|
@ -235,6 +450,7 @@ try {
|
||||||
|
|
||||||
// 2. /cars/{id}/kilometrage
|
// 2. /cars/{id}/kilometrage
|
||||||
if ($carId && $subResource === 'kilometrage') {
|
if ($carId && $subResource === 'kilometrage') {
|
||||||
|
// GET: Public
|
||||||
if ($method === 'GET') {
|
if ($method === 'GET') {
|
||||||
$limit = isset($_GET['limit']) ? (int)$_GET['limit'] : 50;
|
$limit = isset($_GET['limit']) ? (int)$_GET['limit'] : 50;
|
||||||
try {
|
try {
|
||||||
|
|
@ -252,7 +468,10 @@ try {
|
||||||
sendJson(200, ['success' => true, 'data' => $data]);
|
sendJson(200, ['success' => true, 'data' => $data]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// POST: [ACTION PROTÉGÉE - AUTHENTIFICATION REQUISE]
|
||||||
if ($method === 'POST') {
|
if ($method === 'POST') {
|
||||||
|
authenticate($db, true);
|
||||||
|
|
||||||
$body = getBody();
|
$body = getBody();
|
||||||
if (!isset($body['valeur'])) {
|
if (!isset($body['valeur'])) {
|
||||||
sendJson(400, ['success' => false, 'error' => 'Le champ valeur (kilométrage) est obligatoire']);
|
sendJson(400, ['success' => false, 'error' => 'Le champ valeur (kilométrage) est obligatoire']);
|
||||||
|
|
@ -281,13 +500,17 @@ try {
|
||||||
|
|
||||||
// 3. /cars/{id}/maintenance
|
// 3. /cars/{id}/maintenance
|
||||||
if ($carId && ($subResource === 'maintenance' || $subResource === 'entretiens')) {
|
if ($carId && ($subResource === 'maintenance' || $subResource === 'entretiens')) {
|
||||||
|
// GET: Public
|
||||||
if ($method === 'GET') {
|
if ($method === 'GET') {
|
||||||
$stmt = $db->prepare('SELECT * FROM maintenance_logs WHERE car_id = ? ORDER BY date_evenement DESC');
|
$stmt = $db->prepare('SELECT * FROM maintenance_logs WHERE car_id = ? ORDER BY date_evenement DESC');
|
||||||
$stmt->execute([$carId]);
|
$stmt->execute([$carId]);
|
||||||
sendJson(200, ['success' => true, 'data' => $stmt->fetchAll()]);
|
sendJson(200, ['success' => true, 'data' => $stmt->fetchAll()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// POST: [ACTION PROTÉGÉE - AUTHENTIFICATION REQUISE]
|
||||||
if ($method === 'POST') {
|
if ($method === 'POST') {
|
||||||
|
authenticate($db, true);
|
||||||
|
|
||||||
$body = getBody();
|
$body = getBody();
|
||||||
if (empty($body['type_entretien']) || empty($body['date_evenement']) || !isset($body['kilometrage'])) {
|
if (empty($body['type_entretien']) || empty($body['date_evenement']) || !isset($body['kilometrage'])) {
|
||||||
sendJson(400, [
|
sendJson(400, [
|
||||||
|
|
@ -323,15 +546,21 @@ try {
|
||||||
sendJson(405, ['success' => false, 'error' => 'Méthode non autorisée pour cette sous-ressource']);
|
sendJson(405, ['success' => false, 'error' => 'Méthode non autorisée pour cette sous-ressource']);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. /cars/{id}/notes
|
// 4. /cars/{id}/notes [CONTENU RESTREINT - AUTHENTIFICATION REQUISE]
|
||||||
if ($carId && $subResource === 'notes') {
|
if ($carId && $subResource === 'notes') {
|
||||||
|
// GET: Protégé (Notes de suivi confidentielles)
|
||||||
if ($method === 'GET') {
|
if ($method === 'GET') {
|
||||||
|
authenticate($db, true);
|
||||||
|
|
||||||
$stmt = $db->prepare('SELECT * FROM notes WHERE car_id = ? ORDER BY date_creation DESC');
|
$stmt = $db->prepare('SELECT * FROM notes WHERE car_id = ? ORDER BY date_creation DESC');
|
||||||
$stmt->execute([$carId]);
|
$stmt->execute([$carId]);
|
||||||
sendJson(200, ['success' => true, 'data' => $stmt->fetchAll()]);
|
sendJson(200, ['success' => true, 'data' => $stmt->fetchAll()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// POST: [ACTION PROTÉGÉE - AUTHENTIFICATION REQUISE]
|
||||||
if ($method === 'POST') {
|
if ($method === 'POST') {
|
||||||
|
authenticate($db, true);
|
||||||
|
|
||||||
$body = getBody();
|
$body = getBody();
|
||||||
if (empty($body['contenu'])) {
|
if (empty($body['contenu'])) {
|
||||||
sendJson(400, ['success' => false, 'error' => 'Le champ contenu est obligatoire']);
|
sendJson(400, ['success' => false, 'error' => 'Le champ contenu est obligatoire']);
|
||||||
|
|
@ -359,7 +588,7 @@ try {
|
||||||
|
|
||||||
// --- ACTIONS PRINCIPALES SUR LES VOITURES ---
|
// --- ACTIONS PRINCIPALES SUR LES VOITURES ---
|
||||||
|
|
||||||
// GET /cars : Liste toutes les voitures (ou filtre ?immatriculation=...)
|
// GET /cars : Liste toutes les voitures (Public)
|
||||||
if ($carId === null && $subResource === null && $method === 'GET') {
|
if ($carId === null && $subResource === null && $method === 'GET') {
|
||||||
if (!empty($_GET['immatriculation'])) {
|
if (!empty($_GET['immatriculation'])) {
|
||||||
findCarByPlate($db, (string)$_GET['immatriculation']);
|
findCarByPlate($db, (string)$_GET['immatriculation']);
|
||||||
|
|
@ -373,7 +602,7 @@ try {
|
||||||
sendJson(200, ['success' => true, 'data' => $stmt->fetchAll()]);
|
sendJson(200, ['success' => true, 'data' => $stmt->fetchAll()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET /cars/{id} : Consulter une voiture
|
// GET /cars/{id} : Consulter une voiture (Public)
|
||||||
if ($carId !== null && $subResource === null && $method === 'GET') {
|
if ($carId !== null && $subResource === null && $method === 'GET') {
|
||||||
$stmt = $db->prepare('
|
$stmt = $db->prepare('
|
||||||
SELECT c.*,
|
SELECT c.*,
|
||||||
|
|
@ -389,10 +618,11 @@ try {
|
||||||
sendJson(200, ['success' => true, 'data' => $car]);
|
sendJson(200, ['success' => true, 'data' => $car]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST /cars : Créer une voiture
|
// POST /cars : Créer une voiture [ACTION PROTÉGÉE - AUTHENTIFICATION REQUISE]
|
||||||
if ($carId === null && $method === 'POST') {
|
if ($carId === null && $method === 'POST') {
|
||||||
$body = getBody();
|
authenticate($db, true);
|
||||||
|
|
||||||
|
$body = getBody();
|
||||||
$required = ['marque', 'modele', 'annee', 'dateAchat'];
|
$required = ['marque', 'modele', 'annee', 'dateAchat'];
|
||||||
foreach ($required as $field) {
|
foreach ($required as $field) {
|
||||||
if (empty($body[$field])) {
|
if (empty($body[$field])) {
|
||||||
|
|
@ -407,7 +637,7 @@ try {
|
||||||
$vin = !empty($body['VIN']) ? trim((string)$body['VIN']) : null;
|
$vin = !empty($body['VIN']) ? trim((string)$body['VIN']) : null;
|
||||||
$immatriculation = !empty($body['immatriculation']) ? strtoupper(trim((string)$body['immatriculation'])) : null;
|
$immatriculation = !empty($body['immatriculation']) ? strtoupper(trim((string)$body['immatriculation'])) : null;
|
||||||
|
|
||||||
// Vérifier unicité de l'immatriculation si renseignée
|
// Vérifier unicité de l'immatriculation
|
||||||
if ($immatriculation !== null) {
|
if ($immatriculation !== null) {
|
||||||
$normImmat = str_replace(['-', ' ', '.', '_'], '', $immatriculation);
|
$normImmat = str_replace(['-', ' ', '.', '_'], '', $immatriculation);
|
||||||
$stmtCheck = $db->prepare('
|
$stmtCheck = $db->prepare('
|
||||||
|
|
@ -444,7 +674,6 @@ try {
|
||||||
$newId = (int)$db->lastInsertId();
|
$newId = (int)$db->lastInsertId();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Kilométrage initial optionnel
|
|
||||||
if (!empty($body['kilometrage_initial'])) {
|
if (!empty($body['kilometrage_initial'])) {
|
||||||
$kmStmt = $db->prepare('INSERT INTO kilometrage (car_id, date_releve, valeur) VALUES (?, ?, ?)');
|
$kmStmt = $db->prepare('INSERT INTO kilometrage (car_id, date_releve, valeur) VALUES (?, ?, ?)');
|
||||||
$kmStmt->execute([$newId, $dateAchat, (int)$body['kilometrage_initial']]);
|
$kmStmt->execute([$newId, $dateAchat, (int)$body['kilometrage_initial']]);
|
||||||
|
|
@ -465,8 +694,10 @@ try {
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// PUT / PATCH /cars/{id} : Mettre à jour les paramètres d'une voiture
|
// PUT / PATCH /cars/{id} : Mettre à jour les paramètres [ACTION PROTÉGÉE - AUTHENTIFICATION REQUISE]
|
||||||
if ($carId !== null && $subResource === null && ($method === 'PUT' || $method === 'PATCH')) {
|
if ($carId !== null && $subResource === null && ($method === 'PUT' || $method === 'PATCH')) {
|
||||||
|
authenticate($db, true);
|
||||||
|
|
||||||
$check = $db->prepare('SELECT id FROM cars WHERE id = ?');
|
$check = $db->prepare('SELECT id FROM cars WHERE id = ?');
|
||||||
$check->execute([$carId]);
|
$check->execute([$carId]);
|
||||||
if (!$check->fetch()) {
|
if (!$check->fetch()) {
|
||||||
|
|
@ -478,7 +709,6 @@ try {
|
||||||
$updates = [];
|
$updates = [];
|
||||||
$params = [];
|
$params = [];
|
||||||
|
|
||||||
// Si immatriculation modifiée, vérifier unicité
|
|
||||||
if (!empty($body['immatriculation'])) {
|
if (!empty($body['immatriculation'])) {
|
||||||
$immat = strtoupper(trim((string)$body['immatriculation']));
|
$immat = strtoupper(trim((string)$body['immatriculation']));
|
||||||
$body['immatriculation'] = $immat;
|
$body['immatriculation'] = $immat;
|
||||||
|
|
@ -529,8 +759,10 @@ try {
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// DELETE /cars/{id} : Supprimer une voiture
|
// DELETE /cars/{id} : Supprimer une voiture [ACTION PROTÉGÉE - RÔLE ADMIN REQUIS]
|
||||||
if ($carId !== null && $subResource === null && $method === 'DELETE') {
|
if ($carId !== null && $subResource === null && $method === 'DELETE') {
|
||||||
|
authenticate($db, true, 'admin');
|
||||||
|
|
||||||
$check = $db->prepare('SELECT id FROM cars WHERE id = ?');
|
$check = $db->prepare('SELECT id FROM cars WHERE id = ?');
|
||||||
$check->execute([$carId]);
|
$check->execute([$carId]);
|
||||||
if (!$check->fetch()) {
|
if (!$check->fetch()) {
|
||||||
|
|
@ -561,7 +793,10 @@ try {
|
||||||
if ($resource === 'kilometrage') {
|
if ($resource === 'kilometrage') {
|
||||||
$kmId = isset($segments[1]) && is_numeric($segments[1]) ? (int)$segments[1] : null;
|
$kmId = isset($segments[1]) && is_numeric($segments[1]) ? (int)$segments[1] : null;
|
||||||
|
|
||||||
|
// DELETE: [ACTION PROTÉGÉE - RÔLE ADMIN REQUIS]
|
||||||
if ($kmId && $method === 'DELETE') {
|
if ($kmId && $method === 'DELETE') {
|
||||||
|
authenticate($db, true, 'admin');
|
||||||
|
|
||||||
$stmt = $db->prepare('DELETE FROM kilometrage WHERE id = ?');
|
$stmt = $db->prepare('DELETE FROM kilometrage WHERE id = ?');
|
||||||
$stmt->execute([$kmId]);
|
$stmt->execute([$kmId]);
|
||||||
if ($stmt->rowCount() === 0) {
|
if ($stmt->rowCount() === 0) {
|
||||||
|
|
@ -583,6 +818,7 @@ try {
|
||||||
sendJson(400, ['success' => false, 'error' => 'Identifiant d\'entretien requis']);
|
sendJson(400, ['success' => false, 'error' => 'Identifiant d\'entretien requis']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GET: Public
|
||||||
if ($method === 'GET') {
|
if ($method === 'GET') {
|
||||||
$stmt = $db->prepare('SELECT * FROM maintenance_logs WHERE id = ?');
|
$stmt = $db->prepare('SELECT * FROM maintenance_logs WHERE id = ?');
|
||||||
$stmt->execute([$maintId]);
|
$stmt->execute([$maintId]);
|
||||||
|
|
@ -593,7 +829,10 @@ try {
|
||||||
sendJson(200, ['success' => true, 'data' => $item]);
|
sendJson(200, ['success' => true, 'data' => $item]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PUT/PATCH: [ACTION PROTÉGÉE - AUTHENTIFICATION REQUISE]
|
||||||
if ($method === 'PUT' || $method === 'PATCH') {
|
if ($method === 'PUT' || $method === 'PATCH') {
|
||||||
|
authenticate($db, true);
|
||||||
|
|
||||||
$body = getBody();
|
$body = getBody();
|
||||||
$allowed = ['type_entretien', 'date_evenement', 'kilometrage', 'description', 'prix'];
|
$allowed = ['type_entretien', 'date_evenement', 'kilometrage', 'description', 'prix'];
|
||||||
$updates = [];
|
$updates = [];
|
||||||
|
|
@ -623,7 +862,10 @@ try {
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DELETE: [ACTION PROTÉGÉE - RÔLE ADMIN REQUIS]
|
||||||
if ($method === 'DELETE') {
|
if ($method === 'DELETE') {
|
||||||
|
authenticate($db, true, 'admin');
|
||||||
|
|
||||||
$stmt = $db->prepare('DELETE FROM maintenance_logs WHERE id = ?');
|
$stmt = $db->prepare('DELETE FROM maintenance_logs WHERE id = ?');
|
||||||
$stmt->execute([$maintId]);
|
$stmt->execute([$maintId]);
|
||||||
if ($stmt->rowCount() === 0) {
|
if ($stmt->rowCount() === 0) {
|
||||||
|
|
@ -636,7 +878,7 @@ try {
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// RESSOURCE: NOTES DIRECT (/notes/{id})
|
// RESSOURCE: NOTES DIRECT (/notes/{id}) [CONTENU RESTREINT - AUTHENTIFICATION REQUISE]
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
if ($resource === 'notes') {
|
if ($resource === 'notes') {
|
||||||
$noteId = isset($segments[1]) && is_numeric($segments[1]) ? (int)$segments[1] : null;
|
$noteId = isset($segments[1]) && is_numeric($segments[1]) ? (int)$segments[1] : null;
|
||||||
|
|
@ -645,7 +887,10 @@ try {
|
||||||
sendJson(400, ['success' => false, 'error' => 'Identifiant de note requis']);
|
sendJson(400, ['success' => false, 'error' => 'Identifiant de note requis']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GET: Authentifié
|
||||||
if ($method === 'GET') {
|
if ($method === 'GET') {
|
||||||
|
authenticate($db, true);
|
||||||
|
|
||||||
$stmt = $db->prepare('SELECT * FROM notes WHERE id = ?');
|
$stmt = $db->prepare('SELECT * FROM notes WHERE id = ?');
|
||||||
$stmt->execute([$noteId]);
|
$stmt->execute([$noteId]);
|
||||||
$item = $stmt->fetch();
|
$item = $stmt->fetch();
|
||||||
|
|
@ -655,7 +900,10 @@ try {
|
||||||
sendJson(200, ['success' => true, 'data' => $item]);
|
sendJson(200, ['success' => true, 'data' => $item]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PUT/PATCH: Authentifié
|
||||||
if ($method === 'PUT' || $method === 'PATCH') {
|
if ($method === 'PUT' || $method === 'PATCH') {
|
||||||
|
authenticate($db, true);
|
||||||
|
|
||||||
$body = getBody();
|
$body = getBody();
|
||||||
$allowed = ['titre', 'contenu'];
|
$allowed = ['titre', 'contenu'];
|
||||||
$updates = [];
|
$updates = [];
|
||||||
|
|
@ -685,7 +933,10 @@ try {
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DELETE: Authentifié
|
||||||
if ($method === 'DELETE') {
|
if ($method === 'DELETE') {
|
||||||
|
authenticate($db, true);
|
||||||
|
|
||||||
$stmt = $db->prepare('DELETE FROM notes WHERE id = ?');
|
$stmt = $db->prepare('DELETE FROM notes WHERE id = ?');
|
||||||
$stmt->execute([$noteId]);
|
$stmt->execute([$noteId]);
|
||||||
if ($stmt->rowCount() === 0) {
|
if ($stmt->rowCount() === 0) {
|
||||||
|
|
|
||||||
|
|
@ -183,6 +183,34 @@ ALTER TABLE `maintenance_logs`
|
||||||
--
|
--
|
||||||
ALTER TABLE `notes`
|
ALTER TABLE `notes`
|
||||||
ADD CONSTRAINT `fk_notes_car_id` FOREIGN KEY (`car_id`) REFERENCES `cars` (`id`) ON DELETE CASCADE;
|
ADD CONSTRAINT `fk_notes_car_id` FOREIGN KEY (`car_id`) REFERENCES `cars` (`id`) ON DELETE CASCADE;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Structure de la table `users`
|
||||||
|
--
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `users`;
|
||||||
|
CREATE TABLE IF NOT EXISTS `users` (
|
||||||
|
`id` int NOT NULL AUTO_INCREMENT,
|
||||||
|
`username` varchar(50) NOT NULL,
|
||||||
|
`password` varchar(255) NOT NULL,
|
||||||
|
`nom` varchar(100) DEFAULT NULL,
|
||||||
|
`role` varchar(20) NOT NULL DEFAULT 'user',
|
||||||
|
`api_token` varchar(64) DEFAULT NULL,
|
||||||
|
`token_expires_at` datetime DEFAULT NULL,
|
||||||
|
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `idx_username` (`username`),
|
||||||
|
UNIQUE KEY `idx_api_token` (`api_token`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Déchargement des données de la table `users`
|
||||||
|
--
|
||||||
|
|
||||||
|
INSERT INTO `users` (`id`, `username`, `password`, `nom`, `role`, `api_token`) VALUES
|
||||||
|
(1, 'admin', '$2y$10$v0vjF8yL15gA1kXfA9q3hOb00Q0pM0N8dMh2pA7uT1VlqjXlZ9rKq', 'Administrateur', 'admin', 'admin-token-secret-12345'),
|
||||||
|
(2, 'user', '$2y$10$Q4M1s1Fk6vjF8yL15gA1kXfA9q3hOb00Q0pM0N8dMh2pA7uT1Vlqy', 'Utilisateur Standard', 'user', 'user-token-secret-67890');
|
||||||
|
|
||||||
COMMIT;
|
COMMIT;
|
||||||
|
|
||||||
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue