Ajout du tp 9 et 10 pour l'actualisation du mini-crm
This commit is contained in:
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();
|
||||||
@@ -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']);
|
||||||
Reference in New Issue
Block a user