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