chore: build
Backend - Build & Lint / build (push) Failing after 20m59s

This commit is contained in:
2026-07-01 21:53:04 +02:00
parent c8d1c75ee9
commit cc3a283fba
4 changed files with 116 additions and 64 deletions
+4 -2
View File
@@ -53,7 +53,7 @@ func InitDB() *Database {
// Configuration du pool de connexions // Configuration du pool de connexions
db.SetMaxOpenConns(50) db.SetMaxOpenConns(50)
db.SetMaxIdleConns(10) db.SetMaxIdleConns(10)
db.SetConnMaxLifetime(5 * time.Minute) db.SetConnMaxLifetime(30 * time.Minute)
// Tester la connexion // Tester la connexion
if err = db.Ping(); err != nil { if err = db.Ping(); err != nil {
@@ -66,7 +66,9 @@ func InitDB() *Database {
gormDB, err := gorm.Open(postgres.New(postgres.Config{ gormDB, err := gorm.Open(postgres.New(postgres.Config{
Conn: db, Conn: db,
}), &gorm.Config{ }), &gorm.Config{
Logger: logger.Default.LogMode(logger.Silent), SkipDefaultTransaction: true,
PrepareStmt: true,
Logger: logger.Default.LogMode(logger.Silent),
}) })
if err != nil { if err != nil {
log.Fatalf("❌ Erreur initialisation GORM: %v", err) log.Fatalf("❌ Erreur initialisation GORM: %v", err)
+27 -7
View File
@@ -57,15 +57,35 @@ type AdminStatsFilters struct {
ResetDoses time.Time ResetDoses time.Time
} }
// LoadAdminStatsFilters lit toutes les dates de reset nécessaires au dashboard. // LoadAdminStatsFilters lit toutes les dates de reset en une seule requête.
func (d *Database) LoadAdminStatsFilters() AdminStatsFilters { func (d *Database) LoadAdminStatsFilters() AdminStatsFilters {
keys := []string{
"stats_reset_commandes_at",
"stats_reset_revenus_at",
"stats_reset_produits_at",
"stats_reset_heures_at",
"stats_reset_jours_at",
"stats_reset_doses_at",
}
var rows []struct {
Key string `gorm:"column:key"`
Value string `gorm:"column:value"`
}
d.GDB.Table("app_settings").Select("key, value").Where("key IN ?", keys).Scan(&rows)
m := make(map[string]time.Time, len(keys))
for _, r := range rows {
if t, err := time.Parse(time.RFC3339, r.Value); err == nil {
m[r.Key] = t
}
}
return AdminStatsFilters{ return AdminStatsFilters{
ResetCommandes: d.ReadResetAt("stats_reset_commandes_at"), ResetCommandes: m["stats_reset_commandes_at"],
ResetRevenus: d.ReadResetAt("stats_reset_revenus_at"), ResetRevenus: m["stats_reset_revenus_at"],
ResetProduits: d.ReadResetAt("stats_reset_produits_at"), ResetProduits: m["stats_reset_produits_at"],
ResetHeures: d.ReadResetAt("stats_reset_heures_at"), ResetHeures: m["stats_reset_heures_at"],
ResetJours: d.ReadResetAt("stats_reset_jours_at"), ResetJours: m["stats_reset_jours_at"],
ResetDoses: d.ReadResetAt("stats_reset_doses_at"), ResetDoses: m["stats_reset_doses_at"],
} }
} }
+9 -1
View File
@@ -477,7 +477,15 @@ func ValidateBasket(c *gin.Context) {
priceAmt, _ := payResp.PriceAmount.Float64() priceAmt, _ := payResp.PriceAmount.Float64()
payAmt, _ := payResp.PayAmount.Float64() payAmt, _ := payResp.PayAmount.Float64()
_, _ = database.CreateCryptoPayment(commandID, payResp.PaymentID.String(), payResp.Status, payResp.PriceCurrency, payResp.PayCurrency, payResp.PayAddress, priceAmt, payAmt) if _, err := database.CreateCryptoPayment(commandID, payResp.PaymentID.String(), payResp.Status, payResp.PriceCurrency, payResp.PayCurrency, payResp.PayAddress, priceAmt, payAmt); err != nil {
log.Printf("❌ [CHECKOUT] Erreur enregistrement paiement crypto (commande %d, nowpayment %s): %v", commandID, payResp.PaymentID.String(), err)
_ = database.CancelCryptoCommand(commandID)
if referralUsed > 0 {
_ = database.CreditClientReferral(usernameStr, referralUsed)
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur interne lors de l'enregistrement du paiement"})
return
}
log.Printf("✅ [CHECKOUT] Commande %d en attente paiement crypto (%s)", commandID, req.PayCurrency) log.Printf("✅ [CHECKOUT] Commande %d en attente paiement crypto (%s)", commandID, req.PayCurrency)
c.JSON(http.StatusCreated, gin.H{ c.JSON(http.StatusCreated, gin.H{
+76 -54
View File
@@ -1,6 +1,7 @@
package handlers package handlers
import ( import (
"context"
"fmt" "fmt"
"gestion/db" "gestion/db"
"gestion/models" "gestion/models"
@@ -8,6 +9,7 @@ import (
"time" "time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"golang.org/x/sync/errgroup"
) )
var weekdayNames = []string{"Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi"} var weekdayNames = []string{"Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi"}
@@ -200,10 +202,62 @@ func GetAdminStats(c *gin.Context) {
filters := database.LoadAdminStatsFilters() filters := database.LoadAdminStatsFilters()
// ── Commandes par jour de la semaine (non annulées) ──────────────────────── // Toutes les requêtes sont indépendantes — on les lance en parallèle.
var wdRows []models.WeekdayRow var (
database.OrderPerDaysPerWeeks(&wdRows, filters.ResetJours) wdRows []models.WeekdayRow
dayRows []models.DayRow
dayRevRows []models.DayRevenueRow
hourRows []models.HourRow
prodRows []models.ProductRow
qtyRows []models.QuantityBreakdownRow
dailyRows []models.DailyProductRow
totalOrders int64
totalRevenue float64
dailyTotalOrders int64
activeDays int64
last30Count int64
)
eg, _ := errgroup.WithContext(context.Background())
eg.Go(func() error { return database.OrderPerDaysPerWeeks(&wdRows, filters.ResetJours) })
eg.Go(func() error { return database.OrdersByDayLast30(&dayRows, filters.ResetCommandes) })
eg.Go(func() error { return database.RevenueByDayLast30(&dayRevRows, filters.ResetRevenus) })
eg.Go(func() error { return database.OrdersAndRevenueByHour(&hourRows, filters.ResetHeures) })
eg.Go(func() error { return database.TopProducts(&prodRows, filters.ResetProduits, 15) })
eg.Go(func() error { return database.QuantityBreakdown(&qtyRows, filters.ResetDoses) })
eg.Go(func() error { return database.DailyProductDetail(&dailyRows) })
eg.Go(func() error {
var err error
totalOrders, err = database.TotalOrders(filters.ResetCommandes)
return err
})
eg.Go(func() error {
var err error
totalRevenue, err = database.TotalRevenue(filters.ResetRevenus)
return err
})
eg.Go(func() error {
var err error
dailyTotalOrders, err = database.DailyOrdersCount()
return err
})
eg.Go(func() error {
var err error
activeDays, err = database.ActiveDaysLast30(filters.ResetCommandes)
return err
})
eg.Go(func() error {
var err error
last30Count, err = database.OrdersCountLast30(filters.ResetCommandes)
return err
})
if err := eg.Wait(); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur lors de la récupération des statistiques"})
return
}
// ── Commandes par jour de la semaine ──────────────────────────────────────
byWeekday := make([]gin.H, 7) byWeekday := make([]gin.H, 7)
wdMap := make(map[int]int, len(wdRows)) wdMap := make(map[int]int, len(wdRows))
for _, r := range wdRows { for _, r := range wdRows {
@@ -220,9 +274,6 @@ func GetAdminStats(c *gin.Context) {
} }
// ── Commandes par jour sur 30 jours ─────────────────────────────────────── // ── Commandes par jour sur 30 jours ───────────────────────────────────────
var dayRows []models.DayRow
database.OrdersByDayLast30(&dayRows, filters.ResetCommandes)
byDay := make([]gin.H, len(dayRows)) byDay := make([]gin.H, len(dayRows))
for i, r := range dayRows { for i, r := range dayRows {
byDay[i] = gin.H{ byDay[i] = gin.H{
@@ -232,10 +283,7 @@ func GetAdminStats(c *gin.Context) {
} }
} }
// ── Revenus par jour sur 30 jours (commandes approuvées) ───────────────── // ── Revenus par jour sur 30 jours ─────────────────────────────────────────
var dayRevRows []models.DayRevenueRow
database.RevenueByDayLast30(&dayRevRows, filters.ResetRevenus)
byDayRevenue := make([]gin.H, len(dayRevRows)) byDayRevenue := make([]gin.H, len(dayRevRows))
for i, r := range dayRevRows { for i, r := range dayRevRows {
byDayRevenue[i] = gin.H{ byDayRevenue[i] = gin.H{
@@ -245,10 +293,7 @@ func GetAdminStats(c *gin.Context) {
} }
} }
// ── Commandes & revenus par heure (non annulées) ───────────────────────── // ── Commandes & revenus par heure ─────────────────────────────────────────
var hourRows []models.HourRow
database.OrdersAndRevenueByHour(&hourRows, filters.ResetHeures)
hourMap := make(map[int]models.HourRow, len(hourRows)) hourMap := make(map[int]models.HourRow, len(hourRows))
for _, r := range hourRows { for _, r := range hourRows {
hourMap[r.Hour] = r hourMap[r.Hour] = r
@@ -264,10 +309,7 @@ func GetAdminStats(c *gin.Context) {
} }
} }
// ── Top produits (quantité vendue, commandes terminées) ─────────────────── // ── Top produits ──────────────────────────────────────────────────────────
var prodRows []models.ProductRow
database.TopProducts(&prodRows, filters.ResetProduits, 15)
topProducts := make([]gin.H, len(prodRows)) topProducts := make([]gin.H, len(prodRows))
topProductName := "" topProductName := ""
for i, r := range prodRows { for i, r := range prodRows {
@@ -285,10 +327,7 @@ func GetAdminStats(c *gin.Context) {
} }
} }
// ── Répartition des doses/quantités par produit ─────────────────────────── // ── Répartition des doses/quantités ───────────────────────────────────────
var qtyRows []models.QuantityBreakdownRow
database.QuantityBreakdown(&qtyRows, filters.ResetDoses)
type productGroup struct { type productGroup struct {
ProductID int ProductID int
Name string Name string
@@ -317,8 +356,6 @@ func GetAdminStats(c *gin.Context) {
"revenue": r.Revenue, "revenue": r.Revenue,
}) })
} }
// Trier par total de commandes décroissant, garder 15 max
for i := 0; i < len(groups)-1; i++ { for i := 0; i < len(groups)-1; i++ {
for j := i + 1; j < len(groups); j++ { for j := i + 1; j < len(groups); j++ {
if groups[j].TotalOrders > groups[i].TotalOrders { if groups[j].TotalOrders > groups[i].TotalOrders {
@@ -329,22 +366,18 @@ func GetAdminStats(c *gin.Context) {
if len(groups) > 15 { if len(groups) > 15 {
groups = groups[:15] groups = groups[:15]
} }
byQuantity := make([]gin.H, len(groups)) byQuantity := make([]gin.H, len(groups))
for i, g := range groups { for i, grp := range groups {
byQuantity[i] = gin.H{ byQuantity[i] = gin.H{
"product_id": g.ProductID, "product_id": grp.ProductID,
"name": g.Name, "name": grp.Name,
"category_color": g.CategoryColor, "category_color": grp.CategoryColor,
"total_orders": g.TotalOrders, "total_orders": grp.TotalOrders,
"quantities": g.Quantities, "quantities": grp.Quantities,
} }
} }
// ── Détail du jour (catégorie → produits) ──────────────────────────────── // ── Détail du jour ────────────────────────────────────────────────────────
var dailyRows []models.DailyProductRow
database.DailyProductDetail(&dailyRows)
type dailyCatGroup struct { type dailyCatGroup struct {
Category string Category string
CategoryColor string CategoryColor string
@@ -356,7 +389,6 @@ func GetAdminStats(c *gin.Context) {
dailyCatIdx := map[string]int{} dailyCatIdx := map[string]int{}
dailyTotalRevenue := 0.0 dailyTotalRevenue := 0.0
dailyTotalQty := 0.0 dailyTotalQty := 0.0
for _, r := range dailyRows { for _, r := range dailyRows {
dailyTotalRevenue += r.Revenue dailyTotalRevenue += r.Revenue
dailyTotalQty += r.TotalQuantity dailyTotalQty += r.TotalQuantity
@@ -379,31 +411,21 @@ func GetAdminStats(c *gin.Context) {
"revenue": r.Revenue, "revenue": r.Revenue,
}) })
} }
dailyCatsJSON := make([]gin.H, len(dailyCats)) dailyCatsJSON := make([]gin.H, len(dailyCats))
for i, g := range dailyCats { for i, grp := range dailyCats {
dailyCatsJSON[i] = gin.H{ dailyCatsJSON[i] = gin.H{
"category": g.Category, "category": grp.Category,
"category_color": g.CategoryColor, "category_color": grp.CategoryColor,
"total_quantity": g.TotalQuantity, "total_quantity": grp.TotalQuantity,
"total_revenue": g.TotalRevenue, "total_revenue": grp.TotalRevenue,
"products": g.Products, "products": grp.Products,
} }
} }
dailyTotalOrders, _ := database.DailyOrdersCount()
// ── Résumé global ───────────────────────────────────────────────────────── // ── Résumé global ─────────────────────────────────────────────────────────
totalOrders, _ := database.TotalOrders(filters.ResetCommandes)
totalRevenue, _ := database.TotalRevenue(filters.ResetRevenus)
avgPerDay := 0.0 avgPerDay := 0.0
if totalOrders > 0 { if totalOrders > 0 && activeDays > 0 {
activeDays, _ := database.ActiveDaysLast30(filters.ResetCommandes) avgPerDay = float64(last30Count) / float64(activeDays)
if activeDays > 0 {
last30Count, _ := database.OrdersCountLast30(filters.ResetCommandes)
avgPerDay = float64(last30Count) / float64(activeDays)
}
} }
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{