package handlers import ( "crypto/rand" "fmt" "gestion/db" "gestion/models" "gestion/services" "gestion/utils" "log" "net/http" "os" "strconv" "strings" "time" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" "github.com/google/uuid" "golang.org/x/crypto/bcrypt" ) var ( clientTokenDuration = 5 * time.Hour adminTokenDuration = 10 * time.Hour userJWTSecret = []byte(os.Getenv("USER_JWT_SECRET")) adminJWTSecret = []byte(os.Getenv("ADMIN_JWT_SECRET")) ) func generateClientToken(client *models.Client) (string, error) { sessionID := utils.GenerateSessionID() claims := models.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) if err != nil { return "", err } return tokenString, nil } func generateAdminToken(user *models.User) (string, error) { sessionID := utils.GenerateSessionID() claims := models.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) if err != nil { return "", err } return tokenString, nil } // RegisterClient crée un nouveau compte client func RegisterClient(c *gin.Context) { var req models.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", }) return } // Sanitize text inputs req.Username = utils.StripHTML(req.Username) req.Nom = utils.StripHTML(req.Nom) req.Prenom = utils.StripHTML(req.Prenom) // Validation téléphone if !utils.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 := utils.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 = "" c.JSON(http.StatusCreated, models.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, }, }) } // AdminCreateClient crée un client depuis l'interface admin (sans session ni token) func AdminCreateClient(c *gin.Context) { var req models.RegisterClientRequest if err := c.ShouldBindJSON(&req); err != nil { log.Printf("❌ [ADMIN_CREATE_CLIENT] Binding error: %v | body: username=%q nom=%q prenom=%q tel=%q", err, req.Username, req.Nom, req.Prenom, req.Telephone) c.JSON(http.StatusBadRequest, gin.H{"error": "Données invalides", "detail": err.Error()}) return } // Sanitize text inputs req.Username = utils.StripHTML(req.Username) req.Nom = utils.StripHTML(req.Nom) req.Prenom = utils.StripHTML(req.Prenom) if !utils.ValidatePhoneNumber(req.Telephone) { log.Printf("❌ [ADMIN_CREATE_CLIENT] Téléphone invalide: %q", req.Telephone) c.JSON(http.StatusBadRequest, gin.H{"error": "Numéro de téléphone invalide"}) return } normalizedPhone := utils.NormalizePhoneNumber(req.Telephone) database := c.MustGet("database").(*db.Database) if existing, _ := database.GetClientByUsername(req.Username); existing != nil { c.JSON(http.StatusConflict, gin.H{"error": "Nom d'utilisateur déjà utilisé"}) return } if existing, _ := database.GetClientByTelephone(normalizedPhone); existing != nil { c.JSON(http.StatusConflict, gin.H{"error": "Ce numéro de téléphone est déjà utilisé"}) return } hashed, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur traitement mot de passe"}) return } client := &models.Client{ Username: req.Username, Password: string(hashed), Nom: strings.TrimSpace(req.Nom), Prenom: strings.TrimSpace(req.Prenom), Telephone: normalizedPhone, MustChangePassword: true, } if err := database.CreateClient(client); err != nil { log.Printf("❌ [ADMIN_CREATE_CLIENT] Erreur création: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur création client"}) return } c.JSON(http.StatusCreated, gin.H{ "message": "Client créé avec succès", "client": gin.H{ "id": client.ID, "username": client.Username, "nom": client.Nom, "prenom": client.Prenom, "telephone": client.Telephone, }, }) } func cryptoRandInt() int { b := make([]byte, 4) rand.Read(b) return int(b[0])<<24 | int(b[1])<<16 | int(b[2])<<8 | int(b[3]) } // LoginClient authentifie un client func LoginClient(c *gin.Context) { database := c.MustGet("database").(*db.Database) var req models.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 } 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 } settings, err := database.GetSettings() if err != nil { log.Printf("❌ [LOGIN_CLIENT] Erreur récupération des paramètres: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur interne"}) return } if settings.Telegram2FAEnabled && client.TwoFAEnabled { chatID, linked, _ := database.GetClientTelegramChatID(client.Username) if linked { code := fmt.Sprintf("%06d", cryptoRandInt()%1000000) sessionToken := uuid.New().String() if err := db.Store2FASession(sessionToken, client.Username, code); err == nil { msg := fmt.Sprintf("🔐 Code de vérification : %s\n\nValable 5 minutes.", code) services.TelegramBot.SendMessage(chatID, msg) c.JSON(http.StatusOK, gin.H{ "requires_2fa": true, "session_token": sessionToken, }) return } } } 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 } sessionID := uuid.New().String() if err := database.CreateClientSession(client.ID, client.Username, sessionID); err != nil { log.Printf("⚠️ [LOGIN_CLIENT] Erreur session Redis: %v", err) } c.JSON(http.StatusOK, models.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, }, }) } func Verify2FAClient(c *gin.Context) { database := c.MustGet("database").(*db.Database) var req struct { SessionToken string `json:"session_token" binding:"required"` Code string `json:"code" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Données invalides"}) return } username, err := db.Verify2FASession(req.SessionToken, req.Code) if err != nil { log.Printf("❌ [2FA] Échec vérification: %v", err) c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()}) return } client, err := database.GetClientByUsername(username) if err != nil || client == nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur interne"}) return } token, err := generateClientToken(client) if err != nil { 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 { c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur enregistrement token"}) return } sessionID := uuid.New().String() if err := database.CreateClientSession(client.ID, client.Username, sessionID); err != nil { log.Printf("⚠️ [2FA] Erreur session Redis: %v", err) } c.JSON(http.StatusOK, models.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, }, }) } func GetClient2FAStatus(c *gin.Context) { clientID := c.GetInt("client_id") database := c.MustGet("database").(*db.Database) client, err := database.GetClientByID(clientID) if err != nil || client == nil { c.JSON(http.StatusNotFound, gin.H{"error": "Client non trouvé"}) return } _, tgLinked, _ := database.GetClientTelegramChatID(client.Username) settings, _ := database.GetSettings() c.JSON(http.StatusOK, gin.H{ "two_fa_enabled": client.TwoFAEnabled, "telegram_linked": tgLinked, "admin_2fa_enabled": settings.Telegram2FAEnabled, }) } func ToggleClient2FA(c *gin.Context) { clientID := c.GetInt("client_id") database := c.MustGet("database").(*db.Database) var req struct { Enabled bool `json:"enabled"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Données invalides"}) return } client, err := database.GetClientByID(clientID) if err != nil || client == nil { c.JSON(http.StatusNotFound, gin.H{"error": "Client non trouvé"}) return } if req.Enabled { _, linked, _ := database.GetClientTelegramChatID(client.Username) if !linked { c.JSON(http.StatusBadRequest, gin.H{"error": "Telegram non lié — impossible d'activer la 2FA"}) return } settings, _ := database.GetSettings() if !settings.Telegram2FAEnabled { c.JSON(http.StatusBadRequest, gin.H{"error": "La 2FA n'est pas activée par l'administrateur"}) return } } if err := database.SetClientTwoFAEnabled(clientID, req.Enabled); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur mise à jour"}) return } c.JSON(http.StatusOK, gin.H{"success": true, "two_fa_enabled": req.Enabled}) } 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 { utils.BindErr(c, err) 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 } c.JSON(http.StatusOK, gin.H{"success": true, "message": "Mot de passe mis à jour avec succès"}) } // LogoutClient déconnecte un client func LogoutClient(c *gin.Context) { database := c.MustGet("database").(*db.Database) 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) } } authHeader := c.GetHeader("Authorization") if authHeader != "" && strings.HasPrefix(authHeader, "Bearer ") { tokenStr := strings.TrimPrefix(authHeader, "Bearer ") database.RevokeToken(tokenStr) } c.JSON(http.StatusOK, gin.H{"message": "Déconnexion réussie"}) } // RegisterAdmin crée un nouvel utilisateur admin/cabine/livreur func RegisterAdmin(c *gin.Context) { var req models.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) 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 } 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 } 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 } user.Password = "" c.JSON(http.StatusCreated, models.LoginResponse{ AccessToken: token, TokenType: "Bearer", ExpiresIn: int(adminTokenDuration.Seconds()), User: user, }) } // LoginAdmin authentifie un admin/cabine/livreur func LoginAdmin(c *gin.Context) { var req models.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 } 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 } 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 } 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 } c.JSON(http.StatusOK, models.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 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, "amende": client.Amende, "points_extra": client.PointsExtra, }, }) } // 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": models.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, "points_extra": cl.PointsExtra, "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éé"}) }