feat: Livraison version 3.0.6
- Amélioration de la gestion des entités et des utilisateurs - Mise à jour des modèles Amicale et Client avec champs supplémentaires - Ajout du service de logging et amélioration du chargement UI - Refactoring des formulaires utilisateur et amicale - Intégration de file_picker et image_picker pour la gestion des fichiers - Amélioration de la gestion des membres et de leur suppression - Optimisation des performances de l'API - Mise à jour de la documentation technique 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -227,6 +227,26 @@ class UserController {
|
||||
$role = isset($data['role']) ? (int)$data['role'] : 1;
|
||||
$entiteId = isset($data['fk_entite']) ? (int)$data['fk_entite'] : 1;
|
||||
|
||||
// Récupérer les paramètres de gestion de l'entité
|
||||
$entiteStmt = $this->db->prepare('
|
||||
SELECT chk_mdp_manuel, chk_username_manuel, code_postal, ville
|
||||
FROM entites
|
||||
WHERE id = ?
|
||||
');
|
||||
$entiteStmt->execute([$entiteId]);
|
||||
$entiteConfig = $entiteStmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$entiteConfig) {
|
||||
Response::json([
|
||||
'status' => 'error',
|
||||
'message' => 'Entité non trouvée'
|
||||
], 404);
|
||||
return;
|
||||
}
|
||||
|
||||
$chkMdpManuel = (int)$entiteConfig['chk_mdp_manuel'];
|
||||
$chkUsernameManuel = (int)$entiteConfig['chk_username_manuel'];
|
||||
|
||||
// Vérification des longueurs d'entrée
|
||||
if (strlen($email) > 75 || strlen($name) > 50) {
|
||||
Response::json([
|
||||
@@ -260,9 +280,83 @@ class UserController {
|
||||
return;
|
||||
}
|
||||
|
||||
// Génération du mot de passe
|
||||
$password = ApiService::generateSecurePassword();
|
||||
$passwordHash = password_hash($password, PASSWORD_DEFAULT);
|
||||
// Gestion du USERNAME selon chk_username_manuel
|
||||
$encryptedUsername = '';
|
||||
if ($chkUsernameManuel === 1) {
|
||||
// Username manuel obligatoire
|
||||
if (!isset($data['username']) || empty(trim($data['username']))) {
|
||||
Response::json([
|
||||
'status' => 'error',
|
||||
'message' => 'Le nom d\'utilisateur est requis pour cette entité'
|
||||
], 400);
|
||||
return;
|
||||
}
|
||||
|
||||
$username = trim(strtolower($data['username']));
|
||||
|
||||
// Validation du format du username
|
||||
if (!preg_match('/^[a-z][a-z0-9._-]{9,29}$/', $username)) {
|
||||
Response::json([
|
||||
'status' => 'error',
|
||||
'message' => 'Format du nom d\'utilisateur invalide (10-30 caractères, commence par une lettre, caractères autorisés: a-z, 0-9, ., -, _)'
|
||||
], 400);
|
||||
return;
|
||||
}
|
||||
|
||||
$encryptedUsername = ApiService::encryptSearchableData($username);
|
||||
|
||||
// Vérification de l'unicité du username
|
||||
$checkUsernameStmt = $this->db->prepare('SELECT id FROM users WHERE encrypted_user_name = ?');
|
||||
$checkUsernameStmt->execute([$encryptedUsername]);
|
||||
if ($checkUsernameStmt->fetch()) {
|
||||
Response::json([
|
||||
'status' => 'error',
|
||||
'message' => 'Ce nom d\'utilisateur est déjà utilisé dans GeoSector'
|
||||
], 409);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Génération automatique du username
|
||||
$username = ApiService::generateUserName(
|
||||
$this->db,
|
||||
$name,
|
||||
$entiteConfig['code_postal'] ?? '00000',
|
||||
$entiteConfig['ville'] ?? 'ville',
|
||||
10
|
||||
);
|
||||
$encryptedUsername = ApiService::encryptSearchableData($username);
|
||||
}
|
||||
|
||||
// Gestion du MOT DE PASSE selon chk_mdp_manuel
|
||||
$password = '';
|
||||
$passwordHash = '';
|
||||
if ($chkMdpManuel === 1) {
|
||||
// Mot de passe manuel obligatoire
|
||||
if (!isset($data['password']) || empty($data['password'])) {
|
||||
Response::json([
|
||||
'status' => 'error',
|
||||
'message' => 'Le mot de passe est requis pour cette entité'
|
||||
], 400);
|
||||
return;
|
||||
}
|
||||
|
||||
$password = $data['password'];
|
||||
|
||||
// Validation du mot de passe (minimum 8 caractères)
|
||||
if (strlen($password) < 8) {
|
||||
Response::json([
|
||||
'status' => 'error',
|
||||
'message' => 'Le mot de passe doit contenir au moins 8 caractères'
|
||||
], 400);
|
||||
return;
|
||||
}
|
||||
|
||||
$passwordHash = password_hash($password, PASSWORD_DEFAULT);
|
||||
} else {
|
||||
// Génération automatique du mot de passe
|
||||
$password = ApiService::generateSecurePassword();
|
||||
$passwordHash = password_hash($password, PASSWORD_DEFAULT);
|
||||
}
|
||||
|
||||
// Préparation des champs optionnels
|
||||
$phone = isset($data['phone']) ? ApiService::encryptData(trim($data['phone'])) : null;
|
||||
@@ -276,13 +370,13 @@ class UserController {
|
||||
// Insertion en base de données
|
||||
$stmt = $this->db->prepare('
|
||||
INSERT INTO users (
|
||||
encrypted_email, user_pass_hash, encrypted_name, first_name,
|
||||
encrypted_email, encrypted_user_name, user_pass_hash, encrypted_name, first_name,
|
||||
sect_name, encrypted_phone, encrypted_mobile, fk_role,
|
||||
fk_entite, chk_alert_email, chk_suivi,
|
||||
date_naissance, date_embauche,
|
||||
created_at, fk_user_creat, chk_active
|
||||
) VALUES (
|
||||
?, ?, ?, ?,
|
||||
?, ?, ?, ?, ?,
|
||||
?, ?, ?, ?,
|
||||
?, ?, ?,
|
||||
?, ?,
|
||||
@@ -291,6 +385,7 @@ class UserController {
|
||||
');
|
||||
$stmt->execute([
|
||||
$encryptedEmail,
|
||||
$encryptedUsername,
|
||||
$passwordHash,
|
||||
$encryptedName,
|
||||
$firstName,
|
||||
@@ -307,21 +402,54 @@ class UserController {
|
||||
]);
|
||||
$userId = $this->db->lastInsertId();
|
||||
|
||||
// Envoi de l'email avec les identifiants
|
||||
ApiService::sendEmail($email, $name, 'welcome', ['password' => $password]);
|
||||
// Envoi des emails séparés pour plus de sécurité
|
||||
|
||||
// 1er email : TOUJOURS envoyer l'identifiant (username)
|
||||
$usernameEmailData = [
|
||||
'email' => $email,
|
||||
'username' => $username,
|
||||
'name' => $name
|
||||
];
|
||||
ApiService::sendEmail($email, $name, 'welcome_username', $usernameEmailData);
|
||||
|
||||
// 2ème email : Envoyer le mot de passe (toujours, qu'il soit manuel ou généré)
|
||||
// Attendre un peu entre les deux emails pour éviter qu'ils arrivent dans le mauvais ordre
|
||||
sleep(1);
|
||||
|
||||
$passwordEmailData = [
|
||||
'email' => $email,
|
||||
'password' => $password,
|
||||
'name' => $name
|
||||
];
|
||||
ApiService::sendEmail($email, $name, 'welcome_password', $passwordEmailData);
|
||||
|
||||
LogService::log('Utilisateur GeoSector créé', [
|
||||
'level' => 'info',
|
||||
'createdBy' => $currentUserId,
|
||||
'newUserId' => $userId,
|
||||
'email' => $email
|
||||
'email' => $email,
|
||||
'username' => $username,
|
||||
'usernameManual' => $chkUsernameManuel === 1 ? 'oui' : 'non',
|
||||
'passwordManual' => $chkMdpManuel === 1 ? 'oui' : 'non',
|
||||
'emailsSent' => '2 emails (username + password)'
|
||||
]);
|
||||
|
||||
Response::json([
|
||||
// Préparer la réponse avec les informations de connexion si générées automatiquement
|
||||
$responseData = [
|
||||
'status' => 'success',
|
||||
'message' => 'Utilisateur créé avec succès',
|
||||
'id' => $userId
|
||||
], 201);
|
||||
];
|
||||
|
||||
// Ajouter le username dans la réponse (toujours, car nécessaire pour la connexion)
|
||||
$responseData['username'] = $username;
|
||||
|
||||
// Ajouter le mot de passe seulement si généré automatiquement
|
||||
if ($chkMdpManuel === 0) {
|
||||
$responseData['password'] = $password;
|
||||
}
|
||||
|
||||
Response::json($responseData, 201);
|
||||
} catch (PDOException $e) {
|
||||
LogService::log('Erreur lors de la création d\'un utilisateur GeoSector', [
|
||||
'level' => 'error',
|
||||
@@ -756,6 +884,106 @@ class UserController {
|
||||
}
|
||||
}
|
||||
|
||||
public function checkUsername(): void {
|
||||
Session::requireAuth();
|
||||
|
||||
try {
|
||||
$data = Request::getJson();
|
||||
|
||||
// Validation de la présence du username
|
||||
if (!isset($data['username']) || empty(trim($data['username']))) {
|
||||
Response::json([
|
||||
'status' => 'error',
|
||||
'message' => 'Username requis pour la vérification'
|
||||
], 400);
|
||||
return;
|
||||
}
|
||||
|
||||
$username = trim(strtolower($data['username']));
|
||||
|
||||
// Validation du format du username
|
||||
if (!preg_match('/^[a-z][a-z0-9._-]{9,29}$/', $username)) {
|
||||
Response::json([
|
||||
'status' => 'error',
|
||||
'message' => 'Format invalide : 10-30 caractères, commence par une lettre, caractères autorisés: a-z, 0-9, ., -, _',
|
||||
'available' => false
|
||||
], 400);
|
||||
return;
|
||||
}
|
||||
|
||||
// Chiffrement du username pour la recherche
|
||||
$encryptedUsername = ApiService::encryptSearchableData($username);
|
||||
|
||||
// Vérification de l'existence dans la base
|
||||
$stmt = $this->db->prepare('
|
||||
SELECT id, encrypted_name, fk_entite
|
||||
FROM users
|
||||
WHERE encrypted_user_name = ?
|
||||
LIMIT 1
|
||||
');
|
||||
$stmt->execute([$encryptedUsername]);
|
||||
$existingUser = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($existingUser) {
|
||||
// Username déjà pris - générer des suggestions
|
||||
$baseName = substr($username, 0, -2); // Enlever les 2 derniers caractères
|
||||
$suggestions = [];
|
||||
|
||||
// Génération de 3 suggestions
|
||||
$suggestions[] = $username . '_' . rand(10, 99);
|
||||
$suggestions[] = $baseName . rand(100, 999);
|
||||
|
||||
// Suggestion avec l'année courante
|
||||
$year = date('y');
|
||||
$suggestions[] = $username . $year;
|
||||
|
||||
// Vérifier que les suggestions sont aussi disponibles
|
||||
$availableSuggestions = [];
|
||||
foreach ($suggestions as $suggestion) {
|
||||
$encryptedSuggestion = ApiService::encryptSearchableData($suggestion);
|
||||
$checkStmt = $this->db->prepare('SELECT id FROM users WHERE encrypted_user_name = ?');
|
||||
$checkStmt->execute([$encryptedSuggestion]);
|
||||
if (!$checkStmt->fetch()) {
|
||||
$availableSuggestions[] = $suggestion;
|
||||
}
|
||||
}
|
||||
|
||||
// Si aucune suggestion n'est disponible, en générer d'autres
|
||||
if (empty($availableSuggestions)) {
|
||||
for ($i = 0; $i < 3; $i++) {
|
||||
$randomSuffix = rand(1000, 9999);
|
||||
$availableSuggestions[] = $baseName . $randomSuffix;
|
||||
}
|
||||
}
|
||||
|
||||
Response::json([
|
||||
'status' => 'success',
|
||||
'available' => false,
|
||||
'message' => 'Ce nom d\'utilisateur est déjà utilisé',
|
||||
'suggestions' => array_slice($availableSuggestions, 0, 3)
|
||||
]);
|
||||
} else {
|
||||
// Username disponible
|
||||
Response::json([
|
||||
'status' => 'success',
|
||||
'available' => true,
|
||||
'message' => 'Nom d\'utilisateur disponible',
|
||||
'username' => $username
|
||||
]);
|
||||
}
|
||||
|
||||
} catch (PDOException $e) {
|
||||
LogService::log('Erreur lors de la vérification du username', [
|
||||
'level' => 'error',
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
Response::json([
|
||||
'status' => 'error',
|
||||
'message' => 'Erreur serveur lors de la vérification'
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
// Méthodes auxiliaires
|
||||
private function validateUpdateData(array $data): ?string {
|
||||
// Validation de l'email
|
||||
|
||||
Reference in New Issue
Block a user