Ajout du tp 9 et 10 pour l'actualisation du mini-crm

This commit is contained in:
2026-10-21 15:57:28 +02:00
parent ffe85d5f0d
commit 6c0056944c
7 changed files with 342 additions and 0 deletions
Binary file not shown.
@@ -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é
@@ -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.
@@ -0,0 +1,108 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Http\Resources\ClientResource;
use App\Models\Client;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
class ClientController extends Controller
{
/**
* TP10 - Je liste les clients avec une pagination de 10 éléments.
* J'utilise ClientResource pour contrôler les champs exposés par l'API.
*/
public function index()
{
return ClientResource::collection(Client::paginate(10));
}
/**
* TP9/TP10 - Je crée un client après avoir validé les données reçues.
* L'email est unique dans la migration du projet, donc je vérifie aussi
* cette contrainte ici afin d'obtenir une erreur HTTP 422 propre.
*/
public function store(Request $request)
{
$validated = $request->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);
}
}
@@ -0,0 +1,24 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class ClientResource extends JsonResource
{
/**
* TP10 - J'ai créé une Resource pour choisir précisément les données
* du modèle Client que je souhaite exposer dans les réponses JSON.
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'phone' => $this->phone,
'city' => $this->city,
];
}
}
@@ -0,0 +1,28 @@
<?php
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
use Illuminate\Http\Request;
return Application::configure(basePath: dirname(__DIR__))
->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();
+31
View File
@@ -0,0 +1,31 @@
<?php
use App\Http\Controllers\Api\ClientController;
use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
| TP9 / TP10 - Routes de l'API
|--------------------------------------------------------------------------
| J'ai séparé les routes API des routes web afin que l'application puisse
| continuer à utiliser Blade tout en proposant des données au format JSON.
*/
// TP9 : j'ai gardé cette route simple pour vérifier rapidement que l'API répond.
Route::get('/test', function () {
return [
'message' => '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']);