Files
Xor290 22a8d5026c
Frontend Admin - EAS Build / build (push) Canceled after 0s
Frontend Client - EAS Build / build (push) Canceled after 0s
Backend - Build & Lint / build (push) Failing after 25m18s
Frontend Web - Build & Lint / build (push) Failing after 9m58s
chore: build
2026-08-06 12:06:05 +02:00

480 lines
15 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ============================================
// 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
}
// 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,
}
}