chore: build
Backend - Build & Lint / build (push) Failing after 31m35s

This commit is contained in:
2026-07-06 21:22:30 +02:00
parent 75a241dadb
commit 5cd8ada828
10 changed files with 664 additions and 177 deletions
+162 -151
View File
@@ -96,64 +96,66 @@ func (d *Database) CreateCommand(username string) (*models.Command, error) {
adresse = clientCheck.Username
}
basketItems, totalPrix, err := d.fetchBasketItems(username)
if err != nil {
return nil, err
}
if len(basketItems) == 0 {
return nil, fmt.Errorf("le panier est vide")
}
var cmdResult struct {
ID int `gorm:"column:id"`
ClientOrderID int `gorm:"column:client_order_id"`
CreatedAt time.Time `gorm:"column:created_at"`
UpdatedAt time.Time `gorm:"column:updated_at"`
}
err = d.GDB.Raw(`
INSERT INTO commandes (username, status, adresse, total_prix, client_order_id, created_at, updated_at)
VALUES (?, ?, ?, ?, (SELECT COALESCE(MAX(client_order_id), 0) + 1 FROM commandes WHERE username = ?), CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
RETURNING id, client_order_id, created_at, updated_at`,
username, "pending", adresse, totalPrix, username).Scan(&cmdResult).Error
if err != nil {
return nil, fmt.Errorf("erreur lors de la création de la commande: %w", err)
}
commandID := cmdResult.ID
productIDs := make([]int, 0, len(basketItems))
for _, item := range basketItems {
productIDs = append(productIDs, item.ProductID)
}
productNames, _ := d.GetProductNamesByIDs(productIDs)
cmdItems := make([]models.CommandItem, 0, len(basketItems))
for _, item := range basketItems {
productName := productNames[item.ProductID]
if productName == "" {
productName = "Produit inconnu"
var command *models.Command
err := d.GDB.Transaction(func(tx *gorm.DB) error {
// Verrou sur le panier : un double-submit concurrent du même client se
// bloque ici puis échoue proprement ("panier vide") une fois le premier
// passage terminé, au lieu de créer une commande fantôme.
var basketItems []basketItem
if err := tx.Raw(`SELECT product_id, quantity, price, is_reward, reward_pool_key FROM baskets WHERE username = ? FOR UPDATE`, username).Scan(&basketItems).Error; err != nil {
return fmt.Errorf("erreur récupération panier: %w", err)
}
cmdItems = append(cmdItems, models.CommandItem{
CommandID: commandID,
Produit: productName,
ProductID: item.ProductID,
Quantity: item.Quantity,
Price: item.Price,
IsReward: item.IsReward,
RewardPoolKey: item.RewardPoolKey,
})
}
if err := d.GDB.Create(&cmdItems).Error; err != nil {
return nil, fmt.Errorf("erreur lors de l'insertion des items: %w", err)
}
// Décrémenter le stock et vider le panier de manière atomique.
if err := d.GDB.Transaction(func(tx *gorm.DB) error {
if len(basketItems) == 0 {
return fmt.Errorf("le panier est vide")
}
totalPrix := 0.0
for _, item := range basketItems {
if item.IsReward {
continue
totalPrix += item.Price
}
var cmdResult struct {
ID int `gorm:"column:id"`
ClientOrderID int `gorm:"column:client_order_id"`
}
if err := tx.Raw(`
INSERT INTO commandes (username, status, adresse, total_prix, client_order_id, created_at, updated_at)
VALUES (?, ?, ?, ?, (SELECT COALESCE(MAX(client_order_id), 0) + 1 FROM commandes WHERE username = ?), CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
RETURNING id, client_order_id`,
username, "pending", adresse, totalPrix, username).Scan(&cmdResult).Error; err != nil {
return fmt.Errorf("erreur lors de la création de la commande: %w", err)
}
commandID := cmdResult.ID
productIDs := make([]int, 0, len(basketItems))
for _, item := range basketItems {
productIDs = append(productIDs, item.ProductID)
}
productNames, _ := d.GetProductNamesByIDs(productIDs)
cmdItems := make([]models.CommandItem, 0, len(basketItems))
for _, item := range basketItems {
productName := productNames[item.ProductID]
if productName == "" {
productName = "Produit inconnu"
}
cmdItems = append(cmdItems, models.CommandItem{
CommandID: commandID,
Produit: productName,
ProductID: item.ProductID,
Quantity: item.Quantity,
Price: item.Price,
IsReward: item.IsReward,
RewardPoolKey: item.RewardPoolKey,
})
}
if err := tx.Create(&cmdItems).Error; err != nil {
return fmt.Errorf("erreur lors de l'insertion des items: %w", err)
}
// Les articles récompense (payés en points) restent des produits physiques
// réellement distribués : le stock doit être décrémenté comme pour un
// article payant.
for _, item := range basketItems {
var currentStock float64
if err := tx.Raw(`SELECT stock FROM products WHERE id = ? FOR UPDATE`, item.ProductID).Scan(&currentStock).Error; err != nil {
return fmt.Errorf("erreur lecture stock produit %d: %w", item.ProductID, err)
@@ -165,16 +167,20 @@ func (d *Database) CreateCommand(username string) (*models.Command, error) {
return fmt.Errorf("erreur décrémentation stock produit %d: %w", item.ProductID, err)
}
}
return tx.Exec(`DELETE FROM baskets WHERE username = ?`, username).Error
}); err != nil {
return nil, err
}
if err := tx.Exec(`DELETE FROM baskets WHERE username = ?`, username).Error; err != nil {
return err
}
command := &models.Command{
ID: commandID,
ClientOrderID: cmdResult.ClientOrderID,
Status: "pending",
Total: totalPrix,
command = &models.Command{
ID: commandID,
ClientOrderID: cmdResult.ClientOrderID,
Status: "pending",
Total: totalPrix,
}
return nil
})
if err != nil {
return nil, err
}
return command, nil
@@ -203,83 +209,84 @@ func (d *Database) CreateCommandWithAddress(username, deliveryAddress string) (*
clientTelephone = sanitizeString(client.Telephone)
}
basketItems, totalPrix, err := d.fetchBasketItems(username)
if err != nil {
log.Printf("❌ Erreur query basket: %v", err)
return nil, err
}
if len(basketItems) == 0 {
return nil, fmt.Errorf("le panier est vide")
}
for _, item := range basketItems {
if item.ProductID <= 0 || item.Quantity <= 0 || item.Price < 0 {
return nil, fmt.Errorf("données panier invalides")
var (
command *models.Command
totalPrix float64
)
err = d.GDB.Transaction(func(tx *gorm.DB) error {
// Verrou sur le panier : un double-submit concurrent du même client se
// bloque ici puis échoue proprement ("panier vide") une fois le premier
// passage terminé, au lieu de créer une commande fantôme.
var basketItems []basketItem
if err := tx.Raw(`SELECT product_id, quantity, price, is_reward, reward_pool_key FROM baskets WHERE username = ? FOR UPDATE`, username).Scan(&basketItems).Error; err != nil {
return fmt.Errorf("erreur récupération panier: %w", err)
}
}
if totalPrix <= 0 || totalPrix > 100000 {
return nil, fmt.Errorf("montant de commande invalide: %.2f€", totalPrix)
}
var cmdResult struct {
ID int `gorm:"column:id"`
ClientOrderID int `gorm:"column:client_order_id"`
CreatedAt time.Time `gorm:"column:created_at"`
UpdatedAt time.Time `gorm:"column:updated_at"`
}
err = d.GDB.Raw(`
INSERT INTO commandes (username, status, adresse, total_prix, client_order_id, created_at, updated_at)
VALUES (?, ?, ?, ?, (SELECT COALESCE(MAX(client_order_id), 0) + 1 FROM commandes WHERE username = ?), CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
RETURNING id, client_order_id, created_at, updated_at`,
username, "pending", deliveryAddress, totalPrix, username).Scan(&cmdResult).Error
if err != nil {
return nil, fmt.Errorf("erreur création commande: %w", err)
}
commandID := cmdResult.ID
productIDs2 := make([]int, 0, len(basketItems))
for _, item := range basketItems {
productIDs2 = append(productIDs2, item.ProductID)
}
productNames2, _ := d.GetProductNamesByIDs(productIDs2)
batchItems := make([]commandItemFull, 0, len(basketItems))
for _, item := range basketItems {
productName := productNames2[item.ProductID]
if productName == "" {
productName = fmt.Sprintf("Produit #%d", item.ProductID)
if len(basketItems) == 0 {
return fmt.Errorf("le panier est vide")
}
batchItems = append(batchItems, commandItemFull{
CommandID: commandID,
Produit: productName,
ProductID: item.ProductID,
Quantite: item.Quantity,
Prix: item.Price,
IsReward: item.IsReward,
RewardPoolKey: item.RewardPoolKey,
ClientUsername: username,
ClientNom: clientNom,
ClientPrenom: clientPrenom,
ClientTelephone: clientTelephone,
DeliveryAddress: deliveryAddress,
Status: "pending",
})
// Stock déjà déduit à l'ajout au panier — ne pas déduire une seconde fois ici.
}
if err := d.InsertCommandItemsBatch(batchItems); err != nil {
log.Printf("❌ Erreur INSERT command_items batch: %v", err)
return nil, fmt.Errorf("erreur insertion items: %w", err)
}
// Décrémenter le stock et vider le panier de manière atomique.
if err := d.GDB.Transaction(func(tx *gorm.DB) error {
for _, item := range basketItems {
if item.IsReward {
continue
if item.ProductID <= 0 || item.Quantity <= 0 || item.Price < 0 {
return fmt.Errorf("données panier invalides")
}
totalPrix += item.Price
}
if totalPrix <= 0 || totalPrix > 100000 {
return fmt.Errorf("montant de commande invalide: %.2f€", totalPrix)
}
var cmdResult struct {
ID int `gorm:"column:id"`
ClientOrderID int `gorm:"column:client_order_id"`
CreatedAt time.Time `gorm:"column:created_at"`
UpdatedAt time.Time `gorm:"column:updated_at"`
}
if err := tx.Raw(`
INSERT INTO commandes (username, status, adresse, total_prix, client_order_id, created_at, updated_at)
VALUES (?, ?, ?, ?, (SELECT COALESCE(MAX(client_order_id), 0) + 1 FROM commandes WHERE username = ?), CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
RETURNING id, client_order_id, created_at, updated_at`,
username, "pending", deliveryAddress, totalPrix, username).Scan(&cmdResult).Error; err != nil {
return fmt.Errorf("erreur création commande: %w", err)
}
commandID := cmdResult.ID
productIDs2 := make([]int, 0, len(basketItems))
for _, item := range basketItems {
productIDs2 = append(productIDs2, item.ProductID)
}
productNames2, _ := d.GetProductNamesByIDs(productIDs2)
batchItems := make([]commandItemFull, 0, len(basketItems))
for _, item := range basketItems {
productName := productNames2[item.ProductID]
if productName == "" {
productName = fmt.Sprintf("Produit #%d", item.ProductID)
}
batchItems = append(batchItems, commandItemFull{
CommandID: commandID,
Produit: productName,
ProductID: item.ProductID,
Quantite: item.Quantity,
Prix: item.Price,
IsReward: item.IsReward,
RewardPoolKey: item.RewardPoolKey,
ClientUsername: username,
ClientNom: clientNom,
ClientPrenom: clientPrenom,
ClientTelephone: clientTelephone,
DeliveryAddress: deliveryAddress,
Status: "pending",
})
}
if err := tx.Create(&batchItems).Error; err != nil {
return fmt.Errorf("erreur insertion items: %w", err)
}
// Les articles récompense (payés en points) restent des produits physiques
// réellement distribués : le stock doit être décrémenté comme pour un
// article payant.
for _, item := range basketItems {
var currentStock float64
if err := tx.Raw(`SELECT stock FROM products WHERE id = ? FOR UPDATE`, item.ProductID).Scan(&currentStock).Error; err != nil {
return fmt.Errorf("erreur lecture stock produit %d: %w", item.ProductID, err)
@@ -291,29 +298,33 @@ func (d *Database) CreateCommandWithAddress(username, deliveryAddress string) (*
return fmt.Errorf("erreur décrémentation stock produit %d: %w", item.ProductID, err)
}
}
return tx.Exec(`DELETE FROM baskets WHERE username = ?`, username).Error
}); err != nil {
log.Printf("❌ Erreur décrément stock / vidage panier: %v", err)
if err := tx.Exec(`DELETE FROM baskets WHERE username = ?`, username).Error; err != nil {
return err
}
command = &models.Command{
ID: commandID,
ClientOrderID: cmdResult.ClientOrderID,
Username: username,
Status: "pending",
Total: totalPrix,
DeliveryAddress: deliveryAddress,
CreatedAt: cmdResult.CreatedAt,
UpdatedAt: cmdResult.UpdatedAt,
}
return nil
})
if err != nil {
log.Printf("❌ Erreur création commande: %v", err)
return nil, err
}
sanitizedAddress := sanitizeLogMessage(deliveryAddress)
d.AddCommandLog(commandID, "created",
d.AddCommandLog(command.ID, "created",
fmt.Sprintf("Commande créée - Adresse: %s - Total: %.2f€ - Client: %s %s",
sanitizedAddress, totalPrix, sanitizeLogMessage(clientNom), sanitizeLogMessage(clientPrenom)),
username)
command := &models.Command{
ID: commandID,
ClientOrderID: cmdResult.ClientOrderID,
Username: username,
Status: "pending",
Total: totalPrix,
DeliveryAddress: deliveryAddress,
CreatedAt: cmdResult.CreatedAt,
UpdatedAt: cmdResult.UpdatedAt,
}
return command, nil
}