From 12a7facc4593df19e4746a779c4faa7f902a8c3b Mon Sep 17 00:00:00 2001 From: Nuxgrid Date: Wed, 8 Jul 2026 21:54:00 +0200 Subject: [PATCH] chore: build --- README.md | 11 +- backend/gestion/db/db_address.go | 2 +- backend/gestion/db/db_commands.go | 12 -- backend/gestion/db/db_sanction_client.go | 93 +++++----- backend/gestion/db/db_telegram.go | 2 +- backend/gestion/handlers/alert.go | 9 + backend/gestion/handlers/auth.go | 162 ----------------- backend/gestion/handlers/cabine.go | 165 ------------------ backend/gestion/handlers/client_tracking.go | 2 +- backend/gestion/handlers/deleviry.go | 24 ++- backend/gestion/handlers/history.go | 74 -------- backend/gestion/handlers/redis_services.go | 30 ---- backend/gestion/handlers/traffic.go | 52 ------ backend/gestion/handlers/update_profile.go | 3 - .../gestion/services/adresses_correction.go | 2 +- backend/gestion/utils/auth.go | 22 +-- backend/gestion/utils/cabine.go | 14 -- backend/gestion/utils/utils.go | 15 -- backend/gestion/workers/payment_checker.go | 10 -- 19 files changed, 93 insertions(+), 611 deletions(-) delete mode 100644 backend/gestion/handlers/cabine.go delete mode 100644 backend/gestion/handlers/traffic.go delete mode 100644 backend/gestion/utils/cabine.go diff --git a/README.md b/README.md index 200dca63..1206e965 100644 --- a/README.md +++ b/README.md @@ -3101,6 +3101,13 @@ Le système tente d'abord toutes les clés disponibles en rotation, puis bascule ## 📋 Changelog +### v5.5.0 — 2026-07-08 + +- **Fix — amende annulation livreur (`ApplyCancellationPenalty`)** : l'amende appliquée quand un livreur marque le client absent écrasait le montant existant au lieu de l'additionner, et n'était pas protégée par un verrou (`FOR UPDATE`). Elle est désormais cumulative et transactionnelle, cohérente avec le chemin d'annulation client (`CancelAtomic`). +- **Fix — mot de passe loggé en clair** : la modification d'un client par un admin (`PUT /admin/protected/clients/:id`) journalisait le corps de requête complet, y compris le nouveau mot de passe. Le log ne contient plus de donnée sensible. +- **Fix — IDOR consultation d'alerte police** : un livreur pouvait consulter le détail de l'alerte d'un autre livreur en devinant l'ID (`GET /api/v1/livreur/alert/:id`). L'accès est désormais restreint à ses propres alertes ; admin et cabine conservent l'accès complet. +- **Nettoyage — code mort** : suppression des handlers et fonctions utilitaires non routés/non appelés (ancien module `handlers/cabine.go`, `RegisterClient`, `GetCurrentClient`, `GetCurrentAdmin`, `GetMyCompletedOrders`, `GetRealtimeStats`, `StartPaymentChecker`, et helpers internes associés), identifiés via `staticcheck` et `deadcode`. + ### v5.4.0 — 2026-05-18 - **Rotation automatique des clés TomTom** : jusqu'à 3 clés configurables (`TOMTOM_API_KEY_1/2/3`). En cas de quota dépassé (403/429), le système passe à la clé suivante automatiquement sans interruption. Fallback Haversine si toutes les clés sont épuisées. @@ -3127,8 +3134,8 @@ Le système tente d'abord toutes les clés disponibles en rotation, puis bascule --- -**Documentation mise à jour le :** 2026-06-11 -**Version API :** 5.4.0 +**Documentation mise à jour le :** 2026-07-08 +**Version API :** 5.5.0 **Technologies :** Go 1.24, Gin, PostgreSQL 16, Redis 7, React 19, Expo 54, TomTom API, ModSecurity WAF **Déploiement :** Docker Compose · Nginx + ModSecurity OWASP CRS · TLS 1.2/1.3 **Base URL prod :** `https://mln-uber.club` diff --git a/backend/gestion/db/db_address.go b/backend/gestion/db/db_address.go index b600d9da..ad544f1c 100644 --- a/backend/gestion/db/db_address.go +++ b/backend/gestion/db/db_address.go @@ -15,7 +15,7 @@ func (d *Database) CheckAddress(addressByUser *models.Command) error { return fmt.Errorf("checkAddress: %w", result.Error) } addressByUser.DeliveryAddress = correction.CorrectAddress - return fmt.Errorf("Adresse invalide %s", correction.CorrectAddress) + return fmt.Errorf("adresse invalide %s", correction.CorrectAddress) } func (d *Database) AddAddress(CorrectAddressByAdmin string, InvalidAddressByAdmin string) error { diff --git a/backend/gestion/db/db_commands.go b/backend/gestion/db/db_commands.go index 3da933cd..a9eb0ebb 100644 --- a/backend/gestion/db/db_commands.go +++ b/backend/gestion/db/db_commands.go @@ -57,18 +57,6 @@ type basketItem struct { RewardPoolKey string `gorm:"column:reward_pool_key"` } -func (d *Database) fetchBasketItems(username string) ([]basketItem, float64, error) { - var items []basketItem - if err := d.GDB.Table("baskets").Select("product_id, quantity, price, is_reward, reward_pool_key").Where("username = ?", username).Scan(&items).Error; err != nil { - return nil, 0, fmt.Errorf("erreur récupération panier: %w", err) - } - total := 0.0 - for _, item := range items { - total += item.Price - } - return items, total, nil -} - // validateCommandStatus vérifie si le statut est valide func validateCommandStatus(status string) error { validStatuses := map[string]bool{ diff --git a/backend/gestion/db/db_sanction_client.go b/backend/gestion/db/db_sanction_client.go index d7cd437d..90b5fd71 100644 --- a/backend/gestion/db/db_sanction_client.go +++ b/backend/gestion/db/db_sanction_client.go @@ -27,25 +27,6 @@ func (d *Database) GetClientCancellationsCount(username string) (int, error) { return result.Count, nil } -// IncrementClientCancellationsCount incrémente le compteur d'annulations -func (d *Database) IncrementClientCancellationsCount(username string) error { - result := d.GDB.Model(&models.Client{}).Where("username = ?", username).Updates(map[string]any{ - "cancellations_count": gorm.Expr("COALESCE(cancellations_count, 0) + 1"), - }) - if result.Error != nil { - log.Printf("❌ [IncrementCancellations] Erreur: %v", result.Error) - return fmt.Errorf("erreur incrémentation: %w", result.Error) - } - if result.RowsAffected == 0 { - return fmt.Errorf("client non trouvé") - } - - cacheKey := fmt.Sprintf("client:%s", username) - Redis.Del(RedisCtx, cacheKey) - - return nil -} - // penaltyForCount retourne le montant du palier applicable pour un nombre d'annulations donné func penaltyForCount(count int, tiers []models.PenaltyTier) int { if len(tiers) == 0 { @@ -64,6 +45,16 @@ func penaltyForCount(count int, tiers []models.PenaltyTier) int { return sorted[len(sorted)-1].Amount } +// penaltyTiers charge le barème de pénalités configuré, avec repli sur le barème par défaut si les settings sont indisponibles +func (d *Database) penaltyTiers(logCtx string) []models.PenaltyTier { + settings, err := d.GetSettings() + if err != nil { + log.Printf("⚠️ [%s] Impossible de charger les settings, barème par défaut: %v", logCtx, err) + settings = DefaultSettings() + } + return settings.PenaltyTiers +} + // CalculateCancellationPenalty calcule la pénalité selon l'historique et le barème configuré func (d *Database) CalculateCancellationPenalty(username string) (int, error) { count, err := d.GetClientCancellationsCount(username) @@ -71,13 +62,7 @@ func (d *Database) CalculateCancellationPenalty(username string) (int, error) { return 0, err } - settings, err := d.GetSettings() - if err != nil { - log.Printf("⚠️ [CalculatePenalty] Impossible de charger les settings, barème par défaut: %v", err) - settings = DefaultSettings() - } - - penalty := penaltyForCount(count, settings.PenaltyTiers) + penalty := penaltyForCount(count, d.penaltyTiers("CalculatePenalty")) log.Printf("💰 [CalculatePenalty] Client %s - Annulations: %d → Pénalité: %d points", username, count, penalty) @@ -85,30 +70,48 @@ func (d *Database) CalculateCancellationPenalty(username string) (int, error) { return penalty, nil } -// ApplyCancellationPenalty applique une pénalité et incrémente le compteur d'annulations +// ApplyCancellationPenalty applique une pénalité (cumulative) et incrémente le compteur d'annulations. +// Verrouillée via FOR UPDATE pour éviter qu'un appel concurrent (même client, deux livraisons en parallèle) +// calcule la pénalité sur un compteur pas encore à jour, et l'amende s'additionne au lieu d'écraser +// le solde existant (cohérent avec CancelCommandAtomic pour l'annulation côté client). func (d *Database) ApplyCancellationPenalty(username string) (int, error) { - penalty, err := d.CalculateCancellationPenalty(username) + tiers := d.penaltyTiers("ApplyCancellationPenalty") + + var penalty int + + err := d.GDB.Transaction(func(tx *gorm.DB) error { + var count int + if err := tx.Raw(` + SELECT COALESCE(cancellations_count, 0) FROM clients + WHERE username = ? FOR UPDATE`, username).Scan(&count).Error; err != nil { + return fmt.Errorf("erreur récupération compteur: %w", err) + } + + penalty = penaltyForCount(count, tiers) + + log.Printf("⚠️ [ApplyCancellationPenalty] Client %s - Pénalité calculée: %d points", username, penalty) + + result := tx.Exec(` + UPDATE clients + SET cancellations_count = COALESCE(cancellations_count, 0) + 1, + amende = amende + ?, + updated_at = CURRENT_TIMESTAMP + WHERE username = ?`, penalty, username) + if result.Error != nil { + log.Printf("❌ [ApplyCancellationPenalty] Erreur UPDATE: %v", result.Error) + return fmt.Errorf("erreur application pénalité: %w", result.Error) + } + if result.RowsAffected == 0 { + return fmt.Errorf("client non trouvé") + } + + log.Printf("✅ [ApplyCancellationPenalty] Amende %d appliquée à %s", penalty, username) + return nil + }) if err != nil { return 0, err } - log.Printf("⚠️ [ApplyCancellationPenalty] Client %s - Pénalité calculée: %d points", username, penalty) - - if err := d.IncrementClientCancellationsCount(username); err != nil { - return 0, err - } - - result := d.GDB.Model(&models.Client{}).Where("username = ?", username).Update("amende", float64(penalty)) - if result.Error != nil { - log.Printf("❌ [ApplyCancellationPenalty] Erreur UPDATE: %v", result.Error) - return 0, fmt.Errorf("erreur application pénalité: %w", result.Error) - } - if result.RowsAffected == 0 { - return 0, fmt.Errorf("client non trouvé") - } - - log.Printf("✅ [ApplyCancellationPenalty] Amende %d appliquée à %s", penalty, username) - cacheKey := fmt.Sprintf("client:%s", username) Redis.Del(RedisCtx, cacheKey) diff --git a/backend/gestion/db/db_telegram.go b/backend/gestion/db/db_telegram.go index c521def1..85255871 100644 --- a/backend/gestion/db/db_telegram.go +++ b/backend/gestion/db/db_telegram.go @@ -43,7 +43,7 @@ func GenerateLinkToken(username, role string) (string, error) { key := fmt.Sprintf("telegram:link:%s", token) if err := Redis.Set(RedisCtx, key, val, linkTokenTTL).Err(); err != nil { - return "", fmt.Errorf("Redis SET: %w", err) + return "", fmt.Errorf("redis set: %w", err) } return token, nil } diff --git a/backend/gestion/handlers/alert.go b/backend/gestion/handlers/alert.go index b2fd2669..3a6f883a 100644 --- a/backend/gestion/handlers/alert.go +++ b/backend/gestion/handlers/alert.go @@ -92,6 +92,15 @@ func GetAlert(c *gin.Context) { return } + // Un livreur ne peut consulter que ses propres alertes — admin/cabine gardent l'accès complet pour le dispatch + if userRole == "livreur" { + username, exists := c.Get("username") + if !exists || alert.Username != username.(string) { + c.JSON(http.StatusForbidden, gin.H{"error": "Cette alerte ne vous appartient pas"}) + return + } + } + c.JSON(http.StatusOK, gin.H{ "success": true, "alert": alert, diff --git a/backend/gestion/handlers/auth.go b/backend/gestion/handlers/auth.go index 03eee1fc..d0943bda 100644 --- a/backend/gestion/handlers/auth.go +++ b/backend/gestion/handlers/auth.go @@ -73,112 +73,6 @@ func generateAdminToken(user *models.User) (string, error) { 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) { if userRole := c.GetString("role"); userRole != "admin" { @@ -602,62 +496,6 @@ func LogoutAdmin(c *gin.Context) { // 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, - }, - }) -} - // GetAllUsers récupère tous les utilisateurs (Admin only) func GetAllUsers(c *gin.Context) { database := c.MustGet("database").(*db.Database) diff --git a/backend/gestion/handlers/cabine.go b/backend/gestion/handlers/cabine.go deleted file mode 100644 index 76ccab24..00000000 --- a/backend/gestion/handlers/cabine.go +++ /dev/null @@ -1,165 +0,0 @@ -package handlers - -import ( - "gestion/db" - "net/http" - "strconv" - - "github.com/gin-gonic/gin" -) - -func GetLivreurPosition(c *gin.Context) { - database := c.MustGet("database").(*db.Database) - livreurUsername := c.Param("username") - - userRole := c.GetString("role") - if userRole != "admin" && userRole != "cabine" { - c.JSON(http.StatusForbidden, gin.H{ - "error": "Accès refusé - Réservé aux administrateurs et cabines", - }) - return - } - - if livreurUsername == "" { - c.JSON(http.StatusBadRequest, gin.H{"error": "Username livreur requis"}) - return - } - - position, err := database.GetLivreurPosition(livreurUsername) - if err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "livreur": livreurUsername, - "message": "Position non disponible (GPS désactivé ou livraison terminée)", - }) - return - } - - c.JSON(http.StatusOK, gin.H{ - "success": true, - "livreur": livreurUsername, - "position": position, - }) -} - -func GetDeliveryIssues(c *gin.Context) { - database := c.MustGet("database").(*db.Database) - - status := c.Query("status") - - userRole := c.GetString("role") - if userRole != "admin" && userRole != "cabine" { - c.JSON(http.StatusForbidden, gin.H{ - "error": "Accès refusé - Réservé aux administrateurs et cabines", - }) - return - } - issues, err := database.GetDeliveryIssues(status) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Erreur récupération problèmes", - }) - return - } - - c.JSON(http.StatusOK, gin.H{ - "success": true, - "issues": issues, - "count": len(issues), - }) -} - -func CreateDeliveryIssue(c *gin.Context) { - database := c.MustGet("database").(*db.Database) - - var req struct { - CommandID int `json:"command_id" binding:"required"` - IssueType string `json:"issue_type" binding:"required"` - Description string `json:"description" binding:"required"` - } - - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "Données invalides"}) - return - } - - cabineUsername, _ := c.Get("username") - - issue, err := database.CreateDeliveryIssue( - req.CommandID, - req.IssueType, - req.Description, - cabineUsername.(string), - ) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Erreur création problème", - }) - return - } - - c.JSON(http.StatusCreated, gin.H{ - "success": true, - "message": "Problème enregistré", - "issue": issue, - }) -} - -func UpdateDeliveryIssue(c *gin.Context) { - database := c.MustGet("database").(*db.Database) - - issueID, err := strconv.Atoi(c.Param("id")) - if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "ID invalide"}) - return - } - - var req struct { - Status string `json:"status"` - Resolution string `json:"resolution"` - } - - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "Données invalides"}) - return - } - - cabineUsername, _ := c.Get("username") - - err = database.UpdateDeliveryIssue(issueID, req.Status, req.Resolution, cabineUsername.(string)) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Erreur mise à jour", - }) - return - } - - c.JSON(http.StatusOK, gin.H{ - "success": true, - "message": "Problème mis à jour", - }) -} - -func GetCommandLogs(c *gin.Context) { - database := c.MustGet("database").(*db.Database) - - commandID, err := strconv.Atoi(c.Param("id")) - if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "ID commande invalide"}) - return - } - - logs, err := database.GetCommandLogs(commandID) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Erreur récupération logs", - }) - return - } - - c.JSON(http.StatusOK, gin.H{ - "success": true, - "logs": logs, - "count": len(logs), - }) -} diff --git a/backend/gestion/handlers/client_tracking.go b/backend/gestion/handlers/client_tracking.go index ddaf52a4..7c983c98 100644 --- a/backend/gestion/handlers/client_tracking.go +++ b/backend/gestion/handlers/client_tracking.go @@ -209,7 +209,7 @@ func buildTimeline(logs []map[string]any) []gin.H { for _, logEntry := range logs { status, _ := logEntry["status"].(string) message, _ := logEntry["message"].(string) - createdAt, _ := logEntry["created_at"] + createdAt := logEntry["created_at"] timeline = append(timeline, gin.H{ "status": status, diff --git a/backend/gestion/handlers/deleviry.go b/backend/gestion/handlers/deleviry.go index 3e6a74e5..c83c7b0e 100644 --- a/backend/gestion/handlers/deleviry.go +++ b/backend/gestion/handlers/deleviry.go @@ -627,6 +627,20 @@ func GetMyDeliveryStats(c *gin.Context) { ORDER BY year, month_num `, usernameStr).Scan(&monthRows) + type TodayRow struct { + Count int `gorm:"column:count"` + Revenue float64 `gorm:"column:revenue"` + } + var todayRow TodayRow + gdb.Raw(` + SELECT COUNT(*) AS count, + COALESCE(SUM(total_prix - COALESCE(referral_used, 0)), 0) AS revenue + FROM commandes + WHERE livreur_assign = ? + AND status IN ('livre', 'approved') + AND DATE(updated_at) = CURRENT_DATE + `, usernameStr).Scan(&todayRow) + monthNames := [13]string{"", "Jan", "Fév", "Mar", "Avr", "Mai", "Jun", "Jul", "Aoû", "Sep", "Oct", "Nov", "Déc"} byDay := make([]gin.H, len(dayRows)) @@ -661,9 +675,11 @@ func GetMyDeliveryStats(c *gin.Context) { } c.JSON(http.StatusOK, gin.H{ - "success": true, - "by_day": byDay, - "by_week": byWeek, - "by_month": byMonth, + "success": true, + "by_day": byDay, + "by_week": byWeek, + "by_month": byMonth, + "today_count": todayRow.Count, + "today_revenue": todayRow.Revenue, }) } diff --git a/backend/gestion/handlers/history.go b/backend/gestion/handlers/history.go index 8fbc858b..57430dcb 100644 --- a/backend/gestion/handlers/history.go +++ b/backend/gestion/handlers/history.go @@ -10,80 +10,6 @@ import ( "github.com/gin-gonic/gin" ) -func GetMyCompletedOrders(c *gin.Context) { - database := c.MustGet("database").(*db.Database) - - // ✅ SÉCURITÉ: Récupérer depuis JWT validé - username, exists := c.Get("username") - if !exists { - log.Printf("❌ [HISTORY] Utilisateur non authentifié") - c.JSON(http.StatusUnauthorized, gin.H{ - "error": "Authentification requise", - }) - return - } - - usernameStr := username.(string) - log.Printf("📚 [HISTORY] Récupération historique pour: %s", usernameStr) - - // ✅ Récupérer les commandes terminées (approved) - commands, err := database.GetCompletedCommandsByUsername(usernameStr) - if err != nil { - log.Printf("❌ [HISTORY] Erreur: %v", err) - c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Erreur lors de la récupération de l'historique", - }) - return - } - - log.Printf("✅ [HISTORY] %d commandes terminées trouvées", len(commands)) - - // ✅ Récupérer les infos client pour statistiques - client, err := database.GetClientByUsername(usernameStr) - - // ✅ Récupérer les noms et clés des pools de points - poolNames := []string{"Pool 1", "Pool 2"} - var poolKeys []string - if settings, sErr := database.GetSettings(); sErr == nil && len(settings.PointsPools) > 0 { - poolNames = make([]string, len(settings.PointsPools)) - poolKeys = make([]string, len(settings.PointsPools)) - for i, p := range settings.PointsPools { - poolNames[i] = p.Name - poolKeys[i] = p.Key - } - } - - response := gin.H{ - "success": true, - "commands": commands, - "count": len(commands), - } - - if err == nil && client != nil { - // Construire le tableau depuis points_extra[poolKey] pour tous les pools (stockage dynamique) - poolPoints := make([]int, len(poolKeys)) - for i, key := range poolKeys { - if key != "" { - poolPoints[i] = client.PointsExtra[key] - } - } - - log.Printf("📊 [HISTORY] pool_names=%v pool_keys=%v pool_points=%v extra=%v", - poolNames, poolKeys, poolPoints, client.PointsExtra) - - response["client_stats"] = gin.H{ - "username": client.Username, - "total_commands": client.Command, - "points_extra": client.PointsExtra, - "pool_points": poolPoints, - "pool_names": poolNames, - "penalties": client.Amende, - } - } - - c.JSON(http.StatusOK, response) -} - // GetMyCompletedOrdersWithItems récupère l'historique avec les détails des items // GET /api/v1/my-commands/history/detailed func GetMyCompletedOrdersWithItems(c *gin.Context) { diff --git a/backend/gestion/handlers/redis_services.go b/backend/gestion/handlers/redis_services.go index 6ded3e41..0bc2d4c8 100644 --- a/backend/gestion/handlers/redis_services.go +++ b/backend/gestion/handlers/redis_services.go @@ -947,36 +947,6 @@ func SubtractClientPointsAdmin(c *gin.Context) { }) } -func GetRealtimeStats(c *gin.Context) { - userRole := c.GetString("role") - if userRole != "admin" { - c.JSON(http.StatusForbidden, gin.H{"error": "Accès refusé"}) - return - } - - stats, err := db.Redis.HGetAll(db.RedisCtx, "stats:realtime").Result() - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Erreur lors de la récupération des statistiques", - }) - return - } - - if len(stats) == 0 { - c.JSON(http.StatusOK, gin.H{ - "success": true, - "message": "Aucune statistique disponible pour le moment", - "stats": map[string]interface{}{}, - }) - return - } - - c.JSON(http.StatusOK, gin.H{ - "success": true, - "stats": stats, - }) -} - func refreshETAForActivDelivery(username string, lat, lon float64) { // 1. Récupérer le statut actuel du livreur statusKey := fmt.Sprintf("delivery:status:%s", username) diff --git a/backend/gestion/handlers/traffic.go b/backend/gestion/handlers/traffic.go deleted file mode 100644 index 92d88281..00000000 --- a/backend/gestion/handlers/traffic.go +++ /dev/null @@ -1,52 +0,0 @@ -// ============================================ -// handlers/traffic_handlers.go - COMPLET -// ============================================ - -package handlers - -import ( - "encoding/json" - "strconv" -) - -// getFloatFromMap récupère un float64 depuis une map avec différents types -func getFloatFromMap(m map[string]any, key string) (float64, bool) { - value, exists := m[key] - if !exists || value == nil { - return 0, false - } - - switch v := value.(type) { - case float64: - return v, true - case float32: - return float64(v), true - case int: - return float64(v), true - case int64: - return float64(v), true - case int32: - return float64(v), true - case json.Number: - f, err := v.Float64() - if err != nil { - return 0, false - } - return f, true - case string: - f, err := strconv.ParseFloat(v, 64) - if err != nil { - return 0, false - } - return f, true - case []byte: - s := string(v) - f, err := strconv.ParseFloat(s, 64) - if err != nil { - return 0, false - } - return f, true - default: - return 0, false - } -} diff --git a/backend/gestion/handlers/update_profile.go b/backend/gestion/handlers/update_profile.go index 337b6fba..f91eab91 100644 --- a/backend/gestion/handlers/update_profile.go +++ b/backend/gestion/handlers/update_profile.go @@ -198,9 +198,6 @@ func UpdateClientByAdmin(c *gin.Context) { 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 { diff --git a/backend/gestion/services/adresses_correction.go b/backend/gestion/services/adresses_correction.go index b879d6a6..ec5f8dc5 100644 --- a/backend/gestion/services/adresses_correction.go +++ b/backend/gestion/services/adresses_correction.go @@ -172,7 +172,7 @@ func (acs *AddressCorrectionService) queryNominatim(query string, limit int) ([] defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("Nominatim status %d", resp.StatusCode) + return nil, fmt.Errorf("nominatim status %d", resp.StatusCode) } body, err := io.ReadAll(resp.Body) diff --git a/backend/gestion/utils/auth.go b/backend/gestion/utils/auth.go index a8e2ac07..6583cb43 100644 --- a/backend/gestion/utils/auth.go +++ b/backend/gestion/utils/auth.go @@ -32,29 +32,13 @@ func NormalizePhoneNumber(phone string) string { } func CheckRoleAdmin(c *gin.Context, role string) bool { - if role == "admin" { - return true - } - return false + return role == "admin" } func CheckRoleClient(c *gin.Context, role string) bool { - if role == "client" { - return true - } - return false + return role == "client" } func CheckRoleCabine(c *gin.Context, role string) bool { - if role == "cabine" { - return true - } - return false -} - -func CheckRoleLivreur(c *gin.Context, role string) bool { - if role == "livreur" { - return true - } - return false + return role == "cabine" } diff --git a/backend/gestion/utils/cabine.go b/backend/gestion/utils/cabine.go deleted file mode 100644 index 9595db8f..00000000 --- a/backend/gestion/utils/cabine.go +++ /dev/null @@ -1,14 +0,0 @@ -package utils - -import ( - "gestion/db" -) - -func CheckCommand(commandID int, database *db.Database) bool { - _, err := database.GetCommandByID(commandID) - if err != nil { - return false - } - - return true -} diff --git a/backend/gestion/utils/utils.go b/backend/gestion/utils/utils.go index 53635094..87e5050f 100644 --- a/backend/gestion/utils/utils.go +++ b/backend/gestion/utils/utils.go @@ -3,21 +3,10 @@ package utils import ( "crypto/rand" "fmt" - "gestion/db" - "gestion/models" "math/big" - "os" "path/filepath" ) -// -------------------------------------------- -// MEDIA -// -------------------------------------------- - -func GetMediaForProduct(productID int) ([]models.Media, error) { - return db.DB.GetMediaByProductID(productID) -} - // -------------------------------------------- // FILES NAMES // -------------------------------------------- @@ -28,7 +17,3 @@ func GenerateUniqueFileName(productName string, originalFileName string) string ext := filepath.Ext(originalFileName) return fmt.Sprintf("%s_%s%s", productName, randomString, ext) } - -func DeleteOldFile(filePath string) error { - return os.Remove(filePath) -} diff --git a/backend/gestion/workers/payment_checker.go b/backend/gestion/workers/payment_checker.go index f9504bc3..e1022774 100644 --- a/backend/gestion/workers/payment_checker.go +++ b/backend/gestion/workers/payment_checker.go @@ -7,16 +7,6 @@ import ( "time" ) -func StartPaymentChecker(database *db.Database, np *services.NowPaymentsClient, interval time.Duration) { - go func() { - for { - time.Sleep(interval) - checkPendingCryptoPayments(database, np) - } - }() - log.Printf("[CRON] payment checker démarré (toutes les %s)", interval) -} - // StartDynamicPaymentChecker démarre un checker qui recharge le client NowPayments à chaque tick // (permet de prendre en compte les changements de clé API sans redémarrer) func StartDynamicPaymentChecker(database *db.Database, clientFn func() *services.NowPaymentsClient, interval time.Duration) {