483 lines
15 KiB
Go
483 lines
15 KiB
Go
// ============================================
|
||
// handlers/profile_handlers.go - VERSION CORRIGÉE
|
||
// ============================================
|
||
// Gestion des modifications de profils
|
||
|
||
package handlers
|
||
|
||
import (
|
||
"log"
|
||
"net/http"
|
||
"strconv"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
"golang.org/x/crypto/bcrypt"
|
||
|
||
"gestion/db"
|
||
"gestion/models"
|
||
"gestion/utils"
|
||
)
|
||
|
||
// ============================================
|
||
// MODIFICATION PROFIL CLIENT (PAR LE CLIENT)
|
||
// ============================================
|
||
|
||
// UpdateMyProfile permet à un client de modifier son propre profil
|
||
// PUT /api/v1/profile/update
|
||
func UpdateMyProfile(c *gin.Context) {
|
||
clientID := c.GetInt("client_id")
|
||
database := c.MustGet("database").(*db.Database)
|
||
|
||
var req models.UpdateClientProfileRequest
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
log.Printf("❌ [UPDATE_MY_PROFILE] Erreur binding: %v", err)
|
||
c.JSON(http.StatusBadRequest, gin.H{
|
||
"error": "Données invalides",
|
||
})
|
||
return
|
||
}
|
||
|
||
// Récupérer le client actuel
|
||
client, err := database.GetClientByID(clientID)
|
||
if err != nil {
|
||
log.Printf("❌ [UPDATE_MY_PROFILE] Client non trouvé: ID=%d", clientID)
|
||
c.JSON(http.StatusNotFound, gin.H{"error": "Client non trouvé"})
|
||
return
|
||
}
|
||
|
||
// Vérifier si des modifications sont demandées
|
||
hasChanges := false
|
||
|
||
// Mise à jour du username
|
||
if req.Username != "" {
|
||
req.Username = utils.StripHTML(req.Username)
|
||
}
|
||
if req.Username != "" && req.Username != client.Username {
|
||
// Vérifier que le nouveau username n'existe pas (clients et users)
|
||
if existingClient, _ := database.GetClientByUsername(req.Username); existingClient != nil {
|
||
log.Printf("❌ [UPDATE_MY_PROFILE] Username déjà utilisé: %s", req.Username)
|
||
c.JSON(http.StatusConflict, gin.H{"error": "Nom d'utilisateur déjà utilisé"})
|
||
return
|
||
}
|
||
if existingUser, _ := database.GetUserByUsername(req.Username); existingUser != nil {
|
||
log.Printf("❌ [UPDATE_MY_PROFILE] Username réservé: %s", req.Username)
|
||
c.JSON(http.StatusConflict, gin.H{"error": "Nom d'utilisateur déjà utilisé"})
|
||
return
|
||
}
|
||
client.Username = req.Username
|
||
hasChanges = true
|
||
}
|
||
|
||
// Mise à jour du mot de passe
|
||
if req.Password != "" {
|
||
if len(req.Password) < 8 {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "Le mot de passe doit contenir au moins 8 caractères"})
|
||
return
|
||
}
|
||
hashed, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
|
||
if err != nil {
|
||
log.Printf("❌ [UPDATE_MY_PROFILE] Erreur bcrypt: %v", err)
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur traitement mot de passe"})
|
||
return
|
||
}
|
||
client.Password = string(hashed)
|
||
hasChanges = true
|
||
}
|
||
|
||
// Mise à jour du nom
|
||
if req.Nom != "" {
|
||
req.Nom = utils.StripHTML(req.Nom)
|
||
}
|
||
if req.Nom != "" && req.Nom != client.Nom {
|
||
if len(req.Nom) < 2 {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "Le nom doit contenir au moins 2 caractères"})
|
||
return
|
||
}
|
||
client.Nom = req.Nom
|
||
hasChanges = true
|
||
}
|
||
|
||
// Mise à jour du prénom
|
||
if req.Prenom != "" {
|
||
req.Prenom = utils.StripHTML(req.Prenom)
|
||
}
|
||
if req.Prenom != "" && req.Prenom != client.Prenom {
|
||
if len(req.Prenom) < 2 {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "Le prénom doit contenir au moins 2 caractères"})
|
||
return
|
||
}
|
||
client.Prenom = req.Prenom
|
||
hasChanges = true
|
||
}
|
||
|
||
// Mise à jour du téléphone
|
||
if req.Telephone != "" && req.Telephone != client.Telephone {
|
||
if !utils.ValidatePhoneNumber(req.Telephone) {
|
||
log.Printf("❌ [UPDATE_MY_PROFILE] Téléphone invalide: %s", req.Telephone)
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "Numéro de téléphone invalide"})
|
||
return
|
||
}
|
||
normalizedPhone := utils.NormalizePhoneNumber(req.Telephone)
|
||
|
||
// Vérifier que le téléphone n'est pas déjà utilisé
|
||
if existingClient, _ := database.GetClientByTelephone(normalizedPhone); existingClient != nil && existingClient.ID != clientID {
|
||
log.Printf("❌ [UPDATE_MY_PROFILE] Téléphone déjà utilisé: %s", normalizedPhone)
|
||
c.JSON(http.StatusConflict, gin.H{"error": "Ce numéro de téléphone est déjà utilisé"})
|
||
return
|
||
}
|
||
client.Telephone = normalizedPhone
|
||
hasChanges = true
|
||
}
|
||
|
||
if !hasChanges {
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"message": "Aucune modification détectée",
|
||
"client": sanitizeClient(client),
|
||
})
|
||
return
|
||
}
|
||
|
||
// Sauvegarder les modifications
|
||
if err := database.UpdateClient(client); err != nil {
|
||
log.Printf("❌ [UPDATE_MY_PROFILE] Erreur mise à jour: %v", err)
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur lors de la mise à jour"})
|
||
return
|
||
}
|
||
|
||
log.Printf("✅ [UPDATE_MY_PROFILE] Profil mis à jour: %s (ID=%d)", client.Username, client.ID)
|
||
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"message": "Profil mis à jour avec succès",
|
||
"client": sanitizeClient(client),
|
||
})
|
||
}
|
||
|
||
// GetMyProfile retourne le profil du client connecté
|
||
// GET /api/v1/profile
|
||
func GetMyProfile(c *gin.Context) {
|
||
database := c.MustGet("database").(*db.Database)
|
||
username, exists := c.Get("username")
|
||
if !exists {
|
||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Non authentifié"})
|
||
return
|
||
}
|
||
client, err := database.GetClientByUsername(username.(string))
|
||
if err != nil || client == nil {
|
||
c.JSON(http.StatusNotFound, gin.H{"error": "Client non trouvé"})
|
||
return
|
||
}
|
||
c.JSON(http.StatusOK, gin.H{"success": true, "client": sanitizeClient(client)})
|
||
}
|
||
|
||
// UpdateClientByAdmin permet à un admin de modifier n'importe quel profil client
|
||
// PUT /api/v2/admin/protected/clients/:id
|
||
func UpdateClientByAdmin(c *gin.Context) {
|
||
database := c.MustGet("database").(*db.Database)
|
||
role, exists := c.Get("role")
|
||
if !exists || role != "admin" {
|
||
c.JSON(http.StatusForbidden, gin.H{
|
||
"error": "Accès réservé aux administrateurs",
|
||
})
|
||
return
|
||
}
|
||
|
||
// Récupérer l'ID du client à modifier
|
||
clientIDStr := c.Param("id")
|
||
clientID, err := strconv.Atoi(clientIDStr)
|
||
if err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "ID client invalide"})
|
||
return
|
||
}
|
||
|
||
var req models.AdminUpdateClientRequest
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
log.Printf("❌ [UPDATE_CLIENT_ADMIN] Erreur binding: %v", err)
|
||
c.JSON(http.StatusBadRequest, gin.H{
|
||
"error": "Données invalides",
|
||
})
|
||
return
|
||
}
|
||
|
||
// ✅ LOG DEBUG - Voir ce qui est reçu
|
||
log.Printf("📝 [UPDATE_CLIENT_ADMIN] Requête reçue: %+v", req)
|
||
|
||
// Récupérer le client actuel
|
||
client, err := database.GetClientByID(clientID)
|
||
if err != nil {
|
||
log.Printf("❌ [UPDATE_CLIENT_ADMIN] Client non trouvé: ID=%d", clientID)
|
||
c.JSON(http.StatusNotFound, gin.H{"error": "Client non trouvé"})
|
||
return
|
||
}
|
||
|
||
// ✅ LOG DEBUG - État initial
|
||
log.Printf("📊 [UPDATE_CLIENT_ADMIN] État initial - Command: %d, Amende: %.2f",
|
||
client.Command, client.Amende)
|
||
|
||
// Vérifier si des modifications sont demandées
|
||
hasChanges := false
|
||
|
||
// Mise à jour du username
|
||
if req.Username != "" {
|
||
req.Username = utils.StripHTML(req.Username)
|
||
}
|
||
if req.Username != "" && req.Username != client.Username {
|
||
// Vérifier que le nouveau username n'existe pas
|
||
if existingClient, _ := database.GetClientByUsername(req.Username); existingClient != nil {
|
||
log.Printf("❌ [UPDATE_CLIENT_ADMIN] Username déjà utilisé: %s", req.Username)
|
||
c.JSON(http.StatusConflict, gin.H{"error": "Nom d'utilisateur déjà utilisé"})
|
||
return
|
||
}
|
||
client.Username = req.Username
|
||
hasChanges = true
|
||
log.Printf("✏️ [UPDATE_CLIENT_ADMIN] Username modifié: %s", req.Username)
|
||
}
|
||
|
||
// Mise à jour du mot de passe (opération séparée pour garantir l'écriture)
|
||
var newHashedPassword string
|
||
if req.Password != "" {
|
||
if len(req.Password) < 8 {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "Le mot de passe doit contenir au moins 8 caractères"})
|
||
return
|
||
}
|
||
hashed, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
|
||
if err != nil {
|
||
log.Printf("❌ [UPDATE_CLIENT_ADMIN] Erreur bcrypt: %v", err)
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur traitement mot de passe"})
|
||
return
|
||
}
|
||
newHashedPassword = string(hashed)
|
||
hasChanges = true
|
||
log.Printf("✏️ [UPDATE_CLIENT_ADMIN] Mot de passe modifié")
|
||
}
|
||
|
||
// Mise à jour du nom
|
||
if req.Nom != "" {
|
||
req.Nom = utils.StripHTML(req.Nom)
|
||
}
|
||
if req.Nom != "" && req.Nom != client.Nom {
|
||
client.Nom = req.Nom
|
||
hasChanges = true
|
||
log.Printf("✏️ [UPDATE_CLIENT_ADMIN] Nom modifié: %s", req.Nom)
|
||
}
|
||
|
||
// Mise à jour du prénom
|
||
if req.Prenom != "" {
|
||
req.Prenom = utils.StripHTML(req.Prenom)
|
||
}
|
||
if req.Prenom != "" && req.Prenom != client.Prenom {
|
||
client.Prenom = req.Prenom
|
||
hasChanges = true
|
||
log.Printf("✏️ [UPDATE_CLIENT_ADMIN] Prénom modifié: %s", req.Prenom)
|
||
}
|
||
|
||
// Mise à jour du téléphone
|
||
if req.Telephone != "" && req.Telephone != client.Telephone {
|
||
if !utils.ValidatePhoneNumber(req.Telephone) {
|
||
log.Printf("❌ [UPDATE_CLIENT_ADMIN] Téléphone invalide: %s", req.Telephone)
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "Numéro de téléphone invalide"})
|
||
return
|
||
}
|
||
normalizedPhone := utils.NormalizePhoneNumber(req.Telephone)
|
||
|
||
// Vérifier que le téléphone n'est pas déjà utilisé
|
||
if existingClient, _ := database.GetClientByTelephone(normalizedPhone); existingClient != nil && existingClient.ID != clientID {
|
||
log.Printf("❌ [UPDATE_CLIENT_ADMIN] Téléphone déjà utilisé: %s", normalizedPhone)
|
||
c.JSON(http.StatusConflict, gin.H{"error": "Ce numéro de téléphone est déjà utilisé"})
|
||
return
|
||
}
|
||
client.Telephone = normalizedPhone
|
||
hasChanges = true
|
||
log.Printf("✏️ [UPDATE_CLIENT_ADMIN] Téléphone modifié: %s", normalizedPhone)
|
||
}
|
||
|
||
if req.Command != nil && *req.Command != client.Command {
|
||
client.Command = *req.Command
|
||
hasChanges = true
|
||
log.Printf("✏️ [UPDATE_CLIENT_ADMIN] Commandes modifiées: %d → %d", client.Command, *req.Command)
|
||
}
|
||
|
||
if req.Amende != nil {
|
||
if *req.Amende < 0 {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "L'amende ne peut pas être négative"})
|
||
return
|
||
}
|
||
if *req.Amende != client.Amende {
|
||
client.Amende = *req.Amende
|
||
hasChanges = true
|
||
log.Printf("✏️ [UPDATE_CLIENT_ADMIN] Amendes modifiées: %.2f → %.2f", client.Amende, *req.Amende)
|
||
}
|
||
}
|
||
|
||
if !hasChanges {
|
||
log.Printf("ℹ️ [UPDATE_CLIENT_ADMIN] Aucune modification détectée pour client ID=%d", clientID)
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"message": "Aucune modification détectée",
|
||
"client": sanitizeClient(client),
|
||
})
|
||
return
|
||
}
|
||
|
||
log.Printf("📊 [UPDATE_CLIENT_ADMIN] État avant save - Command: %d, Amende: %.2f",
|
||
client.Command, client.Amende)
|
||
|
||
// Sauvegarder les modifications de profil (hors mot de passe)
|
||
if err := database.UpdateClient(client); err != nil {
|
||
log.Printf("❌ [UPDATE_CLIENT_ADMIN] Erreur mise à jour profil: %v", err)
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur lors de la mise à jour"})
|
||
return
|
||
}
|
||
|
||
// Mettre à jour le mot de passe séparément si demandé
|
||
if newHashedPassword != "" {
|
||
if err := database.UpdateClientPasswordAndClearFlag(clientID, newHashedPassword); err != nil {
|
||
log.Printf("❌ [UPDATE_CLIENT_ADMIN] Erreur mise à jour mot de passe: %v", err)
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur lors de la mise à jour du mot de passe"})
|
||
return
|
||
}
|
||
log.Printf("✅ [UPDATE_CLIENT_ADMIN] Mot de passe mis à jour pour client ID=%d", clientID)
|
||
}
|
||
|
||
log.Printf("✅ [UPDATE_CLIENT_ADMIN] Client mis à jour par admin: %s (ID=%d)", client.Username, client.ID)
|
||
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"message": "Client mis à jour avec succès",
|
||
"client": sanitizeClient(client),
|
||
})
|
||
}
|
||
|
||
// UpdateUserByAdmin permet à un admin de modifier n'importe quel profil user
|
||
// PUT /api/v2/admin/protected/users/:id
|
||
func UpdateUserByAdmin(c *gin.Context) {
|
||
database := c.MustGet("database").(*db.Database)
|
||
if c.GetString("role") != "admin" {
|
||
c.JSON(http.StatusForbidden, gin.H{
|
||
"error": "Accès réservé aux administrateurs",
|
||
})
|
||
return
|
||
}
|
||
// Récupérer l'ID de l'utilisateur à modifier
|
||
userIDStr := c.Param("id")
|
||
userID, err := strconv.Atoi(userIDStr)
|
||
if err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "ID utilisateur invalide"})
|
||
return
|
||
}
|
||
|
||
var req models.UpdateUserProfileRequest
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
log.Printf("❌ [UPDATE_USER_ADMIN] Erreur binding: %v", err)
|
||
c.JSON(http.StatusBadRequest, gin.H{
|
||
"error": "Données invalides",
|
||
})
|
||
return
|
||
}
|
||
|
||
// Récupérer l'utilisateur actuel
|
||
user, err := database.GetUserByID(userID)
|
||
if err != nil {
|
||
log.Printf("❌ [UPDATE_USER_ADMIN] User non trouvé: ID=%d", userID)
|
||
c.JSON(http.StatusNotFound, gin.H{"error": "Utilisateur non trouvé"})
|
||
return
|
||
}
|
||
|
||
// Vérifier si des modifications sont demandées
|
||
hasChanges := false
|
||
|
||
// Mise à jour du username
|
||
if req.Username != "" {
|
||
req.Username = utils.StripHTML(req.Username)
|
||
}
|
||
if req.Username != "" && req.Username != user.Username {
|
||
// Vérifier que le nouveau username n'existe pas (users et clients)
|
||
if existingUser, _ := database.GetUserByUsername(req.Username); existingUser != nil {
|
||
log.Printf("❌ [UPDATE_USER_ADMIN] Username déjà utilisé: %s", req.Username)
|
||
c.JSON(http.StatusConflict, gin.H{"error": "Nom d'utilisateur déjà utilisé"})
|
||
return
|
||
}
|
||
if existingClient, _ := database.GetClientByUsername(req.Username); existingClient != nil {
|
||
log.Printf("❌ [UPDATE_USER_ADMIN] Username réservé: %s", req.Username)
|
||
c.JSON(http.StatusConflict, gin.H{"error": "Nom d'utilisateur déjà utilisé"})
|
||
return
|
||
}
|
||
user.Username = req.Username
|
||
hasChanges = true
|
||
}
|
||
|
||
// Mise à jour du mot de passe
|
||
if req.Password != "" {
|
||
if len(req.Password) < 8 {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "Le mot de passe doit contenir au moins 8 caractères"})
|
||
return
|
||
}
|
||
hashed, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
|
||
if err != nil {
|
||
log.Printf("❌ [UPDATE_USER_ADMIN] Erreur bcrypt: %v", err)
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur traitement mot de passe"})
|
||
return
|
||
}
|
||
user.Password = string(hashed)
|
||
hasChanges = true
|
||
}
|
||
|
||
// Mise à jour du rôle
|
||
if req.Role != "" && req.Role != user.Role {
|
||
// Valider le rôle
|
||
validRoles := map[string]bool{
|
||
"admin": true,
|
||
"cabine": true,
|
||
"livreur": true,
|
||
}
|
||
if !validRoles[req.Role] {
|
||
c.JSON(http.StatusBadRequest, gin.H{
|
||
"error": "Rôle invalide. Valeurs acceptées: admin, cabine, livreur",
|
||
})
|
||
return
|
||
}
|
||
user.Role = req.Role
|
||
hasChanges = true
|
||
}
|
||
|
||
if !hasChanges {
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"message": "Aucune modification détectée",
|
||
"user": sanitizeUser(user),
|
||
})
|
||
return
|
||
}
|
||
|
||
// Sauvegarder les modifications
|
||
if err := database.UpdateUser(user); err != nil {
|
||
log.Printf("❌ [UPDATE_USER_ADMIN] Erreur mise à jour: %v", err)
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur lors de la mise à jour"})
|
||
return
|
||
}
|
||
|
||
log.Printf("✅ [UPDATE_USER_ADMIN] User mis à jour par admin: %s (ID=%d, Role=%s)", user.Username, user.ID, user.Role)
|
||
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"message": "Utilisateur mis à jour avec succès",
|
||
"user": sanitizeUser(user),
|
||
})
|
||
}
|
||
|
||
func sanitizeClient(client *models.Client) gin.H {
|
||
return gin.H{
|
||
"id": client.ID,
|
||
"username": client.Username,
|
||
"nom": client.Nom,
|
||
"prenom": client.Prenom,
|
||
"telephone": client.Telephone,
|
||
"command": client.Command,
|
||
"points_extra": client.PointsExtra,
|
||
"amende": client.Amende,
|
||
}
|
||
}
|
||
|
||
func sanitizeUser(user *models.User) gin.H {
|
||
return gin.H{
|
||
"id": user.ID,
|
||
"username": user.Username,
|
||
"role": user.Role,
|
||
}
|
||
}
|