feat: notificaciones Telegram en webhook de Coolify
- CoolifyWebhook parsea el payload y envía mensaje formateado a Telegram - Detecta estado (✅ success / ❌ fail / 🚀 deploying / ⏹ stop / 🔄 restart) - Ruta /webhooks/coolify/:config_id identifica de qué instancia viene - Notifica a todos los chats autorizados del agente bot - GetAgentTelegramConfig() en models para obtener el bot activo Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
a8786e2aca
commit
ee9a2f09d6
@@ -1,6 +1,8 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/sujit-baniya/fiber-boilerplate/app"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
@@ -81,3 +83,15 @@ func CreateAgentAuth(chatID int64, nombre string) error {
|
||||
func DeleteAgentAuth(id uint) error {
|
||||
return app.Http.Database.DB.Delete(&TelegramAgentAuth{}, id).Error
|
||||
}
|
||||
|
||||
// GetAgentTelegramConfig retorna el TelegramConfig vinculado al agente bot activo.
|
||||
func GetAgentTelegramConfig() (*TelegramConfig, error) {
|
||||
var ai AiConfig
|
||||
if err := app.Http.Database.DB.Where("es_agente_bot = ? AND is_active = ?", true, true).First(&ai).Error; err != nil {
|
||||
return nil, fmt.Errorf("no hay agente bot configurado")
|
||||
}
|
||||
if ai.TelegramConfigID == nil {
|
||||
return nil, fmt.Errorf("el agente no tiene Telegram configurado")
|
||||
}
|
||||
return GetTelegramConfigByID(*ai.TelegramConfigID)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
@@ -12,6 +13,7 @@ import (
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
|
||||
"github.com/sujit-baniya/fiber-boilerplate/pkg/services"
|
||||
)
|
||||
|
||||
// filterQueryParam elimina un parámetro específico del querystring.
|
||||
@@ -346,12 +348,137 @@ func CoolifyTeamMembers(c *fiber.Ctx) error {
|
||||
return coolifyProxy(c, http.MethodGet, "/teams/current/members")
|
||||
}
|
||||
|
||||
// CoolifyWebhook recibe notificaciones push de Coolify (deployments, etc.)
|
||||
// Acepta el payload y devuelve 200; el procesamiento puede extenderse aquí.
|
||||
// CoolifyWebhook recibe notificaciones push de Coolify y las reenvía a Telegram.
|
||||
// Configurar en Coolify → Settings → Notifications → Add Notification → Webhook
|
||||
// URL: https://admin.u-site.app/webhooks/coolify/:config_id
|
||||
func CoolifyWebhook(c *fiber.Ctx) error {
|
||||
body := c.Body()
|
||||
log.Printf("[COOLIFY_WEBHOOK] config_id=%s payload=%s", c.Params("config_id"), string(body))
|
||||
|
||||
if len(body) == 0 {
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
|
||||
// Identificar la instancia Coolify si viene en la ruta
|
||||
var instanceName string
|
||||
if idStr := c.Params("config_id"); idStr != "" {
|
||||
if id, err := strconv.ParseUint(idStr, 10, 32); err == nil {
|
||||
if cfg, err := models.GetCoolifyConfigByID(uint(id)); err == nil {
|
||||
instanceName = cfg.Nombre
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
go sendCoolifyTelegramNotif(body, instanceName)
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
|
||||
// sendCoolifyTelegramNotif parsea el payload de Coolify y lo envía a Telegram.
|
||||
func sendCoolifyTelegramNotif(body []byte, instanceName string) {
|
||||
// Intentar parsear como JSON
|
||||
var payload map[string]interface{}
|
||||
isJSON := json.Unmarshal(body, &payload) == nil
|
||||
|
||||
// Extraer campos con múltiples nombres posibles (Coolify cambia formato según versión)
|
||||
get := func(keys ...string) string {
|
||||
if !isJSON {
|
||||
return ""
|
||||
}
|
||||
for _, k := range keys {
|
||||
if v, ok := payload[k]; ok {
|
||||
if s, ok := v.(string); ok && s != "" {
|
||||
return s
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
appName := get("application_name", "name", "app_name", "resource_name")
|
||||
appUUID := get("application_uuid", "uuid", "resource_uuid")
|
||||
status := get("status", "type", "event", "deployment_status")
|
||||
message := get("message", "description", "text", "body")
|
||||
fqdn := get("fqdn", "url", "application_url", "application_fqdn")
|
||||
serverName := get("server_name", "server")
|
||||
|
||||
// Si no pudimos parsear como JSON, tratar el body entero como mensaje
|
||||
if !isJSON {
|
||||
message = strings.TrimSpace(string(body))
|
||||
if len(message) > 500 {
|
||||
message = message[:500] + "…"
|
||||
}
|
||||
}
|
||||
|
||||
// Emoji según estado
|
||||
emoji := "🔔"
|
||||
statusLower := strings.ToLower(status + " " + message)
|
||||
switch {
|
||||
case strings.Contains(statusLower, "success") || strings.Contains(statusLower, "exitoso") || strings.Contains(statusLower, "finished"):
|
||||
emoji = "✅"
|
||||
case strings.Contains(statusLower, "fail") || strings.Contains(statusLower, "error") || strings.Contains(statusLower, "fallo"):
|
||||
emoji = "❌"
|
||||
case strings.Contains(statusLower, "running") || strings.Contains(statusLower, "deploying") || strings.Contains(statusLower, "start") || strings.Contains(statusLower, "building"):
|
||||
emoji = "🚀"
|
||||
case strings.Contains(statusLower, "stop") || strings.Contains(statusLower, "detenido"):
|
||||
emoji = "⏹"
|
||||
case strings.Contains(statusLower, "restart") || strings.Contains(statusLower, "reinici"):
|
||||
emoji = "🔄"
|
||||
}
|
||||
|
||||
// Construir el mensaje
|
||||
lines := []string{fmt.Sprintf("%s <b>Coolify</b>", emoji)}
|
||||
if instanceName != "" {
|
||||
lines = append(lines, fmt.Sprintf("Instancia: <b>%s</b>", instanceName))
|
||||
}
|
||||
if appName != "" {
|
||||
lines = append(lines, fmt.Sprintf("App: <code>%s</code>", appName))
|
||||
}
|
||||
if appUUID != "" && appUUID != appName {
|
||||
lines = append(lines, fmt.Sprintf("UUID: <code>%s</code>", appUUID))
|
||||
}
|
||||
if serverName != "" {
|
||||
lines = append(lines, fmt.Sprintf("Servidor: %s", serverName))
|
||||
}
|
||||
if status != "" {
|
||||
lines = append(lines, fmt.Sprintf("Estado: <b>%s</b>", status))
|
||||
}
|
||||
if message != "" && message != status {
|
||||
if len(message) > 300 {
|
||||
message = message[:300] + "…"
|
||||
}
|
||||
lines = append(lines, fmt.Sprintf("ℹ️ %s", message))
|
||||
}
|
||||
if fqdn != "" {
|
||||
lines = append(lines, fmt.Sprintf("🌐 %s", fqdn))
|
||||
}
|
||||
|
||||
text := strings.Join(lines, "\n")
|
||||
|
||||
// Obtener el bot del agente (TelegramConfig ID que tenga agent bot activo)
|
||||
tgCfg, err := models.GetAgentTelegramConfig()
|
||||
if err != nil {
|
||||
log.Printf("[COOLIFY_WEBHOOK] Sin config Telegram para notificar: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Enviar a todos los chats autorizados del agente
|
||||
auths, _ := models.GetAllAgentAuth()
|
||||
if len(auths) == 0 {
|
||||
log.Printf("[COOLIFY_WEBHOOK] Sin chats autorizados para notificar")
|
||||
return
|
||||
}
|
||||
|
||||
svc := &services.TelegramService{BotToken: tgCfg.BotToken}
|
||||
for _, auth := range auths {
|
||||
if !auth.Activo {
|
||||
continue
|
||||
}
|
||||
if err := svc.SendMessage(auth.ChatID, text); err != nil {
|
||||
log.Printf("[COOLIFY_WEBHOOK] Error enviando a chat %d: %v", auth.ChatID, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─── CRUD de instancias Coolify ───────────────────────────────────────────────
|
||||
|
||||
func CoolifyListConfigs(c *fiber.Ctx) error {
|
||||
|
||||
@@ -24,7 +24,10 @@ func RutasPublicas(web fiber.Router) {
|
||||
web.Post("/webhooks/saas-in/:token", controllers.SaasWebhookInHandler)
|
||||
web.Get("/webhooks/saas-in/:token", controllers.SaasWebhookInHandler) // algunos SaaS verifican con GET
|
||||
// Coolify notifica eventos de deployment/restart aquí (configurar en Coolify → Settings → Notifications → Webhook)
|
||||
// Ruta genérica
|
||||
web.Post("/webhooks/coolify", controllers.CoolifyWebhook)
|
||||
// Ruta por instancia: incluye el config_id para identificar de qué Coolify viene
|
||||
web.Post("/webhooks/coolify/:config_id", controllers.CoolifyWebhook)
|
||||
|
||||
// ─── Página de confirmación de pago ───────────────────────────────────
|
||||
web.Get("/pago-exitoso", apiControllers.PagoExitosoPage)
|
||||
|
||||
Reference in New Issue
Block a user