package handlers import ( "crypto/rand" "encoding/hex" "gestion/db" "gestion/models" "log" "net/http" "os" "regexp" "strconv" "strings" "time" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" "github.com/google/uuid" "golang.org/x/crypto/bcrypt" ) // ============================================ // TYPES JWT CLAIMS (Duplicés dans middleware aussi) // ============================================ type ClientClaims struct { ClientID int `json:"client_id"` Username string `json:"username"` Role string `json:"role"` SessionID string `json:"session_id"` jwt.RegisteredClaims } type AdminClaims struct { UserID int `json:"user_id"` Username string `json:"username"` Role string `json:"role"` SessionID string `json:"session_id"` jwt.RegisteredClaims } // ============================================ // STRUCTURES REQUÊTE / RÉPONSE // ============================================ type LoginRequest struct { Username string `json:"username" binding:"required"` Password string `json:"password" binding:"required"` } type RegisterClientRequest struct { Username string `json:"username" binding:"required,min=3,max=50"` Password string `json:"password" binding:"required,min=8"` Nom string `json:"nom" binding:"required,min=2,max=100"` Prenom string `json:"prenom" binding:"required,min=2,max=100"` Telephone string `json:"telephone" binding:"required"` } type RegisterAdminRequest struct { Username string `json:"username" binding:"required,min=3,max=50"` Password string `json:"password" binding:"required,min=8"` Role string `json:"role" binding:"required,oneof=admin cabine livreur"` } type LoginResponse struct { AccessToken string `json:"access_token"` TokenType string `json:"token_type"` ExpiresIn int `json:"expires_in"` User interface{} `json:"user"` } type ProfileResponse struct { Username string `json:"username"` Role string `json:"role"` } // ============================================ // VARIABLES & CONSTANTS // ============================================ var ( clientTokenDuration = 5 * time.Hour adminTokenDuration = 10 * time.Hour userJWTSecret = []byte(os.Getenv("USER_JWT_SECRET")) // ✅ Pour clients adminJWTSecret = []byte(os.Getenv("ADMIN_JWT_SECRET")) // ✅ Pour admin/cabine/livreur ) // ============================================ // UTILITAIRES // ============================================ func generateSessionID() string { bytes := make([]byte, 16) rand.Read(bytes) return hex.EncodeToString(bytes) } // validatePhoneNumber valide le format du numéro de téléphone func validatePhoneNumber(phone string) bool { // Supprimer espaces/tirets clean := regexp.MustCompile(`[\s\-\(\)]`).ReplaceAllString(phone, "") // Format international ou national validFormat := regexp.MustCompile(`^(\+33|0)[1-9]\d{8}$`) return validFormat.MatchString(clean) } func normalizePhoneNumber(phone string) string { clean := regexp.MustCompile(`[\s\-\(\)]`).ReplaceAllString(phone, "") // Convertir 06... en +336... if strings.HasPrefix(clean, "0") { return "+33" + clean[1:] } return clean } // ============================================ // GENERATION TOKENS // ============================================ func generateClientToken(client *models.Client) (string, error) { sessionID := generateSessionID() claims := ClientClaims{ ClientID: client.ID, Username: client.Username, Role: "client", SessionID: sessionID, RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(time.Now().Add(clientTokenDuration)), IssuedAt: jwt.NewNumericDate(time.Now()), NotBefore: jwt.NewNumericDate(time.Now()), Issuer: "api-client", Subject: strconv.Itoa(client.ID), }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) tokenString, err := token.SignedString(userJWTSecret) // ✅ UTILISER userJWTSecret (CLIENT) if err != nil { return "", err } return tokenString, nil } func generateAdminToken(user *models.User) (string, error) { sessionID := generateSessionID() claims := AdminClaims{ UserID: user.ID, Username: user.Username, Role: user.Role, // ← "admin" ou "cabine" ou "livreur" SessionID: sessionID, RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(time.Now().Add(adminTokenDuration)), IssuedAt: jwt.NewNumericDate(time.Now()), NotBefore: jwt.NewNumericDate(time.Now()), Issuer: "api-admin", // Même issuer pour tous les admins Subject: strconv.Itoa(user.ID), }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) tokenString, err := token.SignedString(adminJWTSecret) // ✅ UTILISER adminJWTSecret (ADMIN/CABINE/LIVREUR) if err != nil { return "", err } return tokenString, nil } // ============================================ // HANDLERS AUTHENTIFICATION CLIENT // ============================================ // RegisterClient crée un nouveau compte client // POST /api/v1/auth/register func RegisterClient(c *gin.Context) { var req RegisterClientRequest if err := c.ShouldBindJSON(&req); err != nil { log.Printf("❌ [REGISTER_CLIENT] Erreur binding: %v", err) c.JSON(http.StatusBadRequest, gin.H{ "error": "Données invalides", "details": err.Error(), }) return } // Validation téléphone if !validatePhoneNumber(req.Telephone) { log.Printf("❌ [REGISTER_CLIENT] Téléphone invalide: %s", req.Telephone) c.JSON(http.StatusBadRequest, gin.H{ "error": "Numéro de téléphone invalide", }) return } normalizedPhone := normalizePhoneNumber(req.Telephone) database := c.MustGet("database").(*db.Database) // Vérifier username unique if existingClient, _ := database.GetClientByUsername(req.Username); existingClient != nil { log.Printf("❌ [REGISTER_CLIENT] Username déjà utilisé: %s", req.Username) c.JSON(http.StatusConflict, gin.H{"error": "Nom d'utilisateur déjà utilisé"}) return } // Vérifier téléphone unique if existingClient, _ := database.GetClientByTelephone(normalizedPhone); existingClient != nil { log.Printf("❌ [REGISTER_CLIENT] 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 } // Hasher le mot de passe hashed, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost) if err != nil { log.Printf("❌ [REGISTER_CLIENT] Erreur bcrypt: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur traitement mot de passe"}) return } // Créer le client client := &models.Client{ Username: req.Username, Password: string(hashed), Nom: strings.TrimSpace(req.Nom), Prenom: strings.TrimSpace(req.Prenom), Telephone: normalizedPhone, CreatedAt: time.Now(), } if err := database.CreateClient(client); err != nil { log.Printf("❌ [REGISTER_CLIENT] Erreur création: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur création client"}) return } // Générer le token token, err := generateClientToken(client) if err != nil { log.Printf("❌ [REGISTER_CLIENT] Erreur génération token: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur génération token"}) return } // Sauvegarder le token expiresAt := time.Now().Add(clientTokenDuration) if err := database.SaveToken(client.ID, "client", token, expiresAt); err != nil { log.Printf("❌ [REGISTER_CLIENT] Erreur SaveToken: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur enregistrement token"}) return } // Créer la session Redis sessionID := uuid.New().String() if err := database.CreateClientSession(client.ID, client.Username, sessionID); err != nil { log.Printf("⚠️ [REGISTER_CLIENT] Erreur session Redis: %v", err) } client.Password = "" log.Printf("✅ [REGISTER_CLIENT] Client créé: %s (ID=%d)", client.Username, client.ID) c.JSON(http.StatusCreated, LoginResponse{ AccessToken: token, TokenType: "Bearer", ExpiresIn: int(clientTokenDuration.Seconds()), User: gin.H{ "id": client.ID, "username": client.Username, "nom": client.Nom, "prenom": client.Prenom, "telephone": client.Telephone, "role": "client", "session_id": sessionID, }, }) } // LoginClient authentifie un client // POST /api/v1/auth/login func LoginClient(c *gin.Context) { var req LoginRequest if err := c.ShouldBindJSON(&req); err != nil { log.Printf("❌ [LOGIN_CLIENT] Erreur binding: %v", err) c.JSON(http.StatusBadRequest, gin.H{"error": "Données invalides"}) return } database := c.MustGet("database").(*db.Database) client, err := database.GetClientByUsername(req.Username) if err != nil || client == nil { log.Printf("❌ [LOGIN_CLIENT] Client non trouvé: %s", req.Username) c.JSON(http.StatusUnauthorized, gin.H{"error": "Identifiants invalides"}) return } if err := bcrypt.CompareHashAndPassword([]byte(client.Password), []byte(req.Password)); err != nil { log.Printf("❌ [LOGIN_CLIENT] Mot de passe invalide: %s", req.Username) c.JSON(http.StatusUnauthorized, gin.H{"error": "Identifiants invalides"}) return } // ✅ LIGNE 302 CORRIGÉE - Gérer l'erreur ! token, err := generateClientToken(client) if err != nil { log.Printf("❌ [LOGIN_CLIENT] Erreur génération token: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur génération token"}) return } expiresAt := time.Now().Add(clientTokenDuration) if err := database.SaveToken(client.ID, "client", token, expiresAt); err != nil { log.Printf("❌ [LOGIN_CLIENT] Erreur SaveToken: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur enregistrement token"}) return } // Créer la session Redis sessionID := uuid.New().String() if err := database.CreateClientSession(client.ID, client.Username, sessionID); err != nil { log.Printf("⚠️ [LOGIN_CLIENT] Erreur session Redis: %v", err) } log.Printf("✅ [LOGIN_CLIENT] Client authentifié: %s", req.Username) c.JSON(http.StatusOK, LoginResponse{ AccessToken: token, TokenType: "Bearer", ExpiresIn: int(clientTokenDuration.Seconds()), User: gin.H{ "id": client.ID, "username": client.Username, "nom": client.Nom, "prenom": client.Prenom, "telephone": client.Telephone, "role": "client", "session_id": sessionID, "must_change_password": client.MustChangePassword, }, }) } // ChangePassword permet à un client de changer son mot de passe // PUT /api/v1/auth/change-password func ChangePassword(c *gin.Context) { var req struct { CurrentPassword string `json:"current_password" binding:"required"` NewPassword string `json:"new_password" binding:"required,min=8"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Données invalides", "details": err.Error()}) return } clientID := c.GetInt("client_id") database := c.MustGet("database").(*db.Database) client, err := database.GetClientByID(clientID) if err != nil { log.Printf("❌ [CHANGE_PASSWORD] Client non trouvé: ID=%d", clientID) c.JSON(http.StatusNotFound, gin.H{"error": "Client non trouvé"}) return } if err := bcrypt.CompareHashAndPassword([]byte(client.Password), []byte(req.CurrentPassword)); err != nil { log.Printf("❌ [CHANGE_PASSWORD] Mot de passe actuel invalide: ID=%d", clientID) c.JSON(http.StatusUnauthorized, gin.H{"error": "Mot de passe actuel incorrect"}) return } hashed, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost) if err != nil { log.Printf("❌ [CHANGE_PASSWORD] Erreur bcrypt: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur traitement mot de passe"}) return } if err := database.UpdateClientPasswordAndClearFlag(clientID, string(hashed)); err != nil { log.Printf("❌ [CHANGE_PASSWORD] Erreur update: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur mise à jour mot de passe"}) return } log.Printf("✅ [CHANGE_PASSWORD] Mot de passe changé: ID=%d", clientID) c.JSON(http.StatusOK, gin.H{"success": true, "message": "Mot de passe mis à jour avec succès"}) } // LogoutClient déconnecte un client // POST /api/v1/auth/logout func LogoutClient(c *gin.Context) { database := c.MustGet("database").(*db.Database) // Invalider la session Redis clientID, hasClientID := c.Get("client_id") if hasClientID && clientID != nil { if err := database.InvalidateSession(clientID.(int)); err != nil { log.Printf("⚠️ [LOGOUT_CLIENT] Erreur invalidation session: %v", err) } } // Révoquer le JWT token authHeader := c.GetHeader("Authorization") if authHeader != "" && strings.HasPrefix(authHeader, "Bearer ") { tokenStr := strings.TrimPrefix(authHeader, "Bearer ") database.RevokeToken(tokenStr) } log.Printf("✅ [LOGOUT_CLIENT] Client déconnecté") c.JSON(http.StatusOK, gin.H{"message": "Déconnexion réussie"}) } // ============================================ // HANDLERS AUTHENTIFICATION ADMIN // ============================================ // RegisterAdmin crée un nouvel utilisateur admin/cabine/livreur // POST /api/v1/auth/admin/register func RegisterAdmin(c *gin.Context) { var req RegisterAdminRequest if err := c.ShouldBindJSON(&req); err != nil { log.Printf("❌ [REGISTER_ADMIN] Erreur binding: %v", err) c.JSON(http.StatusBadRequest, gin.H{"error": "Données invalides"}) return } database := c.MustGet("database").(*db.Database) // Vérifier que l'user n'existe pas if existingUser, _ := database.GetUserByUsername(req.Username); existingUser != nil { log.Printf("❌ [REGISTER_ADMIN] Username déjà utilisé: %s", req.Username) c.JSON(http.StatusConflict, gin.H{"error": "Nom d'utilisateur déjà utilisé"}) return } hashed, _ := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost) user := &models.User{ Username: req.Username, Password: string(hashed), Role: req.Role, } if err := database.CreateUser(user); err != nil { log.Printf("❌ [REGISTER_ADMIN] Erreur création: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur création utilisateur"}) return } log.Printf("✅ [REGISTER_ADMIN] User créé: %s (role=%s, ID=%d)", user.Username, user.Role, user.ID) // Générer le token token, err := generateAdminToken(user) if err != nil { log.Printf("❌ [REGISTER_ADMIN] Erreur génération token: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur génération token"}) return } log.Printf("✅ [REGISTER_ADMIN] Token généré: %s...", token[:50]) // ============================================ // ✅ CRITICAL FIX: ENREGISTRER LE TOKEN EN DB // ============================================ expiresAt := time.Now().Add(adminTokenDuration) if err := database.SaveToken(user.ID, user.Role, token, expiresAt); err != nil { log.Printf("❌ [REGISTER_ADMIN] Erreur SaveToken: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur enregistrement token"}) return } log.Printf("✅ [REGISTER_ADMIN] Token enregistré en DB pour user ID: %d", user.ID) user.Password = "" c.JSON(http.StatusCreated, LoginResponse{ AccessToken: token, TokenType: "Bearer", ExpiresIn: int(adminTokenDuration.Seconds()), User: user, }) } // LoginAdmin authentifie un admin/cabine/livreur // POST /api/v1/auth/admin/login // ✅ AMÉLIORÉ: Meilleure gestion d'erreurs func LoginAdmin(c *gin.Context) { var req LoginRequest if err := c.ShouldBindJSON(&req); err != nil { log.Printf("❌ [LOGIN_ADMIN] Erreur binding: %v", err) c.JSON(http.StatusBadRequest, gin.H{"error": "Données invalides"}) return } database := c.MustGet("database").(*db.Database) user, err := database.GetUserByUsername(req.Username) if err != nil || user == nil { log.Printf("❌ [LOGIN_ADMIN] User non trouvé: %s", req.Username) c.JSON(http.StatusUnauthorized, gin.H{"error": "Identifiants invalides"}) return } // Vérifier que c'est un admin/cabine/livreur if user.Role != "admin" && user.Role != "cabine" && user.Role != "livreur" { log.Printf("❌ [LOGIN_ADMIN] Rôle invalide: %s", user.Role) c.JSON(http.StatusUnauthorized, gin.H{"error": "Accès non autorisé"}) return } // Vérifier le mot de passe if user.Password == "" { log.Printf("❌ [LOGIN_ADMIN] Mot de passe vide en base") c.JSON(http.StatusUnauthorized, gin.H{"error": "Identifiants invalides"}) return } if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.Password)); err != nil { log.Printf("❌ [LOGIN_ADMIN] Mot de passe invalide: %s", req.Username) c.JSON(http.StatusUnauthorized, gin.H{"error": "Identifiants invalides"}) return } // Générer le token token, _ := generateAdminToken(user) expiresAt := time.Now().Add(adminTokenDuration) if err := database.SaveToken(user.ID, user.Role, token, expiresAt); err != nil { log.Printf("❌ [LOGIN_ADMIN] Erreur SaveToken: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur enregistrement token"}) return } log.Printf("✅ [LOGIN_ADMIN] User authentifié: %s (role=%s)", user.Username, user.Role) c.JSON(http.StatusOK, LoginResponse{ AccessToken: token, TokenType: "Bearer", ExpiresIn: int(adminTokenDuration.Seconds()), User: gin.H{ "id": user.ID, "username": user.Username, "role": user.Role, }, }) } // LogoutAdmin déconnecte un admin/cabine/livreur // POST /api/v1/auth/admin/logout func LogoutAdmin(c *gin.Context) { database := c.MustGet("database").(*db.Database) // Révoquer le JWT token authHeader := c.GetHeader("Authorization") if authHeader != "" && strings.HasPrefix(authHeader, "Bearer ") { tokenStr := strings.TrimPrefix(authHeader, "Bearer ") database.RevokeToken(tokenStr) } log.Printf("✅ [LOGOUT_ADMIN] User déconnecté") c.JSON(http.StatusOK, gin.H{"message": "Déconnexion réussie"}) } // ============================================ // HELPERS // ============================================ // GetCurrentClient récupère le client actuel // GET /api/v1/profile/client func GetCurrentClient(c *gin.Context) { clientID := c.GetInt("client_id") database := c.MustGet("database").(*db.Database) client, err := database.GetClientByID(clientID) if err != nil { log.Printf("❌ [GET_CURRENT_CLIENT] Client non trouvé: ID=%d", clientID) c.JSON(http.StatusNotFound, gin.H{"error": "Client non trouvé"}) return } client.Password = "" log.Printf("✅ [GET_CURRENT_CLIENT] Client récupéré: %s", client.Username) c.JSON(http.StatusOK, gin.H{ "client": gin.H{ "id": client.ID, "username": client.Username, "nom": client.Nom, "prenom": client.Prenom, "telephone": client.Telephone, "command": client.Command, "point": client.Point, "amende": client.Amende, }, }) } // GetCurrentAdmin récupère l'admin/user actuel // GET /api/v1/profile/admin func GetCurrentAdmin(c *gin.Context) { userID := c.GetInt("user_id") database := c.MustGet("database").(*db.Database) user, err := database.GetUserByID(userID) if err != nil { log.Printf("❌ [GET_CURRENT_ADMIN] User non trouvé: ID=%d", userID) c.JSON(http.StatusNotFound, gin.H{"error": "Utilisateur non trouvé"}) return } user.Password = "" log.Printf("✅ [GET_CURRENT_ADMIN] User récupéré: %s", user.Username) c.JSON(http.StatusOK, gin.H{ "user": ProfileResponse{ Username: user.Username, Role: user.Role, }, }) } // HealthCheck vérifie la santé de l'API // GET /api/v1/health func HealthCheck(c *gin.Context) { database := c.MustGet("database").(*db.Database) if err := database.DB.Ping(); err != nil { log.Printf("⚠️ [HEALTH] Database down: %v", err) c.JSON(http.StatusServiceUnavailable, gin.H{ "status": "unhealthy", "database": "disconnected", "timestamp": time.Now().Unix(), }) return } log.Printf("✅ [HEALTH] API healthy") c.JSON(http.StatusOK, gin.H{ "status": "healthy", "database": "connected", "timestamp": time.Now().Unix(), "version": "2.0.0", }) } // GetAllUsers récupère tous les utilisateurs (Admin only) func GetAllUsers(c *gin.Context) { database := c.MustGet("database").(*db.Database) userRole := c.GetString("role") if userRole != "admin" && userRole != "cabine" { c.JSON(http.StatusForbidden, gin.H{"error": "Accès réservé aux livreurs et aux administrateurs"}) return } users, err := database.GetAllUsers() if err != nil { log.Printf("❌ [GET_ALL_USERS] Erreur: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur récupération"}) return } var sanitized []gin.H for _, u := range users { sanitized = append(sanitized, gin.H{ "id": u.ID, "username": u.Username, "role": u.Role, }) } log.Printf("✅ [GET_ALL_USERS] %d users récupérés", len(sanitized)) c.JSON(http.StatusOK, gin.H{ "users": sanitized, "count": len(sanitized), }) } func GetAllDeliveryMen(c *gin.Context) { database := c.MustGet("database").(*db.Database) userRole := c.GetString("role") if userRole != "cabine" && userRole != "admin" { c.JSON(http.StatusForbidden, gin.H{"error": "Accès réservé aux livreurs et aux administrateurs"}) return } users, err := database.GetAllDeliveryMen() if err != nil { log.Printf("❌ [GET_ALL_USERS] Erreur: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur récupération"}) return } var sanitized []gin.H for _, u := range users { sanitized = append(sanitized, gin.H{ "id": u.ID, "username": u.Username, "role": u.Role, }) } log.Printf("✅ [GET_ALL_USERS] %d users récupérés", len(sanitized)) c.JSON(http.StatusOK, gin.H{ "users": sanitized, "count": len(sanitized), }) } // GetAllClients récupère tous les clients // GET /api/v1/admin/clients func GetAllClients(c *gin.Context) { database := c.MustGet("database").(*db.Database) userRole := c.GetString("role") if userRole != "cabine" && userRole != "admin" { c.JSON(http.StatusForbidden, gin.H{"error": "Accès réservé aux livreurs et aux administrateurs"}) return } clients, err := database.GetAllClients() if err != nil { log.Printf("❌ [GET_ALL_CLIENTS] Erreur: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur récupération"}) return } var sanitized []gin.H for _, cl := range clients { sanitized = append(sanitized, gin.H{ "id": cl.ID, "username": cl.Username, "nom": cl.Nom, "prenom": cl.Prenom, "telephone": cl.Telephone, "command": cl.Command, "point": cl.Point, "points_zipette": cl.PointZipette, "amende": cl.Amende, "cancellations_count": cl.CancellationsCount, "last_penalty_reason": cl.LastPenaltyReason, "referral_balance": cl.ReferralBalance, }) } log.Printf("✅ [GET_ALL_CLIENTS] %d clients récupérés", len(sanitized)) c.JSON(http.StatusOK, gin.H{ "clients": sanitized, "count": len(sanitized), }) } func DeleteUser(c *gin.Context) { database := c.MustGet("database").(*db.Database) idStr := c.Param("id") id, err := strconv.Atoi(idStr) if err != nil { log.Printf("❌ [DELETE_USER] ID invalide: %v", err) c.JSON(http.StatusBadRequest, gin.H{"error": "ID invalide"}) return } userRole := c.GetString("role") if userRole != "admin" { c.JSON(http.StatusForbidden, gin.H{"error": "Accès réservé aux livreurs et aux administrateurs"}) return } err = database.DeleteUser(id) if err != nil { log.Printf("❌ [DELETE_USER] Erreur: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur suppression"}) return } log.Printf("✅ [DELETE_USER] Utilisateur %d supprimé", id) c.JSON(http.StatusOK, gin.H{"message": "Utilisateur supprimé"}) } // DeleteClient func DeleteClient(c *gin.Context) { database := c.MustGet("database").(*db.Database) idStr := c.Param("id") id, err := strconv.Atoi(idStr) if err != nil { log.Printf("❌ [DELETE_CLIENT] ID invalide: %v", err) c.JSON(http.StatusBadRequest, gin.H{"error": "ID invalide"}) return } userRole := c.GetString("role") if userRole != "cabine" && userRole != "admin" { c.JSON(http.StatusForbidden, gin.H{"error": "Accès réservé aux livreurs et aux administrateurs"}) return } err = database.DeleteClient(id) if err != nil { log.Printf("❌ [DELETE_CLIENT] Erreur: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur suppression"}) return } log.Printf("✅ [DELETE_CLIENT] Client %d supprimé", id) c.JSON(http.StatusOK, gin.H{"message": "Client supprimé"}) } func CreateUser(c *gin.Context) { database := c.MustGet("database").(*db.Database) var user models.User if err := c.ShouldBindJSON(&user); err != nil { log.Printf("❌ [CREATE_USER] Erreur de liaison JSON: %v", err) c.JSON(http.StatusBadRequest, gin.H{"error": "Erreur de liaison JSON"}) return } userRole := c.GetString("role") if userRole != "cabine" && userRole != "admin" { c.JSON(http.StatusForbidden, gin.H{"error": "Accès réservé aux livreurs et aux administrateurs"}) return } err := database.CreateUser(&user) if err != nil { log.Printf("❌ [CREATE_USER] Erreur: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur création"}) return } log.Printf("✅ [CREATE_USER] Utilisateur %d créé", user.ID) c.JSON(http.StatusCreated, gin.H{"message": "Utilisateur créé"}) }