diff --git a/backend/gestion/db/db_notifications.go b/backend/gestion/db/db_notifications.go index e9e2e99c..8bd09d3c 100644 --- a/backend/gestion/db/db_notifications.go +++ b/backend/gestion/db/db_notifications.go @@ -9,6 +9,18 @@ import ( "time" ) +// sendTelegramNotif envoie via le bot principal, puis lbtelegram (BOT1/BOT2) en fallback. +func sendTelegramNotif(chatID int64, text string) { + if err := services.TelegramBot.SendMessage(chatID, text); err != nil { + log.Printf("⚠️ [NOTIF] bot principal échoué: %v — fallback lbtelegram", err) + if services.LBTelegram != nil && services.LBTelegram.IsConfigured() { + if err2 := services.LBTelegram.SendNotification(chatID, text); err2 != nil { + log.Printf("⚠️ [NOTIF] lbtelegram aussi échoué: %v", err2) + } + } + } +} + func (d *Database) NotifyClient(username string, commandID int, notifType, message string) error { notifKey := fmt.Sprintf("notifications:%s", username) @@ -26,11 +38,7 @@ func (d *Database) NotifyClient(username string, commandID int, notifType, messa if services.TelegramBot != nil && services.TelegramBot.IsNotificationsEnabled() { if chatID, ok, err := d.GetClientTelegramChatID(username); err == nil && ok { - go func(id int64, msg string) { - if err := services.TelegramBot.SendNotif(id, fmt.Sprintf("🔔 Notification\n\n%s", msg)); err != nil { - log.Printf("⚠️ [NOTIF] notification client échouée pour %s: %v", username, err) - } - }(chatID, message) + go sendTelegramNotif(chatID, fmt.Sprintf("🔔 Notification\n\n%s", message)) } } @@ -56,11 +64,7 @@ func (d *Database) NotifyLivreur(username string, commandID int, notifType, mess if services.TelegramBot != nil && services.TelegramBot.IsNotificationsEnabled() { if chatID, ok, err := d.GetUserTelegramChatID(username); err == nil && ok { - go func(id int64, msg string) { - if err := services.TelegramBot.SendNotif(id, fmt.Sprintf("🔔 Notification\n\n%s", msg)); err != nil { - log.Printf("⚠️ [NOTIF] notification livreur échouée pour %s: %v", username, err) - } - }(chatID, message) + go sendTelegramNotif(chatID, fmt.Sprintf("🔔 Notification\n\n%s", message)) } } @@ -98,12 +102,7 @@ func (d *Database) NotifyAllAdminCabine(commandID int, clientUsername, deliveryA if services.TelegramBot != nil && services.TelegramBot.IsNotificationsEnabled() { if chatID, ok, err := d.GetUserTelegramChatID(u.Username); err == nil && ok { capturedChatID := chatID - capturedMsg := msg - go func(id int64, m string) { - if err := services.TelegramBot.SendNotif(id, fmt.Sprintf("🔔 Nouvelle commande\n\n%s", m)); err != nil { - log.Printf("⚠️ [NOTIF] notification admin/cabine échouée: %v", err) - } - }(capturedChatID, capturedMsg) + go sendTelegramNotif(capturedChatID, fmt.Sprintf("🔔 Nouvelle commande\n\n%s", msg)) } } count++ @@ -141,12 +140,7 @@ func (d *Database) NotifyAllAdminCabineAlert(alertID int, livreurUsername, alert if services.TelegramBot != nil && services.TelegramBot.IsNotificationsEnabled() { if chatID, ok, err := d.GetUserTelegramChatID(u.Username); err == nil && ok { capturedChatID := chatID - capturedBody := body - go func(id int64, m string) { - if err := services.TelegramBot.SendNotif(id, fmt.Sprintf("🚨 Alerte livreur\n\n%s", m)); err != nil { - log.Printf("⚠️ [NOTIF] notification alerte échouée: %v", err) - } - }(capturedChatID, capturedBody) + go sendTelegramNotif(capturedChatID, fmt.Sprintf("🚨 Alerte livreur\n\n%s", body)) } } count++ diff --git a/backend/gestion/db/db_settings.go b/backend/gestion/db/db_settings.go index 8dd5fca1..996bf74e 100644 --- a/backend/gestion/db/db_settings.go +++ b/backend/gestion/db/db_settings.go @@ -154,11 +154,6 @@ func (d *Database) GetSettings() (models.AppSettings, error) { settings.TelegramBotUsername = row.Value case "telegram_notifications_enabled": settings.TelegramNotificationsEnabled = row.Value == "true" - case "telegram_notif_bots": - var bots []models.TelegramBotConfig - if err := json.Unmarshal([]byte(row.Value), &bots); err == nil { - settings.TelegramNotifBots = bots - } case "delivery_mode": var mode models.DeliveryModeConfig if err := json.Unmarshal([]byte(row.Value), &mode); err == nil { @@ -254,13 +249,6 @@ func (d *Database) UpdateSettings(s models.AppSettings) error { {"telegram_bot_token", s.TelegramBotToken}, {"telegram_bot_username", s.TelegramBotUsername}, {"telegram_notifications_enabled", boolStr(s.TelegramNotificationsEnabled)}, - {"telegram_notif_bots", func() string { - if s.TelegramNotifBots == nil { - s.TelegramNotifBots = []models.TelegramBotConfig{} - } - b, _ := json.Marshal(s.TelegramNotifBots) - return string(b) - }()}, {"telegram_2fa_enabled", boolStr(s.Telegram2FAEnabled)}, {"delivery_mode", string(deliveryModeJSON)}, {"shop_name", s.ShopName}, diff --git a/backend/gestion/handlers/settings.go b/backend/gestion/handlers/settings.go index e5453c3e..3482d2cb 100644 --- a/backend/gestion/handlers/settings.go +++ b/backend/gestion/handlers/settings.go @@ -85,7 +85,6 @@ func UpdateSettings(c *gin.Context) { if services.TelegramBot != nil { services.TelegramBot.Reload(req.TelegramBotToken, req.TelegramBotUsername) services.TelegramBot.SetNotificationsEnabled(req.TelegramNotificationsEnabled) - services.TelegramBot.ReloadNotifBots(req.TelegramNotifBots) if req.TelegramBotToken != "" { log.Printf("✅ [SETTINGS] Service Telegram rechargé (username: %s)", req.TelegramBotUsername) if webhookURL := os.Getenv("TELEGRAM_WEBHOOK_URL"); webhookURL != "" { diff --git a/backend/gestion/main.go b/backend/gestion/main.go index 4fed49f1..1c5c65b5 100644 --- a/backend/gestion/main.go +++ b/backend/gestion/main.go @@ -62,7 +62,6 @@ func main() { if dbSettings, err := database.GetSettings(); err == nil { telegramService.Reload(dbSettings.TelegramBotToken, dbSettings.TelegramBotUsername) telegramService.SetNotificationsEnabled(dbSettings.TelegramNotificationsEnabled) - telegramService.ReloadNotifBots(dbSettings.TelegramNotifBots) if dbSettings.TelegramBotToken != "" { log.Printf("✅ [TELEGRAM] Config chargée depuis la DB (username: %s)", dbSettings.TelegramBotUsername) if webhookURL := os.Getenv("TELEGRAM_WEBHOOK_URL"); webhookURL != "" { diff --git a/backend/gestion/models/settings.go b/backend/gestion/models/settings.go index d8188f30..a15b7f7c 100644 --- a/backend/gestion/models/settings.go +++ b/backend/gestion/models/settings.go @@ -1,11 +1,5 @@ package models -// TelegramBotConfig représente un bot de notification configuré par l'admin -type TelegramBotConfig struct { - Token string `json:"token"` - Username string `json:"username"` -} - type PostalZone struct { Name string `json:"name"` MinAmount float64 `json:"min_amount"` @@ -100,8 +94,7 @@ type AppSettings struct { PostalZones []PostalZone `json:"postal_zones"` // zones de livraison avec minimum de commande TelegramBotToken string `json:"telegram_bot_token"` // token du bot Telegram (BotFather) — pour le webhook de liaison TelegramBotUsername string `json:"telegram_bot_username"` // username du bot (sans @) - TelegramNotificationsEnabled bool `json:"telegram_notifications_enabled"` // activer/désactiver les notifications Telegram - TelegramNotifBots []TelegramBotConfig `json:"telegram_notif_bots"` // bots de notification avec failover automatique + TelegramNotificationsEnabled bool `json:"telegram_notifications_enabled"` // activer/désactiver les notifications Telegram DeliveryMode DeliveryModeConfig `json:"delivery_mode"` // mode d'assignation des livreurs ShopName string `json:"shop_name"` // nom affiché dans la sidebar du site client Telegram2FAEnabled bool `json:"telegram_2fa_enabled"` // activer/désactiver l'authentification à deux facteurs diff --git a/backend/gestion/services/telegram.go b/backend/gestion/services/telegram.go index e3bf368d..6a035ab4 100644 --- a/backend/gestion/services/telegram.go +++ b/backend/gestion/services/telegram.go @@ -4,11 +4,9 @@ import ( "bytes" "encoding/json" "fmt" - "gestion/models" "log" "net/http" "os" - "sync" "time" ) @@ -19,8 +17,6 @@ type TelegramService struct { webhookSecret string BotUsername string notificationsEnabled bool - notifBots []models.TelegramBotConfig - notifMu sync.RWMutex } func NewTelegramService() *TelegramService { @@ -45,71 +41,6 @@ func (t *TelegramService) SetNotificationsEnabled(enabled bool) { t.notificationsEnabled = enabled } -// ReloadNotifBots met à jour la liste des bots de notification (thread-safe). -func (t *TelegramService) ReloadNotifBots(bots []models.TelegramBotConfig) { - t.notifMu.Lock() - defer t.notifMu.Unlock() - t.notifBots = bots - log.Printf("✅ [TELEGRAM] %d bot(s) de notification chargé(s)", len(bots)) -} - -// SendNotif envoie un message HTML en essayant chaque bot de notification dans l'ordre. -// En cas de rate-limit (429) ou d'erreur sur le bot courant, il passe au suivant. -func (t *TelegramService) SendNotif(chatID int64, text string) error { - t.notifMu.RLock() - bots := make([]models.TelegramBotConfig, len(t.notifBots)) - copy(bots, t.notifBots) - t.notifMu.RUnlock() - - if len(bots) == 0 { - // Fallback : utiliser le bot webhook principal - return t.SendMessage(chatID, text) - } - - var lastErr error - for i, bot := range bots { - if err := t.sendWithToken(bot.Token, chatID, text); err != nil { - log.Printf("⚠️ [TELEGRAM] bot %d (%s) échoué: %v — essai suivant", i+1, bot.Username, err) - lastErr = err - continue - } - return nil - } - return fmt.Errorf("tous les bots de notification ont échoué: %w", lastErr) -} - -// sendWithToken envoie un message HTML avec un token arbitraire. -func (t *TelegramService) sendWithToken(token string, chatID int64, text string) error { - payload := map[string]interface{}{ - "chat_id": chatID, - "text": text, - "parse_mode": "HTML", - } - body, err := json.Marshal(payload) - if err != nil { - return fmt.Errorf("marshal: %w", err) - } - url := fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", token) - req, err := http.NewRequest("POST", url, bytes.NewBuffer(body)) - if err != nil { - return fmt.Errorf("création requête: %w", err) - } - req.Header.Set("Content-Type", "application/json") - client := &http.Client{Timeout: 10 * time.Second} - resp, err := client.Do(req) - if err != nil { - return fmt.Errorf("envoi: %w", err) - } - defer resp.Body.Close() - if resp.StatusCode == http.StatusTooManyRequests { - return fmt.Errorf("rate limit (429)") - } - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("status %d", resp.StatusCode) - } - return nil -} - // Reload met à jour le token et le username (appelé après UpdateSettings) func (t *TelegramService) Reload(token, username string) { if token != "" { diff --git a/frontend-admin/src/api/api_admin.ts b/frontend-admin/src/api/api_admin.ts index 72344b98..1d55ee3f 100644 --- a/frontend-admin/src/api/api_admin.ts +++ b/frontend-admin/src/api/api_admin.ts @@ -1050,17 +1050,11 @@ export interface AppSettings { telegram_bot_username: string; telegram_notifications_enabled: boolean; telegram_2fa_enabled: boolean; - telegram_notif_bots: TelegramBotConfig[]; delivery_mode: DeliveryModeConfig; shop_name: string; contact_telegram: string; } -export type TelegramBotConfig = { - token: string; - username: string; -}; - export const getSettings = async (): Promise<{ success: boolean; settings?: AppSettings; diff --git a/frontend-admin/src/screens/admin/SettingsScreen.tsx b/frontend-admin/src/screens/admin/SettingsScreen.tsx index a33da0cb..9fe50c1c 100644 --- a/frontend-admin/src/screens/admin/SettingsScreen.tsx +++ b/frontend-admin/src/screens/admin/SettingsScreen.tsx @@ -16,7 +16,7 @@ import { Ionicons } from "@expo/vector-icons"; import { spacing, fontSize, borderRadius } from "../../theme"; import { useTheme } from "../../context/ThemeContext"; import { getSettings, updateSettings, getCategories, getAvailableDeliveryPersons, getAllProductsAdmin, DEFAULT_DELIVERY_SCHEDULE, DEFAULT_POSTAL_ZONES } from "../../api/api_admin"; -import type { AppSettings, Category, CategoryRoute, DeliveryModeConfig, PointsTier, PointsPool, PointsReward, RewardCategoryConfig, DaySchedule, DeliverySchedule, PostalZone, TelegramBotConfig } from "../../api/api_admin"; +import type { AppSettings, Category, CategoryRoute, DeliveryModeConfig, PointsTier, PointsPool, PointsReward, RewardCategoryConfig, DaySchedule, DeliverySchedule, PostalZone } from "../../api/api_admin"; import type { Product } from "../../api/types"; import AlertModal from "../../components/ui/AlertModal"; import { useAlert } from "../../hooks/useAlert"; @@ -1032,7 +1032,6 @@ export default function SettingsScreen() { telegram_bot_username: "", telegram_notifications_enabled: false, telegram_2fa_enabled: false, - telegram_notif_bots: [], delivery_mode: { mode: "single" as const, category_routes: [] }, shop_name: "Milieu-Nantais", contact_telegram: "", @@ -1905,66 +1904,6 @@ export default function SettingsScreen() { )} - {/* Bots de notification (failover) */} - - Bots de notification (failover) - - Les notifications sont envoyées par le premier bot. En cas de rate-limit ou d'erreur, le suivant prend le relais. - Si aucun bot n'est configuré ici, le bot principal est utilisé. - - {(settings.telegram_notif_bots ?? []).map((bot, idx) => ( - - - setSettings((p) => { - const bots = [...(p.telegram_notif_bots ?? [])]; - bots[idx] = { ...bots[idx], token: v }; - return { ...p, telegram_notif_bots: bots }; - })} - autoCapitalize="none" - autoCorrect={false} - secureTextEntry - /> - setSettings((p) => { - const bots = [...(p.telegram_notif_bots ?? [])]; - bots[idx] = { ...bots[idx], username: v }; - return { ...p, telegram_notif_bots: bots }; - })} - autoCapitalize="none" - autoCorrect={false} - /> - - setSettings((p) => ({ - ...p, - telegram_notif_bots: (p.telegram_notif_bots ?? []).filter((_, i) => i !== idx), - }))} - style={{ padding: spacing.s, marginTop: spacing.xs }} - > - - - - ))} - setSettings((p) => ({ - ...p, - telegram_notif_bots: [...(p.telegram_notif_bots ?? []), { token: "", username: "" }], - }))} - style={{ flexDirection: "row", alignItems: "center", gap: spacing.s, paddingVertical: spacing.s }} - > - - Ajouter un bot de notification - -