From 6c0056944c5b60fa42c0ba51f60714d4416b6291 Mon Sep 17 00:00:00 2001 From: yolou Date: Wed, 21 Oct 2026 15:57:28 +0200 Subject: [PATCH] Ajout du tp 9 et 10 pour l'actualisation du mini-crm --- mini-crm/mini-crm_TP9_TP10.zip | Bin 0 -> 5686 bytes mini-crm/mini-crm_TP9_TP10/README_TP9_TP10.md | 140 ++++++++++++++++++ mini-crm/mini-crm_TP9_TP10/TRACE_TP9_TP10.txt | 11 ++ .../Http/Controllers/Api/ClientController.php | 108 ++++++++++++++ .../app/Http/Resources/ClientResource.php | 24 +++ mini-crm/mini-crm_TP9_TP10/bootstrap/app.php | 28 ++++ mini-crm/mini-crm_TP9_TP10/routes/api.php | 31 ++++ 7 files changed, 342 insertions(+) create mode 100644 mini-crm/mini-crm_TP9_TP10.zip create mode 100644 mini-crm/mini-crm_TP9_TP10/README_TP9_TP10.md create mode 100644 mini-crm/mini-crm_TP9_TP10/TRACE_TP9_TP10.txt create mode 100644 mini-crm/mini-crm_TP9_TP10/app/Http/Controllers/Api/ClientController.php create mode 100644 mini-crm/mini-crm_TP9_TP10/app/Http/Resources/ClientResource.php create mode 100644 mini-crm/mini-crm_TP9_TP10/bootstrap/app.php create mode 100644 mini-crm/mini-crm_TP9_TP10/routes/api.php diff --git a/mini-crm/mini-crm_TP9_TP10.zip b/mini-crm/mini-crm_TP9_TP10.zip new file mode 100644 index 0000000000000000000000000000000000000000..4721125df4d3142fcf263b7a4b838bb03929ee9b GIT binary patch literal 5686 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 ZS1ExIyWI0RR_nAyohX literal 0 HcmV?d00001 diff --git a/mini-crm/mini-crm_TP9_TP10/README_TP9_TP10.md b/mini-crm/mini-crm_TP9_TP10/README_TP9_TP10.md new file mode 100644 index 0000000..eaada80 --- /dev/null +++ b/mini-crm/mini-crm_TP9_TP10/README_TP9_TP10.md @@ -0,0 +1,140 @@ +# Mini-CRM — TP9 + TP10 + +Ce ZIP contient les fichiers à ajouter/remplacer dans le projet `mini-crm` +du dépôt `yolou/BUT2`. + +## Ce que j'ai fait — TP9 + +- J'ai activé le routage API dans `bootstrap/app.php`. +- J'ai créé `routes/api.php`. +- J'ai ajouté `GET /api/test`. +- J'ai créé un contrôleur séparé `Api/ClientController`. +- J'ai ajouté `GET /api/clients` pour récupérer les clients. +- J'ai ajouté `GET /api/clients/{client}` avec le route model binding. +- J'ai ajouté `POST /api/clients` pour créer un client. +- J'ai validé `name`, `email`, `phone` et `city`. +- Comme la migration du projet impose un email unique, j'ai ajouté la règle + `unique:clients,email`. +- J'ai réalisé le défi autonome : + `GET /api/clients/search?email=...`. +- Si aucun email ne correspond, l'API renvoie 404. + +## Ce que j'ai fait — TP10 + +- J'ai créé `ClientResource` afin de maîtriser le JSON exposé. +- J'utilise la Resource pour la liste, le détail, la création et la modification. +- J'ai ajouté `PATCH /api/clients/{client}`. +- Pour PATCH, j'utilise `sometimes` afin de permettre une modification partielle. +- Pour l'email, j'ignore l'identifiant du client courant lors du contrôle d'unicité. +- J'ai ajouté `DELETE /api/clients/{client}`. +- Une suppression réussie renvoie HTTP 204 No Content. +- J'ai réalisé le défi de pagination avec `Client::paginate(10)`. + +## Installation dans ton projet + +Copie le contenu de ce dossier dans : + +`C:\Users\Junior\Desktop\laraveltp\mini_crm_aylane` + +en conservant l'arborescence. + +Les fichiers principaux sont : + +- `bootstrap/app.php` +- `routes/api.php` +- `app/Http/Controllers/Api/ClientController.php` +- `app/Http/Resources/ClientResource.php` + +Le modèle `app/Models/Client.php` de ton dépôt possède déjà les champs fillable +`name`, `email`, `phone`, `city`, donc il n'a pas besoin d'être remplacé. + +## Commandes à lancer + +```bash +php artisan optimize:clear +php artisan route:list --path=api +php artisan serve +``` + +> Le sujet propose `php artisan install:api`, notamment pour installer Sanctum. +> Ici Sanctum n'est pas nécessaire au fonctionnement demandé dans les TP9/TP10 : +> le routage API est déclaré directement dans `bootstrap/app.php`. +> Le TP10 demande justement de ne pas mettre en place un nouveau système +> d'authentification pour cette partie pédagogique. + +## Routes attendues + +| Méthode | URL | Rôle | +|---|---|---| +| GET | `/api/test` | Vérifier l'API | +| GET | `/api/clients` | Clients paginés (10/page) | +| GET | `/api/clients/search?email=...` | Recherche par email | +| GET | `/api/clients/{client}` | Détail d'un client | +| POST | `/api/clients` | Créer un client | +| PATCH | `/api/clients/{client}` | Modifier partiellement | +| DELETE | `/api/clients/{client}` | Supprimer | + +## Tests rapides + +### Créer + +```bash +curl -X POST http://127.0.0.1:8000/api/clients ^ + -H "Content-Type: application/json" ^ + -H "Accept: application/json" ^ + -d "{\"name\":\"Thomas Leroy\",\"email\":\"thomas@example.com\",\"city\":\"Paris\"}" +``` + +Résultat attendu : **201 Created**. + +### Modifier + +```bash +curl -X PATCH http://127.0.0.1:8000/api/clients/1 ^ + -H "Content-Type: application/json" ^ + -H "Accept: application/json" ^ + -d "{\"name\":\"Jean Dupont Modifié\"}" +``` + +Résultat attendu : **200 OK**. + +### Supprimer + +```bash +curl -X DELETE http://127.0.0.1:8000/api/clients/1 -H "Accept: application/json" +``` + +Résultat attendu : **204 No Content**. + +### Pagination + +Ouvrir : + +`http://127.0.0.1:8000/api/clients?page=1` + +puis : + +`http://127.0.0.1:8000/api/clients?page=2` + +## Réponses sécurité TP10 + +1. Dans un vrai CRM, les routes de consultation, création, modification et + suppression des clients devraient être protégées car elles manipulent des + données privées ou permettent de les modifier. +2. POST est sensible car un utilisateur non autorisé pourrait créer de fausses + données dans la base. +3. Cacher un bouton dans l'interface ne sécurise rien : quelqu'un peut appeler + directement l'URL de l'API. La vérification doit être faite côté serveur. +4. Une Policy doit être vérifiée dans le contrôleur avant l'opération sensible + (par exemple avant `update()` ou `delete()`), une fois l'utilisateur + authentifié. + +## Codes HTTP à connaître + +- 200 : requête réussie +- 201 : ressource créée +- 204 : suppression réussie sans contenu +- 404 : client inexistant +- 422 : données invalides +- 401 : utilisateur non authentifié +- 403 : utilisateur authentifié mais non autorisé diff --git a/mini-crm/mini-crm_TP9_TP10/TRACE_TP9_TP10.txt b/mini-crm/mini-crm_TP9_TP10/TRACE_TP9_TP10.txt new file mode 100644 index 0000000..cd53957 --- /dev/null +++ b/mini-crm/mini-crm_TP9_TP10/TRACE_TP9_TP10.txt @@ -0,0 +1,11 @@ +TP9 : +J'ai ajouté une API au Mini-CRM sans supprimer les routes web existantes. +J'ai créé les routes GET/POST, le contrôleur API, la validation et la recherche par email. + +TP10 : +J'ai structuré le JSON avec ClientResource, ajouté PATCH et DELETE, +géré l'unicité de l'email et ajouté la pagination à 10 clients par page. + +Important : +Les routes API sont publiques uniquement pour le cadre pédagogique du TP. +Dans un vrai CRM, elles devraient être protégées par authentification et autorisation. diff --git a/mini-crm/mini-crm_TP9_TP10/app/Http/Controllers/Api/ClientController.php b/mini-crm/mini-crm_TP9_TP10/app/Http/Controllers/Api/ClientController.php new file mode 100644 index 0000000..272914a --- /dev/null +++ b/mini-crm/mini-crm_TP9_TP10/app/Http/Controllers/Api/ClientController.php @@ -0,0 +1,108 @@ +validate([ + 'name' => ['required', 'string', 'max:255'], + 'email' => ['required', 'email', 'max:255', 'unique:clients,email'], + 'phone' => ['nullable', 'string', 'max:20'], + 'city' => ['nullable', 'string', 'max:100'], + ]); + + $client = Client::create($validated); + + // 201 = Created : la ressource vient d'être créée. + return (new ClientResource($client)) + ->response() + ->setStatusCode(201); + } + + /** + * TP9/TP10 - Grâce au route model binding, Laravel recherche + * automatiquement le Client correspondant à {client}. + * Si l'identifiant n'existe pas, Laravel renvoie une erreur 404. + */ + public function show(Client $client) + { + return new ClientResource($client); + } + + /** + * TP10 - PATCH permet de modifier seulement les champs envoyés. + * "sometimes" évite d'obliger l'utilisateur à renvoyer tout le client. + */ + public function update(Request $request, Client $client) + { + $validated = $request->validate([ + 'name' => ['sometimes', 'required', 'string', 'max:255'], + 'email' => [ + 'sometimes', + 'required', + 'email', + 'max:255', + Rule::unique('clients', 'email')->ignore($client->id), + ], + 'phone' => ['sometimes', 'nullable', 'string', 'max:20'], + 'city' => ['sometimes', 'nullable', 'string', 'max:100'], + ]); + + $client->update($validated); + + return new ClientResource($client); + } + + /** + * TP10 - Je supprime le client avec Eloquent. + * Une suppression réussie renvoie 204 No Content. + */ + public function destroy(Client $client) + { + $client->delete(); + + return response()->noContent(); + } + + /** + * TP9 - Défi autonome : rechercher un client grâce à son email. + */ + public function search(Request $request) + { + $validated = $request->validate([ + 'email' => ['required', 'email'], + ]); + + $client = Client::where('email', $validated['email'])->first(); + + if (! $client) { + return response()->json([ + 'message' => 'Aucun client ne correspond à cette adresse email.', + ], 404); + } + + return new ClientResource($client); + } +} diff --git a/mini-crm/mini-crm_TP9_TP10/app/Http/Resources/ClientResource.php b/mini-crm/mini-crm_TP9_TP10/app/Http/Resources/ClientResource.php new file mode 100644 index 0000000..4a79eb5 --- /dev/null +++ b/mini-crm/mini-crm_TP9_TP10/app/Http/Resources/ClientResource.php @@ -0,0 +1,24 @@ + $this->id, + 'name' => $this->name, + 'email' => $this->email, + 'phone' => $this->phone, + 'city' => $this->city, + ]; + } +} diff --git a/mini-crm/mini-crm_TP9_TP10/bootstrap/app.php b/mini-crm/mini-crm_TP9_TP10/bootstrap/app.php new file mode 100644 index 0000000..a31da00 --- /dev/null +++ b/mini-crm/mini-crm_TP9_TP10/bootstrap/app.php @@ -0,0 +1,28 @@ +withRouting( + web: __DIR__.'/../routes/web.php', + + // TP9 : j'ai ajouté le fichier api.php au routage Laravel. + // Laravel applique automatiquement le préfixe /api à ces routes. + api: __DIR__.'/../routes/api.php', + + commands: __DIR__.'/../routes/console.php', + health: '/up', + ) + ->withMiddleware(function (Middleware $middleware): void { + // + }) + ->withExceptions(function (Exceptions $exceptions): void { + // Je conserve le comportement déjà présent dans le projet : + // les erreurs de l'API doivent être retournées en JSON. + $exceptions->shouldRenderJsonWhen( + fn (Request $request) => $request->is('api/*') || $request->expectsJson(), + ); + })->create(); diff --git a/mini-crm/mini-crm_TP9_TP10/routes/api.php b/mini-crm/mini-crm_TP9_TP10/routes/api.php new file mode 100644 index 0000000..f83863f --- /dev/null +++ b/mini-crm/mini-crm_TP9_TP10/routes/api.php @@ -0,0 +1,31 @@ + 'Mon API fonctionne', + 'version' => 1, + ]; +}); + +// TP9 - défi : j'ai placé /search AVANT /{client} pour éviter que Laravel +// interprète le mot "search" comme l'identifiant d'un client. +Route::get('/clients/search', [ClientController::class, 'search']); + +// TP9 + TP10 : CRUD complet de l'API clients. +Route::get('/clients', [ClientController::class, 'index']); +Route::post('/clients', [ClientController::class, 'store']); +Route::get('/clients/{client}', [ClientController::class, 'show']); +Route::patch('/clients/{client}', [ClientController::class, 'update']); +Route::delete('/clients/{client}', [ClientController::class, 'destroy']);