From 81b1b3b83552af7501c97a56b6146dcfd75e2aca Mon Sep 17 00:00:00 2001 From: Whykioh Date: Sat, 11 Jul 2026 09:13:29 +0200 Subject: [PATCH] Add User system --- .gitignore | 1 + config/database.example.php | 35 ++++++++ controllers/UserController.php | 75 ++++++++++++++++ index.php | 21 ++++- models/User.php | 159 +++++++++++++++++++++++++++++++++ 5 files changed, 287 insertions(+), 4 deletions(-) create mode 100644 .gitignore create mode 100644 config/database.example.php create mode 100644 controllers/UserController.php create mode 100644 models/User.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..46ac4fb --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +config/database.php \ No newline at end of file diff --git a/config/database.example.php b/config/database.example.php new file mode 100644 index 0000000..a953536 --- /dev/null +++ b/config/database.example.php @@ -0,0 +1,35 @@ +conn = null; + + try { + $this->conn = new PDO( + "mysql:host=" . $this->host . ";dbname=" . $this->db_name . ";charset=utf8mb4", + $this->username, + $this->password + ); + + // Options de sécurité et de debug bien pratiques en dev + $this->conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // Active les erreurs SQL sous forme d'exceptions + $this->conn->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); // Renvoie les résultats sous forme de tableau associatif + + } catch(PDOException $exception) { + // Si ça plante, on renvoie une erreur JSON propre au lieu d'un gros bug PHP + http_response_code(500); + echo json_encode(["error" => "Erreur de connexion à la base de données : " . $exception->getMessage()]); + exit(); + } + + return $this->conn; + } +} \ No newline at end of file diff --git a/controllers/UserController.php b/controllers/UserController.php new file mode 100644 index 0000000..81aea12 --- /dev/null +++ b/controllers/UserController.php @@ -0,0 +1,75 @@ +db = $db; + $this->userModel = new User($this->db); + } + + // --- INTERCEPTE L'INSCRIPTION --- + public function register($data) { + // Validation basique des données reçues + if (empty($data['username']) || empty($data['email']) || empty($data['password'])) { + http_response_code(400); + echo json_encode(["success" => false, "message" => "Données incomplètes. Pseudo, email et mot de passe requis."]); + return; + } + + // Appel du model pour l'insertion + $result = $this->userModel->register($data['username'], $data['email'], $data['password']); + + if ($result['success']) { + http_response_code(201); // 201 Created + } else { + http_response_code(400); // 400 Bad Request + } + + echo json_encode($result); + } + + // --- INTERCEPTE LA CONNEXION --- + public function login($data) { + if (empty($data['email']) || empty($data['password'])) { + http_response_code(400); + echo json_encode(["success" => false, "message" => "Email et mot de passe requis."]); + return; + } + + $result = $this->userModel->login($data['email'], $data['password']); + + if ($result['success']) { + http_response_code(200); // 200 OK + // TODO: Plus tard, on générera un token (JWT) ici pour sécuriser les sessions de l'API + } else { + http_response_code(401); // 401 Unauthorized + } + + echo json_encode($result); + } + + // --- INTERCEPTE LA LIAISON DE COMPTES --- + public function link($data) { + // Sécurité temporaire : On demande l'ID de l'utilisateur actuel et le code du partenaire + if (empty($data['user_id']) || empty($data['partner_code'])) { + http_response_code(400); + echo json_encode(["success" => false, "message" => "ID utilisateur et code partenaire requis."]); + return; + } + + $result = $this->userModel->linkPartner($data['user_id'], $data['partner_code']); + + if ($result['success']) { + http_response_code(200); + } else { + http_response_code(400); + } + + echo json_encode($result); + } +} \ No newline at end of file diff --git a/index.php b/index.php index 9b5849a..e971cd1 100644 --- a/index.php +++ b/index.php @@ -15,6 +15,12 @@ if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') { // --- CONFIGURATION --- require_once __DIR__ . '/config/database.php'; +require_once __DIR__ . '/controllers/UserController.php'; + +// Initialisation de la BDD et du contrôleur +$database = new Database(); +$db = $database->getConnection(); +$userController = new UserController($db); // On récupère la route épurée (ex: si on tape /register, $route vaudra 'register') $request_uri = explode('?', $_SERVER['REQUEST_URI'], 2)[0]; @@ -29,8 +35,7 @@ switch ($route) { case 'register': if ($method === 'POST') { - // TODO: Appeler le contrôleur d'inscription - echo json_encode(["success" => true, "message" => "Route inscription OK"]); + $userController->register($data); } else { http_response_code(405); echo json_encode(["error" => "Méthode non autorisée (POST requis)"]); @@ -39,8 +44,16 @@ switch ($route) { case 'login': if ($method === 'POST') { - // TODO: Appeler le contrôleur de connexion - echo json_encode(["success" => true, "message" => "Route connexion OK"]); + $userController->login($data); + } else { + http_response_code(405); + echo json_encode(["error" => "Méthode non autorisée (POST requis)"]); + } + break; + + case 'link-partner': + if ($method === 'POST') { + $userController->link($data); } else { http_response_code(405); echo json_encode(["error" => "Méthode non autorisée (POST requis)"]); diff --git a/models/User.php b/models/User.php new file mode 100644 index 0000000..b39e28b --- /dev/null +++ b/models/User.php @@ -0,0 +1,159 @@ +conn = $db; + } + + // --- 1. INSCRIPTION --- + public function register($username, $email, $password) { + // On vérifie si l'email ou le pseudo existe déjà + if ($this->emailExists($email) || $this->usernameExists($username)) { + return ["success" => false, "message" => "Pseudo ou email déjà utilisé."]; + } + + // Génération d'un code de liaison unique (ex: WG-X89F2) + $pairing_code = $this->generateUniquePairingCode(); + + // Hachage sécurisé du mot de passe + $hashed_password = password_hash($password, PASSWORD_BCRYPT); + + $query = "INSERT INTO " . $this->table_name . " + SET username = :username, email = :email, password = :password, pairing_code = :pairing_code"; + + $stmt = $this->conn->prepare($query); + + $stmt->bindParam(":username", $username); + $stmt->bindParam(":email", $email); + $stmt->bindParam(":password", $hashed_password); + $stmt->bindParam(":pairing_code", $pairing_code); + + if ($stmt->execute()) { + return [ + "success" => true, + "message" => "Utilisateur créé avec succès.", + "pairing_code" => $pairing_code + ]; + } + return ["success" => false, "message" => "Erreur lors de l'inscription."]; + } + + // --- 2. CONNEXION --- + public function login($email, $password) { + $query = "SELECT id, username, email, password, pairing_code, partner_id FROM " . $this->table_name . " WHERE email = :email LIMIT 0,1"; + $stmt = $this->conn->prepare($query); + $stmt->bindParam(":email", $email); + $stmt->execute(); + + if ($stmt->rowCount() > 0) { + $row = $stmt->fetch(); + + // Vérification du mot de passe + if (password_verify($password, $row['password'])) { + return [ + "success" => true, + "user" => [ + "id" => $row['id'], + "username" => $row['username'], + "email" => $row['email'], + "pairing_code" => $row['pairing_code'], + "partner_id" => $row['partner_id'] + ] + ]; + } + } + return ["success" => false, "message" => "Identifiants incorrects."]; + } + + // --- 3. LIAISON DE DEUX COMPTES --- + public function linkPartner($current_user_id, $partner_code) { + // 1. Trouver le partenaire qui possède ce code + $query = "SELECT id, partner_id FROM " . $this->table_name . " WHERE pairing_code = :partner_code LIMIT 0,1"; + $stmt = $this->conn->prepare($query); + $stmt->bindParam(":partner_code", $partner_code); + $stmt->execute(); + + if ($stmt->rowCount() == 0) { + return ["success" => false, "message" => "Code de liaison invalide."]; + } + + $partner = $stmt->fetch(); + $partner_id = $partner['id']; + + // Sécurité : On ne peut pas se lier à soi-même + if ($partner_id == $current_user_id) { + return ["success" => false, "message" => "Tu ne peux pas te lier à ton propre compte."]; + } + + // Sécurité : Vérifier si le partenaire est déjà pris + if (!is_null($partner['partner_id'])) { + return ["success" => false, "message" => "Ce partenaire est déjà lié à quelqu'un d'autre."]; + } + + try { + // On commence une transaction SQL pour être sûr que les deux updates passent (ou aucun) + $this->conn->beginTransaction(); + + // Update de l'utilisateur actuel + $query1 = "UPDATE " . $this->table_name . " SET partner_id = :partner_id WHERE id = :current_id"; + $stmt1 = $this->conn->prepare($query1); + $stmt1->bindParam(":partner_id", $partner_id); + $stmt1->bindParam(":current_id", $current_user_id); + $stmt1->execute(); + + // Update du partenaire (réciprocité) + $query2 = "UPDATE " . $this->table_name . " SET partner_id = :current_id WHERE id = :partner_id"; + $stmt2 = $this->conn->prepare($query2); + $stmt2->bindParam(":current_id", $current_user_id); + $stmt2->bindParam(":partner_id", $partner_id); + $stmt2->execute(); + + $this->conn->commit(); + return ["success" => true, "message" => "Comptes liés avec succès !"]; + + } catch (Exception $e) { + $this->conn->rollBack(); + return ["success" => false, "message" => "Erreur lors de la liaison."]; + } + } + + // --- UTILS / VALIDATIONS --- + private function emailExists($email) { + $query = "SELECT id FROM " . $this->table_name . " WHERE email = :email LIMIT 1"; + $stmt = $this->conn->prepare($query); + $stmt->bindParam(":email", $email); + $stmt->execute(); + return $stmt->rowCount() > 0; + } + + private function usernameExists($username) { + $query = "SELECT id FROM " . $this->table_name . " WHERE username = :username LIMIT 1"; + $stmt = $this->conn->prepare($query); + $stmt->bindParam(":username", $username); + $stmt->execute(); + return $stmt->rowCount() > 0; + } + + private function generateUniquePairingCode() { + $characters = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + do { + $code = 'WG-'; + for ($i = 0; $i < 6; $i++) { + $code .= $characters[rand(0, strlen($characters) - 1)]; + } + + // On vérifie en BDD que le code n'existe pas déjà par pur hasard + $query = "SELECT id FROM " . $this->table_name . " WHERE pairing_code = :code LIMIT 1"; + $stmt = $this->conn->prepare($query); + $stmt->bindParam(":code", $code); + $stmt->execute(); + $exists = $stmt->rowCount() > 0; + } while ($exists); + + return $code; + } +} \ No newline at end of file