diff --git a/index.php b/index.php index a9e7f14..ab95fa7 100644 --- a/index.php +++ b/index.php @@ -141,7 +141,6 @@ function authenticate(PDO $db, bool $required = true, ?string $requiredRole = nu ]); } - // Vérifier l'expiration du jeton if (!empty($user['token_expires_at']) && strtotime($user['token_expires_at']) < time()) { sendJson(401, [ 'success' => false, @@ -149,7 +148,6 @@ function authenticate(PDO $db, bool $required = true, ?string $requiredRole = nu ]); } - // Vérifier le rôle requis (ex: 'admin') if ($requiredRole !== null && strtolower((string)$user['role']) !== strtolower($requiredRole)) { sendJson(403, [ 'success' => false, @@ -160,6 +158,57 @@ function authenticate(PDO $db, bool $required = true, ?string $requiredRole = nu return $user; } +/** + * Formate une ligne de voiture avec son propriétaire + */ +function formatCarRow(array $row): array +{ + $row['id'] = (int)$row['id']; + $row['annee'] = (int)$row['annee']; + $row['dernier_km'] = isset($row['dernier_km']) ? (int)$row['dernier_km'] : null; + + $row['proprietaire'] = null; + if (!empty($row['user_id'])) { + $row['proprietaire'] = [ + 'id' => (int)$row['user_id'], + 'username' => $row['proprietaire_username'] ?? null, + 'nom' => $row['proprietaire_nom'] ?? null, + ]; + } + unset($row['proprietaire_username'], $row['proprietaire_nom']); + return $row; +} + +/** + * Récupère une voiture par son ID avec les informations du propriétaire + */ +function getCarById(PDO $db, int $carId): ?array +{ + $stmt = $db->prepare(' + SELECT c.*, + u.username AS proprietaire_username, + u.nom AS proprietaire_nom, + (SELECT valeur FROM kilometrage WHERE car_id = c.id ORDER BY date_releve DESC, id DESC LIMIT 1) AS dernier_km + FROM cars c + LEFT JOIN users u ON c.user_id = u.id + WHERE c.id = ? + '); + $stmt->execute([$carId]); + $car = $stmt->fetch(); + return $car ? formatCarRow($car) : null; +} + +/** + * Vérifie si l'utilisateur est le propriétaire de la voiture ou un administrateur + */ +function canModifyCar(array $car, array $currentUser): bool +{ + if (strtolower((string)$currentUser['role']) === 'admin') { + return true; + } + return !empty($car['user_id']) && (int)$car['user_id'] === (int)$currentUser['id']; +} + /** * Recherche d'une voiture par sa plaque d'immatriculation */ @@ -170,8 +219,11 @@ function findCarByPlate(PDO $db, string $plaque): void $stmt = $db->prepare(' SELECT c.*, + u.username AS proprietaire_username, + u.nom AS proprietaire_nom, (SELECT valeur FROM kilometrage WHERE car_id = c.id ORDER BY date_releve DESC, id DESC LIMIT 1) AS dernier_km FROM cars c + LEFT JOIN users u ON c.user_id = u.id WHERE UPPER(c.immatriculation) = :raw OR REPLACE(REPLACE(REPLACE(REPLACE(UPPER(c.immatriculation), "-", ""), " ", ""), ".", ""), "_", "") = :norm LIMIT 1 @@ -188,7 +240,7 @@ function findCarByPlate(PDO $db, string $plaque): void sendJson(200, [ 'success' => true, - 'data' => $car + 'data' => formatCarRow($car) ]); } @@ -201,45 +253,51 @@ try { if (empty($segments)) { sendJson(200, [ 'success' => true, - 'message' => 'API Voitures avec Authentification opérationnelle', + 'message' => 'API Voitures avec Gestion des Propriétaires & Authentification opérationnelle', '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'] + 'admin' => ['username' => 'admin', 'password' => 'adminpassword', 'role' => 'admin', 'token_fixe' => 'admin-token-secret-12345'], + 'user' => ['username' => 'user', 'password' => 'userpassword', 'role' => 'user', 'token_fixe' => 'user-token-secret-67890'] + ], + 'regles_droits' => [ + 'Admins' => 'Accès total à toutes les voitures, détails confidentiels, modifications et suppressions.', + 'Propriétaires' => 'Peuvent modifier/supprimer leurs propres voitures et gérer leurs entretiens, kilométrages et notes.', + 'Public' => 'Consultation générale des voitures et recherche par immatriculation.' ], '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é', + 'GET /auth/me' => '[AUTHENTIFIÉ] Obtenir son profil et son rôle', 'POST /auth/logout' => '[AUTHENTIFIÉ] Révoquer le jeton de session' ], 'Voitures' => [ - 'GET /cars' => '[PUBLIC] Liste des voitures (filtrable par ?immatriculation=...)', - 'GET /cars/{id}' => '[PUBLIC] Détails d\'une voiture', + 'GET /cars' => '[PUBLIC] Liste des voitures (filtrable par ?immatriculation=..., ?user_id=..., ?mine=true)', + 'GET /cars/mes-voitures' => '[AUTHENTIFIÉ] Liste uniquement les voitures de l\'utilisateur connecté', + 'GET /cars/{id}' => '[PUBLIC] Détails d\'une voiture avec son propriétaire', 'GET /cars/immatriculation/{plaque}' => '[PUBLIC] Identifier une voiture par sa plaque', - 'GET /cars/{id}/etat' => '[AUTHENTIFIÉ] État complet (voiture, dernier km, entretiens, notes)', - 'POST /cars' => '[AUTHENTIFIÉ] Créer une voiture (marque, modele, annee, dateAchat, VIN, immatriculation)', - 'PUT /cars/{id}' => '[AUTHENTIFIÉ] Modifier les paramètres d\'une voiture', - 'DELETE /cars/{id}' => '[ADMIN] Supprimer une voiture et toutes ses données associées' + 'GET /cars/{id}/etat' => '[PROPRIÉTAIRE ou ADMIN] État complet (voiture, dernier km, entretiens, notes)', + 'POST /cars' => '[AUTHENTIFIÉ] Créer une voiture (attribuée automatiquement à l\'utilisateur connecté)', + 'PUT /cars/{id}' => '[PROPRIÉTAIRE ou ADMIN] Modifier les paramètres d\'une voiture', + 'DELETE /cars/{id}' => '[PROPRIÉTAIRE ou ADMIN] Supprimer une voiture et toutes ses données associées' ], 'Kilométrage' => [ 'GET /cars/{id}/kilometrage' => '[PUBLIC] Historique des relevés kilométriques', - 'POST /cars/{id}/kilometrage' => '[AUTHENTIFIÉ] Ajouter un relevé kilométrique (valeur, date_releve)', - 'DELETE /kilometrage/{id}' => '[ADMIN] Supprimer un relevé kilométrique' + 'POST /cars/{id}/kilometrage' => '[PROPRIÉTAIRE ou ADMIN] Ajouter un relevé kilométrique', + 'DELETE /kilometrage/{id}' => '[PROPRIÉTAIRE ou ADMIN] Supprimer un relevé kilométrique' ], 'Entretiens' => [ 'GET /cars/{id}/maintenance' => '[PUBLIC] Historique des entretiens d\'une voiture', 'GET /maintenance/{id}' => '[PUBLIC] Détails d\'un entretien', - 'POST /cars/{id}/maintenance' => '[AUTHENTIFIÉ] Ajouter un entretien (type_entretien, date_evenement, kilometrage, ...)', - 'PUT /maintenance/{id}' => '[AUTHENTIFIÉ] Modifier un entretien', - 'DELETE /maintenance/{id}' => '[ADMIN] Supprimer un entretien' + 'POST /cars/{id}/maintenance' => '[PROPRIÉTAIRE ou ADMIN] Ajouter un entretien', + 'PUT /maintenance/{id}' => '[PROPRIÉTAIRE ou ADMIN] Modifier un entretien', + 'DELETE /maintenance/{id}' => '[PROPRIÉTAIRE ou ADMIN] Supprimer un entretien' ], - 'Notes (Contenu interne)' => [ - 'GET /cars/{id}/notes' => '[AUTHENTIFIÉ] Consulter les notes d\'une voiture', - 'POST /cars/{id}/notes' => '[AUTHENTIFIÉ] Ajouter une note (titre, contenu)', - 'PUT /notes/{id}' => '[AUTHENTIFIÉ] Modifier une note', - 'DELETE /notes/{id}' => '[AUTHENTIFIÉ] Supprimer une note' + 'Notes (Confidentielles)' => [ + 'GET /cars/{id}/notes' => '[PROPRIÉTAIRE ou ADMIN] Consulter les notes de suivi', + 'POST /cars/{id}/notes' => '[PROPRIÉTAIRE ou ADMIN] Ajouter une note', + 'PUT /notes/{id}' => '[PROPRIÉTAIRE ou ADMIN] Modifier une note', + 'DELETE /notes/{id}' => '[PROPRIÉTAIRE ou ADMIN] Supprimer une note' ] ] ]); @@ -268,7 +326,6 @@ try { 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)); @@ -302,7 +359,6 @@ try { $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()) { @@ -332,7 +388,7 @@ try { ]); } - // GET /auth/me (Profil de l'utilisateur connecté) + // GET /auth/me if ($action === 'me' && $method === 'GET') { $user = authenticate($db, true); sendJson(200, [ @@ -347,7 +403,7 @@ try { ]); } - // POST /auth/logout (Révocation du jeton) + // POST /auth/logout if ($action === 'logout' && $method === 'POST') { $user = authenticate($db, true); $stmt = $db->prepare('UPDATE users SET api_token = NULL, token_expires_at = NULL WHERE id = ?'); @@ -373,6 +429,24 @@ try { // RESSOURCE: CARS (/cars ...) // ========================================================================= if ($resource === 'cars' || $resource === 'voitures') { + // GET /cars/mes-voitures : Liste des voitures de l'utilisateur connecté + if (isset($segments[1]) && (strtolower($segments[1]) === 'mes-voitures' || strtolower($segments[1]) === 'mine') && $method === 'GET') { + $currentUser = authenticate($db, true); + $stmt = $db->prepare(' + SELECT c.*, + u.username AS proprietaire_username, + u.nom AS proprietaire_nom, + (SELECT valeur FROM kilometrage WHERE car_id = c.id ORDER BY date_releve DESC, id DESC LIMIT 1) AS dernier_km + FROM cars c + LEFT JOIN users u ON c.user_id = u.id + WHERE c.user_id = ? + ORDER BY c.id ASC + '); + $stmt->execute([$currentUser['id']]); + $list = array_map('formatCarRow', $stmt->fetchAll()); + sendJson(200, ['success' => true, 'data' => $list]); + } + // GET /cars/immatriculation/{plaque} (Public) if (isset($segments[1]) && strtolower($segments[1]) === 'immatriculation' && $method === 'GET') { $plaque = $segments[2] ?? null; @@ -385,18 +459,25 @@ try { $carId = isset($segments[1]) && is_numeric($segments[1]) ? (int)$segments[1] : null; $subResource = $segments[2] ?? null; - // 1. GET /cars/{id}/etat [CONTENU RESTREINT - AUTHENTIFICATION REQUISE] + // 1. GET /cars/{id}/etat [PROPRIÉTAIRE OU ADMIN REQUIS] if ($carId && $subResource === 'etat' && $method === 'GET') { $currentUser = authenticate($db, true); + $car = getCarById($db, $carId); + if (!$car) { + sendJson(404, ['success' => false, 'error' => "Voiture #{$carId} introuvable"]); + } + + if (!canModifyCar($car, $currentUser)) { + sendJson(403, [ + 'success' => false, + 'error' => "Accès refusé. Les administrateurs et le propriétaire du véhicule sont les seuls autorisés à consulter son état complet." + ]); + } try { $stmt = $db->prepare('CALL sp_getEtatVoiture(:id)'); $stmt->execute([':id' => $carId]); - - $car = $stmt->fetch(); - if (!$car) { - sendJson(404, ['success' => false, 'error' => "Voiture #{$carId} introuvable"]); - } + $carRow = $stmt->fetch(); $maintenances = []; if ($stmt->nextRowset()) { @@ -410,8 +491,11 @@ try { $stmt->closeCursor(); sendJson(200, [ - 'success' => true, - 'consulted_by' => $currentUser['username'], + 'success' => true, + 'consulted_by' => [ + 'username' => $currentUser['username'], + 'role' => $currentUser['role'] + ], 'data' => [ 'voiture' => $car, 'maintenances' => $maintenances, @@ -419,17 +503,6 @@ try { ] ]); } catch (Exception $e) { - $stmtCar = $db->prepare(' - SELECT c.*, - (SELECT valeur FROM kilometrage WHERE car_id = c.id ORDER BY date_releve DESC, id DESC LIMIT 1) AS dernier_km - FROM cars c WHERE c.id = ? - '); - $stmtCar->execute([$carId]); - $car = $stmtCar->fetch(); - if (!$car) { - sendJson(404, ['success' => false, 'error' => "Voiture #{$carId} introuvable"]); - } - $stmtMaint = $db->prepare('SELECT * FROM maintenance_logs WHERE car_id = ? ORDER BY date_evenement DESC'); $stmtMaint->execute([$carId]); @@ -437,8 +510,11 @@ try { $stmtNotes->execute([$carId]); sendJson(200, [ - 'success' => true, - 'consulted_by' => $currentUser['username'], + 'success' => true, + 'consulted_by' => [ + 'username' => $currentUser['username'], + 'role' => $currentUser['role'] + ], 'data' => [ 'voiture' => $car, 'maintenances' => $stmtMaint->fetchAll(), @@ -468,9 +544,20 @@ try { sendJson(200, ['success' => true, 'data' => $data]); } - // POST: [ACTION PROTÉGÉE - AUTHENTIFICATION REQUISE] + // POST: [PROPRIÉTAIRE OU ADMIN REQUIS] if ($method === 'POST') { - authenticate($db, true); + $currentUser = authenticate($db, true); + $car = getCarById($db, $carId); + if (!$car) { + sendJson(404, ['success' => false, 'error' => "Voiture #{$carId} introuvable"]); + } + + if (!canModifyCar($car, $currentUser)) { + sendJson(403, [ + 'success' => false, + 'error' => "Accès refusé. Seul le propriétaire ou un administrateur peut enregistrer un relevé kilométrique pour cette voiture." + ]); + } $body = getBody(); if (!isset($body['valeur'])) { @@ -507,9 +594,20 @@ try { sendJson(200, ['success' => true, 'data' => $stmt->fetchAll()]); } - // POST: [ACTION PROTÉGÉE - AUTHENTIFICATION REQUISE] + // POST: [PROPRIÉTAIRE OU ADMIN REQUIS] if ($method === 'POST') { - authenticate($db, true); + $currentUser = authenticate($db, true); + $car = getCarById($db, $carId); + if (!$car) { + sendJson(404, ['success' => false, 'error' => "Voiture #{$carId} introuvable"]); + } + + if (!canModifyCar($car, $currentUser)) { + sendJson(403, [ + 'success' => false, + 'error' => "Accès refusé. Seul le propriétaire ou un administrateur peut ajouter un entretien pour cette voiture." + ]); + } $body = getBody(); if (empty($body['type_entretien']) || empty($body['date_evenement']) || !isset($body['kilometrage'])) { @@ -546,21 +644,30 @@ try { sendJson(405, ['success' => false, 'error' => 'Méthode non autorisée pour cette sous-ressource']); } - // 4. /cars/{id}/notes [CONTENU RESTREINT - AUTHENTIFICATION REQUISE] + // 4. /cars/{id}/notes [PROPRIÉTAIRE OU ADMIN REQUIS] if ($carId && $subResource === 'notes') { - // GET: Protégé (Notes de suivi confidentielles) - if ($method === 'GET') { - authenticate($db, true); + $currentUser = authenticate($db, true); + $car = getCarById($db, $carId); + if (!$car) { + sendJson(404, ['success' => false, 'error' => "Voiture #{$carId} introuvable"]); + } + if (!canModifyCar($car, $currentUser)) { + sendJson(403, [ + 'success' => false, + 'error' => "Accès refusé. Seul le propriétaire ou un administrateur peut consulter ou ajouter des notes pour cette voiture." + ]); + } + + // GET: Notes + if ($method === 'GET') { $stmt = $db->prepare('SELECT * FROM notes WHERE car_id = ? ORDER BY date_creation DESC'); $stmt->execute([$carId]); sendJson(200, ['success' => true, 'data' => $stmt->fetchAll()]); } - // POST: [ACTION PROTÉGÉE - AUTHENTIFICATION REQUISE] + // POST: Ajouter une note if ($method === 'POST') { - authenticate($db, true); - $body = getBody(); if (empty($body['contenu'])) { sendJson(400, ['success' => false, 'error' => 'Le champ contenu est obligatoire']); @@ -588,39 +695,59 @@ try { // --- ACTIONS PRINCIPALES SUR LES VOITURES --- - // GET /cars : Liste toutes les voitures (Public) + // GET /cars : Liste toutes les voitures (Public / Filtrable) if ($carId === null && $subResource === null && $method === 'GET') { + // Filtre par immatriculation if (!empty($_GET['immatriculation'])) { findCarByPlate($db, (string)$_GET['immatriculation']); } - $sql = 'SELECT c.*, - (SELECT valeur FROM kilometrage WHERE car_id = c.id ORDER BY date_releve DESC, id DESC LIMIT 1) AS dernier_km - FROM cars c - ORDER BY c.id ASC'; - $stmt = $db->query($sql); - sendJson(200, ['success' => true, 'data' => $stmt->fetchAll()]); + $currentUser = authenticate($db, false); + $where = []; + $params = []; + + // Filtre mes voitures (?mine=true) + if (!empty($_GET['mine']) && $currentUser) { + $where[] = 'c.user_id = ?'; + $params[] = $currentUser['id']; + } elseif (!empty($_GET['user_id'])) { + $where[] = 'c.user_id = ?'; + $params[] = (int)$_GET['user_id']; + } + + $sql = ' + SELECT c.*, + u.username AS proprietaire_username, + u.nom AS proprietaire_nom, + (SELECT valeur FROM kilometrage WHERE car_id = c.id ORDER BY date_releve DESC, id DESC LIMIT 1) AS dernier_km + FROM cars c + LEFT JOIN users u ON c.user_id = u.id + '; + + if (!empty($where)) { + $sql .= ' WHERE ' . implode(' AND ', $where); + } + $sql .= ' ORDER BY c.id ASC'; + + $stmt = $db->prepare($sql); + $stmt->execute($params); + $rows = array_map('formatCarRow', $stmt->fetchAll()); + + sendJson(200, ['success' => true, 'data' => $rows]); } // GET /cars/{id} : Consulter une voiture (Public) if ($carId !== null && $subResource === null && $method === 'GET') { - $stmt = $db->prepare(' - SELECT c.*, - (SELECT valeur FROM kilometrage WHERE car_id = c.id ORDER BY date_releve DESC, id DESC LIMIT 1) AS dernier_km - FROM cars c - WHERE c.id = ? - '); - $stmt->execute([$carId]); - $car = $stmt->fetch(); + $car = getCarById($db, $carId); if (!$car) { sendJson(404, ['success' => false, 'error' => "Voiture #{$carId} introuvable"]); } sendJson(200, ['success' => true, 'data' => $car]); } - // POST /cars : Créer une voiture [ACTION PROTÉGÉE - AUTHENTIFICATION REQUISE] + // POST /cars : Créer une voiture [AUTHENTIFIÉ REQUIS - ATTRIBUTION DU USER_ID] if ($carId === null && $method === 'POST') { - authenticate($db, true); + $currentUser = authenticate($db, true); $body = getBody(); $required = ['marque', 'modele', 'annee', 'dateAchat']; @@ -637,7 +764,19 @@ try { $vin = !empty($body['VIN']) ? trim((string)$body['VIN']) : null; $immatriculation = !empty($body['immatriculation']) ? strtoupper(trim((string)$body['immatriculation'])) : null; - // Vérifier unicité de l'immatriculation + // Détermination du propriétaire (user_id): + // Si l'utilisateur est admin et spécifie un user_id, on l'attribue à cet utilisateur; sinon c'est l'utilisateur connecté + $targetUserId = (int)$currentUser['id']; + if (strtolower((string)$currentUser['role']) === 'admin' && !empty($body['user_id'])) { + $targetUserId = (int)$body['user_id']; + $chkUser = $db->prepare('SELECT id FROM users WHERE id = ?'); + $chkUser->execute([$targetUserId]); + if (!$chkUser->fetch()) { + sendJson(400, ['success' => false, 'error' => "L'utilisateur #$targetUserId spécifié est introuvable."]); + } + } + + // Vérifier unicité de la plaque if ($immatriculation !== null) { $normImmat = str_replace(['-', ' ', '.', '_'], '', $immatriculation); $stmtCheck = $db->prepare(' @@ -654,10 +793,11 @@ try { } } - // Appel de la procédure stockée sp_insertVoiture + // Procédure stockée sp_insertVoiture avec user_id try { - $stmt = $db->prepare('CALL sp_insertVoiture(:marque, :modele, :annee, :dateAchat, :vin, :immatriculation)'); + $stmt = $db->prepare('CALL sp_insertVoiture(:user_id, :marque, :modele, :annee, :dateAchat, :vin, :immatriculation)'); $stmt->execute([ + ':user_id' => $targetUserId, ':marque' => $marque, ':modele' => $modele, ':annee' => $annee, @@ -669,8 +809,8 @@ try { $stmt->closeCursor(); $newId = isset($res['new_id']) ? (int)$res['new_id'] : (int)$db->lastInsertId(); } catch (Exception $e) { - $stmt = $db->prepare('INSERT INTO cars (marque, modele, annee, dateAchat, VIN, immatriculation) VALUES (?, ?, ?, ?, ?, ?)'); - $stmt->execute([$marque, $modele, $annee, $dateAchat, $vin, $immatriculation]); + $stmt = $db->prepare('INSERT INTO cars (user_id, marque, modele, annee, dateAchat, VIN, immatriculation) VALUES (?, ?, ?, ?, ?, ?, ?)'); + $stmt->execute([$targetUserId, $marque, $modele, $annee, $dateAchat, $vin, $immatriculation]); $newId = (int)$db->lastInsertId(); } @@ -679,33 +819,40 @@ try { $kmStmt->execute([$newId, $dateAchat, (int)$body['kilometrage_initial']]); } - $stmtGet = $db->prepare(' - SELECT c.*, - (SELECT valeur FROM kilometrage WHERE car_id = c.id ORDER BY date_releve DESC, id DESC LIMIT 1) AS dernier_km - FROM cars c - WHERE c.id = ? - '); - $stmtGet->execute([$newId]); + $createdCar = getCarById($db, $newId); sendJson(201, [ 'success' => true, 'message' => 'Voiture créée avec succès', - 'data' => $stmtGet->fetch() + 'data' => $createdCar ]); } - // PUT / PATCH /cars/{id} : Mettre à jour les paramètres [ACTION PROTÉGÉE - AUTHENTIFICATION REQUISE] + // PUT / PATCH /cars/{id} : Mettre à jour les paramètres [SEUL LE PROPRIÉTAIRE OU ADMIN] if ($carId !== null && $subResource === null && ($method === 'PUT' || $method === 'PATCH')) { - authenticate($db, true); + $currentUser = authenticate($db, true); + $car = getCarById($db, $carId); - $check = $db->prepare('SELECT id FROM cars WHERE id = ?'); - $check->execute([$carId]); - if (!$check->fetch()) { + if (!$car) { sendJson(404, ['success' => false, 'error' => "Voiture #{$carId} introuvable"]); } + // Vérification de la propriété + if (!canModifyCar($car, $currentUser)) { + sendJson(403, [ + 'success' => false, + 'error' => "Accès refusé. Seul le propriétaire de la voiture ou un administrateur est autorisé à la modifier." + ]); + } + $body = getBody(); $allowedFields = ['marque', 'modele', 'annee', 'dateAchat', 'VIN', 'immatriculation']; + + // Seul l'admin peut réassigner le user_id de la voiture + if (strtolower((string)$currentUser['role']) === 'admin' && array_key_exists('user_id', $body)) { + $allowedFields[] = 'user_id'; + } + $updates = []; $params = []; @@ -744,31 +891,32 @@ try { $stmt = $db->prepare($sql); $stmt->execute($params); - $stmtGet = $db->prepare(' - SELECT c.*, - (SELECT valeur FROM kilometrage WHERE car_id = c.id ORDER BY date_releve DESC, id DESC LIMIT 1) AS dernier_km - FROM cars c - WHERE c.id = ? - '); - $stmtGet->execute([$carId]); + $updatedCar = getCarById($db, $carId); sendJson(200, [ 'success' => true, 'message' => 'Paramètres de la voiture mis à jour avec succès', - 'data' => $stmtGet->fetch() + 'data' => $updatedCar ]); } - // DELETE /cars/{id} : Supprimer une voiture [ACTION PROTÉGÉE - RÔLE ADMIN REQUIS] + // DELETE /cars/{id} : Supprimer une voiture [SEUL LE PROPRIÉTAIRE OU ADMIN] if ($carId !== null && $subResource === null && $method === 'DELETE') { - authenticate($db, true, 'admin'); + $currentUser = authenticate($db, true); + $car = getCarById($db, $carId); - $check = $db->prepare('SELECT id FROM cars WHERE id = ?'); - $check->execute([$carId]); - if (!$check->fetch()) { + if (!$car) { sendJson(404, ['success' => false, 'error' => "Voiture #{$carId} introuvable"]); } + // Vérification de la propriété + if (!canModifyCar($car, $currentUser)) { + sendJson(403, [ + 'success' => false, + 'error' => "Accès refusé. Seul le propriétaire de la voiture ou un administrateur est autorisé à la supprimer." + ]); + } + try { $stmt = $db->prepare('CALL sp_deleteVoiture(:id)'); $stmt->execute([':id' => $carId]); @@ -793,15 +941,24 @@ try { if ($resource === 'kilometrage') { $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') { - authenticate($db, true, 'admin'); + $currentUser = authenticate($db, true); + + // Récupérer la voiture liée + $stmtKm = $db->prepare('SELECT car_id FROM kilometrage WHERE id = ?'); + $stmtKm->execute([$kmId]); + $kmRow = $stmtKm->fetch(); + if (!$kmRow) { + sendJson(404, ['success' => false, 'error' => "Relevé kilométrique #{$kmId} introuvable"]); + } + + $car = getCarById($db, (int)$kmRow['car_id']); + if (!canModifyCar($car, $currentUser)) { + sendJson(403, ['success' => false, 'error' => "Accès refusé. Seul le propriétaire du véhicule ou un administrateur peut supprimer ce relevé."]); + } $stmt = $db->prepare('DELETE FROM kilometrage WHERE id = ?'); $stmt->execute([$kmId]); - if ($stmt->rowCount() === 0) { - sendJson(404, ['success' => false, 'error' => "Relevé kilométrique #{$kmId} introuvable"]); - } sendJson(200, ['success' => true, 'message' => "Relevé kilométrique #{$kmId} supprimé"]); } @@ -818,7 +975,6 @@ try { sendJson(400, ['success' => false, 'error' => 'Identifiant d\'entretien requis']); } - // GET: Public if ($method === 'GET') { $stmt = $db->prepare('SELECT * FROM maintenance_logs WHERE id = ?'); $stmt->execute([$maintId]); @@ -829,9 +985,20 @@ try { sendJson(200, ['success' => true, 'data' => $item]); } - // PUT/PATCH: [ACTION PROTÉGÉE - AUTHENTIFICATION REQUISE] if ($method === 'PUT' || $method === 'PATCH') { - authenticate($db, true); + $currentUser = authenticate($db, true); + + $stmtMaint = $db->prepare('SELECT car_id FROM maintenance_logs WHERE id = ?'); + $stmtMaint->execute([$maintId]); + $maintRow = $stmtMaint->fetch(); + if (!$maintRow) { + sendJson(404, ['success' => false, 'error' => "Entretien #{$maintId} introuvable"]); + } + + $car = getCarById($db, (int)$maintRow['car_id']); + if (!canModifyCar($car, $currentUser)) { + sendJson(403, ['success' => false, 'error' => "Accès refusé. Seul le propriétaire du véhicule ou un administrateur peut modifier cet entretien."]); + } $body = getBody(); $allowed = ['type_entretien', 'date_evenement', 'kilometrage', 'description', 'prix']; @@ -862,15 +1029,23 @@ try { ]); } - // DELETE: [ACTION PROTÉGÉE - RÔLE ADMIN REQUIS] if ($method === 'DELETE') { - authenticate($db, true, 'admin'); + $currentUser = authenticate($db, true); + + $stmtMaint = $db->prepare('SELECT car_id FROM maintenance_logs WHERE id = ?'); + $stmtMaint->execute([$maintId]); + $maintRow = $stmtMaint->fetch(); + if (!$maintRow) { + sendJson(404, ['success' => false, 'error' => "Entretien #{$maintId} introuvable"]); + } + + $car = getCarById($db, (int)$maintRow['car_id']); + if (!canModifyCar($car, $currentUser)) { + sendJson(403, ['success' => false, 'error' => "Accès refusé. Seul le propriétaire du véhicule ou un administrateur peut supprimer cet entretien."]); + } $stmt = $db->prepare('DELETE FROM maintenance_logs WHERE id = ?'); $stmt->execute([$maintId]); - if ($stmt->rowCount() === 0) { - sendJson(404, ['success' => false, 'error' => "Entretien #{$maintId} introuvable"]); - } sendJson(200, ['success' => true, 'message' => "Entretien #{$maintId} supprimé avec succès"]); } @@ -878,7 +1053,7 @@ try { } // ========================================================================= - // RESSOURCE: NOTES DIRECT (/notes/{id}) [CONTENU RESTREINT - AUTHENTIFICATION REQUISE] + // RESSOURCE: NOTES DIRECT (/notes/{id}) [PROPRIÉTAIRE OU ADMIN REQUIS] // ========================================================================= if ($resource === 'notes') { $noteId = isset($segments[1]) && is_numeric($segments[1]) ? (int)$segments[1] : null; @@ -887,23 +1062,27 @@ try { sendJson(400, ['success' => false, 'error' => 'Identifiant de note requis']); } - // GET: Authentifié - if ($method === 'GET') { - authenticate($db, true); + $currentUser = authenticate($db, true); - $stmt = $db->prepare('SELECT * FROM notes WHERE id = ?'); - $stmt->execute([$noteId]); - $item = $stmt->fetch(); - if (!$item) { - sendJson(404, ['success' => false, 'error' => "Note #{$noteId} introuvable"]); - } - sendJson(200, ['success' => true, 'data' => $item]); + $stmtNote = $db->prepare('SELECT car_id, titre, contenu, date_creation FROM notes WHERE id = ?'); + $stmtNote->execute([$noteId]); + $noteRow = $stmtNote->fetch(); + if (!$noteRow) { + sendJson(404, ['success' => false, 'error' => "Note #{$noteId} introuvable"]); } - // PUT/PATCH: Authentifié - if ($method === 'PUT' || $method === 'PATCH') { - authenticate($db, true); + $car = getCarById($db, (int)$noteRow['car_id']); + if (!canModifyCar($car, $currentUser)) { + sendJson(403, ['success' => false, 'error' => "Accès refusé. Seul le propriétaire du véhicule ou un administrateur peut accéder à cette note."]); + } + // GET: Consulter la note + if ($method === 'GET') { + sendJson(200, ['success' => true, 'data' => $noteRow]); + } + + // PUT/PATCH: Modifier la note + if ($method === 'PUT' || $method === 'PATCH') { $body = getBody(); $allowed = ['titre', 'contenu']; $updates = []; @@ -933,15 +1112,10 @@ try { ]); } - // DELETE: Authentifié + // DELETE: Supprimer la note if ($method === 'DELETE') { - authenticate($db, true); - $stmt = $db->prepare('DELETE FROM notes WHERE id = ?'); $stmt->execute([$noteId]); - if ($stmt->rowCount() === 0) { - sendJson(404, ['success' => false, 'error' => "Note #{$noteId} introuvable"]); - } sendJson(200, ['success' => true, 'message' => "Note #{$noteId} supprimée avec succès"]); } diff --git a/voituresapi (1).sql b/voituresapi (1).sql index 02fe86a..1bf1aae 100644 --- a/voituresapi (1).sql +++ b/voituresapi (1).sql @@ -53,9 +53,9 @@ CREATE DEFINER=`root`@`localhost` PROCEDURE `sp_getKilometrages` (IN `p_id` INT, END$$ DROP PROCEDURE IF EXISTS `sp_insertVoiture`$$ -CREATE DEFINER=`root`@`localhost` PROCEDURE `sp_insertVoiture` (IN `p_marque` VARCHAR(255), IN `p_modele` VARCHAR(255), IN `p_annee` INT, IN `p_dateAchat` DATE, IN `p_VIN` VARCHAR(255), IN `p_immatriculation` VARCHAR(20)) BEGIN - INSERT INTO cars (marque, modele, annee, dateAchat, VIN, immatriculation) - VALUES (p_marque, p_modele, p_annee, p_dateAchat, p_VIN, p_immatriculation); +CREATE DEFINER=`root`@`localhost` PROCEDURE `sp_insertVoiture` (IN `p_user_id` INT, IN `p_marque` VARCHAR(255), IN `p_modele` VARCHAR(255), IN `p_annee` INT, IN `p_dateAchat` DATE, IN `p_VIN` VARCHAR(255), IN `p_immatriculation` VARCHAR(20)) BEGIN + INSERT INTO cars (user_id, marque, modele, annee, dateAchat, VIN, immatriculation) + VALUES (p_user_id, p_marque, p_modele, p_annee, p_dateAchat, p_VIN, p_immatriculation); SELECT LAST_INSERT_ID() AS new_id; END$$ @@ -63,6 +63,35 @@ DELIMITER ; -- -------------------------------------------------------- +-- +-- 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'); + +-- -------------------------------------------------------- + -- -- Structure de la table `cars` -- @@ -70,6 +99,7 @@ DELIMITER ; DROP TABLE IF EXISTS `cars`; CREATE TABLE IF NOT EXISTS `cars` ( `id` int NOT NULL AUTO_INCREMENT, + `user_id` int DEFAULT NULL, `marque` varchar(50) NOT NULL, `modele` varchar(50) NOT NULL, `annee` int NOT NULL, @@ -77,6 +107,7 @@ CREATE TABLE IF NOT EXISTS `cars` ( `VIN` varchar(17) DEFAULT NULL, `immatriculation` varchar(20) DEFAULT NULL, PRIMARY KEY (`id`), + KEY `idx_cars_user_id` (`user_id`), UNIQUE KEY `idx_immatriculation` (`immatriculation`) ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; @@ -84,9 +115,9 @@ CREATE TABLE IF NOT EXISTS `cars` ( -- Déchargement des données de la table `cars` -- -INSERT INTO `cars` (`id`, `marque`, `modele`, `annee`, `dateAchat`, `VIN`, `immatriculation`) VALUES -(1, 'Peugeot', '207', 2009, '2025-05-20', 'VF3WA8HZCXXXXXXXX', 'AB-123-CD'), -(2, 'Peugeot', '208', 2010, '2025-05-20', 'VF3WA8HZCXXXXXXXX', 'EF-456-GH'); +INSERT INTO `cars` (`id`, `user_id`, `marque`, `modele`, `annee`, `dateAchat`, `VIN`, `immatriculation`) VALUES +(1, 1, 'Peugeot', '207', 2009, '2025-05-20', 'VF3WA8HZCXXXXXXXX', 'AB-123-CD'), +(2, 2, 'Peugeot', '208', 2010, '2025-05-20', 'VF3WA8HZCXXXXXXXX', 'EF-456-GH'); -- -------------------------------------------------------- @@ -185,31 +216,10 @@ ALTER TABLE `notes` ADD CONSTRAINT `fk_notes_car_id` FOREIGN KEY (`car_id`) REFERENCES `cars` (`id`) ON DELETE CASCADE; -- --- Structure de la table `users` +-- Contraintes pour la table `cars` -- - -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'); +ALTER TABLE `cars` + ADD CONSTRAINT `fk_cars_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL; COMMIT;