package handlers import ( "gestion/db" "gestion/models" "gestion/services" "log" "net/http" "os" "strings" "github.com/gin-gonic/gin" ) func TelegramWebhook(c *gin.Context) { // Vérification du secret webhook secret := c.GetHeader("X-Telegram-Bot-Api-Secret-Token") if services.TelegramBot == nil || !services.TelegramBot.ValidateWebhookSecret(secret) { c.Status(http.StatusUnauthorized) return } var update models.TgUpdate if err := c.ShouldBindJSON(&update); err != nil { c.Status(http.StatusBadRequest) return } if update.Message == nil { c.Status(http.StatusOK) return } text := strings.TrimSpace(update.Message.Text) chatID := update.Message.Chat.ID // Commande /start — liaison de compte if token, ok := strings.CutPrefix(text, "/start "); ok { token = strings.TrimSpace(token) handleLinkAccount(c, token, chatID) return } // Commande /start sans token — message d'accueil if text == "/start" { if services.TelegramBot != nil { services.TelegramBot.SendMessage(chatID, "👋 Bienvenue !\n\nPour lier votre compte, générez un token depuis l'application et envoyez /start <token>.") } } c.Status(http.StatusOK) } func handleLinkAccount(c *gin.Context, token string, chatID int64) { if token == "" { if services.TelegramBot != nil { services.TelegramBot.SendMessage(chatID, "❌ Token manquant. Générez un nouveau token depuis l'application.") } c.Status(http.StatusOK) return } database := c.MustGet("database").(*db.Database) username, role, err := db.ValidateAndConsumeLinkToken(token) if err != nil { log.Printf("⚠️ [TELEGRAM_LINK] Token invalide: %v", err) if services.TelegramBot != nil { services.TelegramBot.SendMessage(chatID, "❌ Token invalide ou expiré. Générez un nouveau token depuis l'application.") } c.Status(http.StatusOK) return } // Enregistrer le chat_id selon le rôle var saveErr error switch role { case "client": saveErr = database.SaveClientTelegramChatID(username, chatID) default: saveErr = database.SaveUserTelegramChatID(username, chatID) } if saveErr != nil { log.Printf("❌ [TELEGRAM_LINK] Erreur sauvegarde chat_id pour %s (%s): %v", username, role, saveErr) if services.TelegramBot != nil { services.TelegramBot.SendMessage(chatID, "❌ Une erreur est survenue. Réessayez.") } c.Status(http.StatusOK) return } log.Printf("✅ [TELEGRAM_LINK] Compte %s (%s) lié au chat_id %d", username, role, chatID) // Enrollment lbtelegram (best effort — n'empêche pas l'envoi du bouton) if services.LBTelegram != nil && services.LBTelegram.IsConfigured() { if err := services.LBTelegram.EnrollUser(chatID, username, role); err != nil { log.Printf("⚠️ [LB] enrollment échoué pour %s: %v", username, err) } } // Message de confirmation — bouton vers BOT1 si lbtelegram configuré, sinon texte simple if services.TelegramBot != nil { if services.LBTelegram != nil && services.LBTelegram.Bot1Username != "" { if err := services.TelegramBot.SendMessageWithButtons(chatID, "✅ Compte lié avec succès !\n\nPour activer vos notifications, démarrez le bot ci-dessous :", [][2]string{{"🔔 Activer les notifications", "https://t.me/" + services.LBTelegram.Bot1Username}}, ); err != nil { log.Printf("⚠️ [TELEGRAM] Envoi bouton BOT1 échoué pour %s: %v", username, err) services.TelegramBot.SendMessage(chatID, "✅ Compte lié avec succès !\n\nVous recevrez désormais vos notifications ici.") } } else { services.TelegramBot.SendMessage(chatID, "✅ Compte lié avec succès !\n\nVous recevrez désormais vos notifications ici.") } } c.Status(http.StatusOK) } func GenerateClientLinkToken(c *gin.Context) { username := c.GetString("username") if username == "" { c.JSON(http.StatusUnauthorized, gin.H{"error": "Non authentifié"}) return } if services.TelegramBot == nil || !services.TelegramBot.IsConfigured() { c.JSON(http.StatusServiceUnavailable, gin.H{"error": "Service Telegram non disponible"}) return } token, err := db.GenerateLinkToken(username, "client") if err != nil { log.Printf("❌ [TELEGRAM] Erreur génération token pour %s: %v", username, err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur génération token"}) return } botUsername := services.TelegramBot.BotUsername c.JSON(http.StatusOK, gin.H{ "token": token, "link_url": "https://t.me/" + botUsername + "?start=" + token, "message": "/start " + token, "expires_in": 600, }) } func GenerateLivreurLinkToken(c *gin.Context) { username := c.GetString("username") if username == "" { c.JSON(http.StatusUnauthorized, gin.H{"error": "Non authentifié"}) return } if services.TelegramBot == nil || !services.TelegramBot.IsConfigured() { c.JSON(http.StatusServiceUnavailable, gin.H{"error": "Service Telegram non disponible"}) return } token, err := db.GenerateLinkToken(username, "livreur") if err != nil { log.Printf("❌ [TELEGRAM] Erreur génération token pour livreur %s: %v", username, err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur génération token"}) return } botUsername := services.TelegramBot.BotUsername c.JSON(http.StatusOK, gin.H{ "token": token, "link_url": "https://t.me/" + botUsername + "?start=" + token, "message": "/start " + token, "expires_in": 600, }) } func GenerateAdminLinkToken(c *gin.Context) { username := c.GetString("username") if username == "" { c.JSON(http.StatusUnauthorized, gin.H{"error": "Non authentifié"}) return } if services.TelegramBot == nil || !services.TelegramBot.IsConfigured() { c.JSON(http.StatusServiceUnavailable, gin.H{"error": "Service Telegram non disponible"}) return } // Récupérer le rôle réel depuis la DB database := c.MustGet("database").(*db.Database) user, err := database.GetUserByUsername(username) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Utilisateur introuvable"}) return } token, err := db.GenerateLinkToken(username, user.Role) if err != nil { log.Printf("❌ [TELEGRAM] Erreur génération token pour admin %s: %v", username, err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur génération token"}) return } botUsername := services.TelegramBot.BotUsername c.JSON(http.StatusOK, gin.H{ "token": token, "link_url": "https://t.me/" + botUsername + "?start=" + token, "message": "/start " + token, "expires_in": 600, }) } // GET /api/v1/telegram/status func GetClientTelegramStatus(c *gin.Context) { username := c.GetString("username") if username == "" { c.JSON(http.StatusUnauthorized, gin.H{"error": "Non authentifié"}) return } database := c.MustGet("database").(*db.Database) _, linked, err := database.GetClientTelegramChatID(username) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur vérification"}) return } c.JSON(http.StatusOK, gin.H{ "linked": linked, "enabled": services.TelegramBot != nil && services.TelegramBot.IsConfigured(), }) } // GET /api/v1/livreur/telegram/status func GetLivreurTelegramStatus(c *gin.Context) { username := c.GetString("username") if username == "" { c.JSON(http.StatusUnauthorized, gin.H{"error": "Non authentifié"}) return } database := c.MustGet("database").(*db.Database) _, linked, err := database.GetUserTelegramChatID(username) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur vérification"}) return } c.JSON(http.StatusOK, gin.H{ "linked": linked, "enabled": services.TelegramBot != nil && services.TelegramBot.IsConfigured(), }) } // ============================================ // DÉLIAISON TELEGRAM // ============================================ // DELETE /api/v1/telegram/unlink func UnlinkClientTelegram(c *gin.Context) { username := c.GetString("username") if username == "" { c.JSON(http.StatusUnauthorized, gin.H{"error": "Non authentifié"}) return } clientID := c.GetInt("client_id") database := c.MustGet("database").(*db.Database) if err := database.DeleteClientTelegramChatID(username); err != nil { log.Printf("❌ [TELEGRAM_UNLINK] Erreur pour %s: %v", username, err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur déliaison"}) return } // Désactiver la 2FA si Telegram est délié if clientID > 0 { _ = database.SetClientTwoFAEnabled(clientID, false) } log.Printf("✅ [TELEGRAM_UNLINK] Compte client %s délié", username) c.JSON(http.StatusOK, gin.H{"success": true}) } // DELETE /api/v1/livreur/telegram/unlink func UnlinkLivreurTelegram(c *gin.Context) { username := c.GetString("username") if username == "" { c.JSON(http.StatusUnauthorized, gin.H{"error": "Non authentifié"}) return } database := c.MustGet("database").(*db.Database) if err := database.DeleteUserTelegramChatID(username); err != nil { log.Printf("❌ [TELEGRAM_UNLINK] Erreur pour %s: %v", username, err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur déliaison"}) return } log.Printf("✅ [TELEGRAM_UNLINK] Compte livreur %s délié", username) c.JSON(http.StatusOK, gin.H{"success": true}) } // DELETE /api/v2/admin/protected/telegram/unlink func UnlinkAdminTelegram(c *gin.Context) { username := c.GetString("username") if username == "" { c.JSON(http.StatusUnauthorized, gin.H{"error": "Non authentifié"}) return } database := c.MustGet("database").(*db.Database) if err := database.DeleteUserTelegramChatID(username); err != nil { log.Printf("❌ [TELEGRAM_UNLINK] Erreur pour %s: %v", username, err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur déliaison"}) return } log.Printf("✅ [TELEGRAM_UNLINK] Compte admin %s délié", username) c.JSON(http.StatusOK, gin.H{"success": true}) } // ============================================ // LIAISON INTERNE (appelée par LBTelegram) // ============================================ // POST /api/internal/telegram/link // Appelée par LBTelegram quand Bot1 reçoit /start TOKEN. // Valide le token, enregistre le chat_id, déclenche l'enrollment. func InternalTelegramLink(c *gin.Context) { secret := c.GetHeader("X-Internal-Secret") expected := os.Getenv("BACKEND_LINK_SECRET") if expected == "" || secret != expected { c.Status(http.StatusUnauthorized) return } var req struct { ChatID int64 `json:"chat_id" binding:"required"` Token string `json:"token" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } database := c.MustGet("database").(*db.Database) username, role, err := db.ValidateAndConsumeLinkToken(req.Token) if err != nil { log.Printf("⚠️ [TELEGRAM_LINK_INTERNAL] Token invalide: %v", err) c.Status(http.StatusUnauthorized) return } var saveErr error switch role { case "client": saveErr = database.SaveClientTelegramChatID(username, req.ChatID) default: saveErr = database.SaveUserTelegramChatID(username, req.ChatID) } if saveErr != nil { log.Printf("❌ [TELEGRAM_LINK_INTERNAL] Erreur sauvegarde pour %s: %v", username, saveErr) c.Status(http.StatusInternalServerError) return } log.Printf("✅ [TELEGRAM_LINK_INTERNAL] Compte %s (%s) lié via Bot1 (chat_id %d)", username, role, req.ChatID) if services.LBTelegram != nil && services.LBTelegram.IsConfigured() { if err := services.LBTelegram.EnrollUser(req.ChatID, username, role); err != nil { log.Printf("⚠️ [LB] enrollment échoué pour %s: %v", username, err) c.Status(http.StatusInternalServerError) return } } c.Status(http.StatusOK) }