Validations des données clients
Cette documentation décrit en détail toutes les règles de validation appliquées aux données des clients du programme de fidélité.
Vue d'ensemble
Les validations sont implémentées à l'aide de class-validator dans les DTOs (Data Transfer Objects). Elles s'appliquent automatiquement lors de la création et de la mise à jour des clients.
Localisation du code
- DTO de création:
backend/src/clients/dto/create-client.dto.ts - DTO de mise à jour:
backend/src/clients/dto/update-client.dto.ts - Validateur personnalisé:
backend/src/clients/validators/is-date-not-in-future.validator.ts
Champs obligatoires
nom (string)
Règles:
- ✅ Obligatoire
- ✅ Doit être une chaîne de caractères
Messages d'erreur:
"Le nom est obligatoire""Le nom doit être une chaîne de caractères"
Exemples:
✅ Valide
{ "nom": "Dupont" }
{ "nom": "Martin-Dubois" }
❌ Invalide
{ "nom": "" } // Vide
{ "nom": null } // Null
{ "nom": 123 } // Pas une chaîne
// nom manquant
prenom (string)
Règles:
- ✅ Obligatoire
- ✅ Doit être une chaîne de caractères
Messages d'erreur:
"Le prénom est obligatoire""Le prénom doit être une chaîne de caractères"
Exemples:
✅ Valide
{ "prenom": "Jean" }
{ "prenom": "Marie-Claire" }
❌ Invalide
{ "prenom": "" } // Vide
{ "prenom": null } // Null
// prenom manquant
adresseMail (string)
Règles:
- ✅ Obligatoire
- ✅ Format email valide
- ✅ Unique dans la base de données
Messages d'erreur:
"L'adresse mail est obligatoire""L'adresse mail n'est pas valide""Un client avec cette adresse mail existe déjà"(409 Conflict)
Exemples:
✅ Valide
{ "adresseMail": "jean.dupont@example.com" }
{ "adresseMail": "user+test@domain.fr" }
❌ Invalide
{ "adresseMail": "invalid-email" } // Format invalide
{ "adresseMail": "user@" } // Domaine manquant
{ "adresseMail": "existing@example.com" } // Déjà existant (409)
// adresseMail manquant
Champs optionnels
civilite (enum)
Règles:
- ⚪ Optionnel
- ✅ Si fourni, doit être:
"M","Mme"ou"Mx"
Message d'erreur:
"La civilité doit être une des valeurs suivantes: M, Mme, Mx (reçu: {value})"
Exemples:
✅ Valide
{ "civilite": "M" }
{ "civilite": "Mme" }
{ "civilite": "Mx" }
// civilite omis
❌ Invalide
{ "civilite": "Mr" } // Valeur non autorisée
{ "civilite": "Madame" } // Doit être "Mme"
{ "civilite": "F" } // Valeur non autorisée
anniversaire (date)
Règles:
- ⚪ Optionnel
- ✅ Format ISO 8601:
YYYY-MM-DD - ✅ Ne doit pas être dans le futur
Messages d'erreur:
"La date d'anniversaire doit être au format YYYY-MM-DD (ex: 1990-05-15)""La date d'anniversaire ne peut pas être dans le futur"
Exemples:
✅ Valide
{ "anniversaire": "1990-05-15" }
{ "anniversaire": "2000-12-31" }
// anniversaire omis
❌ Invalide
{ "anniversaire": "15/05/1990" } // Format incorrect
{ "anniversaire": "1990-5-15" } // Mois/jour sans zéro
{ "anniversaire": "2030-01-01" } // Dans le futur
{ "anniversaire": "invalid-date" } // Pas une date
Note: La validation anti-futur compare la date à minuit (00:00:00) du jour actuel.
telephone (string)
Règles:
- ⚪ Optionnel
- ✅ Si fourni, ne peut contenir que:
- Chiffres:
0-9 - Espaces
- Caractères:
+,-,(,),.
- Chiffres:
Message d'erreur:
"Le téléphone ne peut contenir que des chiffres, espaces et caractères +()-."
Exemples:
✅ Valide
{ "telephone": "0612345678" }
{ "telephone": "+33 6 12 34 56 78" }
{ "telephone": "01-23-45-67-89" }
{ "telephone": "(+33) 612345678" }
{ "telephone": "+33.6.12.34.56.78" }
// telephone omis
❌ Invalide
{ "telephone": "abc123" } // Lettres non autorisées
{ "telephone": "06#12345678" } // Caractère # non autorisé
{ "telephone": "string" } // Texte non numérique
adresse (string)
Règles:
- ⚪ Optionnel
- ✅ Doit être une chaîne de caractères
Message d'erreur:
"L'adresse doit être une chaîne de caractères"
Exemples:
✅ Valide
{ "adresse": "123 Rue de la Paix, 75001 Paris" }
{ "adresse": "45 Avenue des Champs-Élysées" }
{ "adresse": "Appartement 5B, 10 Boulevard Saint-Michel" }
// adresse omis
❌ Invalide
{ "adresse": 123 } // Doit être une chaîne
{ "adresse": null } // Null non autorisé (omettre le champ)
idClient (string)
Règles:
- ⚪ Optionnel
- ✅ Doit être une chaîne de caractères
Message d'erreur:
"L'identifiant client doit être une chaîne de caractères"
Exemples:
✅ Valide
{ "idClient": "CLI123456" }
{ "idClient": "ABC-XYZ-001" }
// idClient omis
niveauFidelisation (enum)
Règles:
- ⚪ Optionnel
- ✅ Si fourni, doit être:
"Standard","Premium"ou"Platine" - 🔄 Défaut:
"Standard"si non fourni
Message d'erreur:
"Le niveau de fidélisation doit être: Standard, Premium ou Platine (reçu: {value})"
Exemples:
✅ Valide
{ "niveauFidelisation": "Standard" }
{ "niveauFidelisation": "Premium" }
{ "niveauFidelisation": "Platine" }
// niveauFidelisation omis → défaut "Standard"
❌ Invalide
{ "niveauFidelisation": "Gold" } // Valeur non autorisée
{ "niveauFidelisation": "standard" } // Sensible à la casse
{ "niveauFidelisation": "VIP" } // Valeur non autorisée
Comportement par défaut:
Si niveauFidelisation n'est pas fourni lors de la création, il est automatiquement défini à "Standard".
pointsFidelite (number)
Règles:
- ⚪ Optionnel
- ✅ Doit être un nombre entier
- ✅ Doit être >= 0 (pas de valeurs négatives)
Messages d'erreur:
"Les points de fidélité doivent être un nombre entier""Les points de fidélité ne peuvent pas être négatifs"
Exemples:
✅ Valide
{ "pointsFidelite": 0 }
{ "pointsFidelite": 100 }
{ "pointsFidelite": 9999 }
// pointsFidelite omis
❌ Invalide
{ "pointsFidelite": -100 } // Négatif
{ "pointsFidelite": 12.5 } // Décimal
{ "pointsFidelite": "100" } // Chaîne au lieu de nombre
Protection importante: Cette validation empêche toute manipulation malveillante ou erreur qui créerait un solde de points négatif.
dateDebutFidelisation (date)
Règles:
- ⚪ Optionnel
- ✅ Format ISO 8601:
YYYY-MM-DD - 🔄 Auto-rempli: Si
niveauFidelisationest fourni mais pas cette date, elle est définie à la date du jour
Message d'erreur:
"La date de début de fidélisation doit être au format YYYY-MM-DD (ex: 2023-10-10)"
Exemples:
✅ Valide
{ "dateDebutFidelisation": "2023-10-10" }
{ "dateDebutFidelisation": "2024-01-15" }
// dateDebutFidelisation omis
❌ Invalide
{ "dateDebutFidelisation": "10/10/2023" } // Format incorrect
{ "dateDebutFidelisation": "string" } // Pas une date
{ "dateDebutFidelisation": "2023-10-1" } // Jour sans zéro
Comportement automatique:
// Si niveauFidelisation est fourni mais pas dateDebutFidelisation
{
"niveauFidelisation": "Premium"
// → dateDebutFidelisation sera automatiquement la date du jour
}
Validation personnalisée
IsDateNotInFuture
Fichier: backend/src/clients/validators/is-date-not-in-future.validator.ts
Cette validation personnalisée vérifie qu'une date n'est pas dans le futur. Elle est utilisée pour le champ anniversaire.
Implémentation:
@ValidatorConstraint({ async: false })
export class IsDateNotInFutureConstraint
implements ValidatorConstraintInterface
{
validate(value: any): boolean {
if (!value) {
return true; // Autorise null/undefined (géré par @IsOptional)
}
const inputDate = new Date(value);
const today = new Date();
today.setHours(0, 0, 0, 0); // Reset l'heure à minuit
return inputDate <= today;
}
defaultMessage(): string {
return "La date d'anniversaire ne peut pas être dans le futur";
}
}
Utilisation:
@IsOptional()
@IsDateString()
@IsDateNotInFuture()
anniversaire?: string;
Gestion des erreurs
Format de réponse d'erreur
Toutes les erreurs de validation retournent un statut 400 Bad Request avec le format suivant:
{
"message": ["Message d'erreur 1", "Message d'erreur 2"],
"error": "Bad Request",
"statusCode": 400
}
Types d'erreurs HTTP
| Code HTTP | Type | Cas d'usage | Exemple |
|---|---|---|---|
| 400 | Bad Request | Erreurs de validation des champs | Email invalide, nom manquant, etc. |
| 409 | Conflict | Email déjà existant | Tentative de créer un client avec un email existant |
| 404 | Not Found | Client non trouvé | Tentative d'accéder à un client inexistant |
Erreurs multiples
Si plusieurs validations échouent, tous les messages d'erreur sont retournés ensemble:
Requête:
{
"civilite": "Invalid",
"nom": "",
"prenom": "Test",
"adresseMail": "not-an-email",
"pointsFidelite": -50
}
Réponse (400):
{
"message": [
"La civilité doit être une des valeurs suivantes: M, Mme, Mx (reçu: Invalid)",
"Le nom est obligatoire",
"L'adresse mail n'est pas valide",
"Les points de fidélité ne peuvent pas être négatifs"
],
"error": "Bad Request",
"statusCode": 400
}
Gestion des erreurs côté frontend
Le frontend (ClientsPage.tsx) implémente une gestion complète des erreurs :
Erreur 409 (Email existant)
if (response.status === 409) {
setValidationErrors({
adresseMail: errorData.message || 'Cette adresse email est déjà utilisée',
});
return;
}
L'erreur est affichée directement sous le champ email.
Erreurs 400 (Validation)
Les erreurs de validation du backend sont automatiquement mappées aux champs correspondants :
if (response.status === 400) {
const newErrors = {};
// Parse les erreurs NestJS (tableau de messages)
if (Array.isArray(errorData.message)) {
errorData.message.forEach((msg: string) => {
const msgLower = msg.toLowerCase();
// Mapping automatique vers les champs
if (msgLower.includes('civilité')) {
newErrors.civilite = msg;
} else if (msgLower.includes('nom')) {
newErrors.nom = msg;
}
// ... autres champs
});
}
setValidationErrors(newErrors);
}
Champs supportés pour l'affichage d'erreurs :
civilite- Civilité invalidenom- Nom manquant ou invalideprenom- Prénom manquant ou invalideadresseMail- Email manquant, invalide ou existanttelephone- Téléphone avec caractères invalidesanniversaire- Date invalide ou dans le futurniveauFidelisation- Niveau de fidélisation invalidegeneral- Erreur non mappée à un champ spécifique
Validation frontend
En plus des validations backend, le frontend effectue des validations préventives :
Email :
function validateEmail(email: string): string | null {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!email) return "L'adresse email est requise";
if (!emailRegex.test(email)) return "Format d'email invalide";
return null;
}
Téléphone :
function validatePhone(phone: string | null): string | null {
if (!phone) return null; // Optionnel
const phoneRegex = /^0[1-9](\s?\d{2}){4}$/;
if (!phoneRegex.test(phone)) {
return 'Format de téléphone invalide (ex: 06 12 34 56 78)';
}
return null;
}
Date d'anniversaire :
function validateBirthday(birthday: string | null): string | null {
if (!birthday) return null; // Optionnel
const inputDate = new Date(birthday);
const today = new Date();
today.setHours(0, 0, 0, 0);
if (inputDate > today) {
return "La date d'anniversaire ne peut pas être dans le futur";
}
return null;
}
Ces validations s'exécutent avant l'envoi de la requête au backend, offrant un retour immédiat à l'utilisateur.
Tests de validation
Tester les validations
Pour tester les validations manuellement:
# Test: Points négatifs (devrait échouer)
curl -X POST 'http://localhost:3000/api/publique/clients' \
-H 'Content-Type: application/json' \
-d '{
"nom": "Test",
"prenom": "User",
"adresseMail": "test@example.com",
"pointsFidelite": -100
}'
# Test: Date invalide (devrait échouer)
curl -X POST 'http://localhost:3000/api/publique/clients' \
-H 'Content-Type: application/json' \
-d '{
"nom": "Test",
"prenom": "User",
"adresseMail": "test2@example.com",
"dateDebutFidelisation": "invalid-date"
}'
# Test: Données valides (devrait réussir)
curl -X POST 'http://localhost:3000/api/publique/clients' \
-H 'Content-Type: application/json' \
-d '{
"civilite": "M",
"nom": "Dupont",
"prenom": "Jean",
"anniversaire": "1990-05-15",
"adresseMail": "jean.dupont@example.com",
"telephone": "0612345678",
"pointsFidelite": 100
}'
Récapitulatif des règles
| Champ | Obligatoire | Type | Contraintes | Défaut |
|---|---|---|---|---|
nom | ✅ | String | Non vide | - |
prenom | ✅ | String | Non vide | - |
adresseMail | ✅ | String | Email valide, unique | - |
civilite | ⚪ | Enum | M, Mme, Mx | - |
anniversaire | ⚪ | Date | YYYY-MM-DD, pas futur | - |
telephone | ⚪ | String | Chiffres et +()-. | - |
idClient | ⚪ | String | - | - |
niveauFidelisation | ⚪ | Enum | Standard, Premium, Platine | "Standard" |
pointsFidelite | ⚪ | Number | Entier >= 0 | - |
dateDebutFidelisation | ⚪ | Date | YYYY-MM-DD | Date du jour si niveau fourni |
Légende:
- ✅ = Obligatoire
- ⚪ = Optionnel