feat: API pública para envío de SMS vía WebSMS
Agrega POST /api/sms/send autenticado con Bearer token (secret_key auto-generado en WebSmsConfig). Recibe numero y mensaje, llama a LabsMobile y registra el resultado en websms_log. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
df1eb75bdd
commit
704664afe7
@@ -1,6 +1,9 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/hex"
|
||||||
|
|
||||||
"github.com/sujit-baniya/fiber-boilerplate/app"
|
"github.com/sujit-baniya/fiber-boilerplate/app"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
@@ -12,6 +15,13 @@ type WebSmsConfig struct {
|
|||||||
Sender string `json:"sender" gorm:"column:sender;size:30"`
|
Sender string `json:"sender" gorm:"column:sender;size:30"`
|
||||||
Notas string `json:"notas" gorm:"column:notas;type:text"`
|
Notas string `json:"notas" gorm:"column:notas;type:text"`
|
||||||
Activo bool `json:"activo" gorm:"column:activo;default:true"`
|
Activo bool `json:"activo" gorm:"column:activo;default:true"`
|
||||||
|
SecretKey string `json:"secret_key" gorm:"column:secret_key;size:64;uniqueIndex"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateWebSmsSecret() string {
|
||||||
|
b := make([]byte, 24)
|
||||||
|
rand.Read(b)
|
||||||
|
return "sms_" + hex.EncodeToString(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (WebSmsConfig) TableName() string { return "websms_config" }
|
func (WebSmsConfig) TableName() string { return "websms_config" }
|
||||||
@@ -30,14 +40,22 @@ func SaveWebSmsConfig(s WebSmsConfig) error {
|
|||||||
Update("activo", false)
|
Update("activo", false)
|
||||||
s.Activo = true
|
s.Activo = true
|
||||||
if s.ID > 0 {
|
if s.ID > 0 {
|
||||||
|
// preserve existing secret_key
|
||||||
|
var existing WebSmsConfig
|
||||||
|
app.Http.Database.DB.First(&existing, s.ID)
|
||||||
|
if existing.SecretKey == "" {
|
||||||
|
existing.SecretKey = generateWebSmsSecret()
|
||||||
|
}
|
||||||
return app.Http.Database.DB.Model(&s).Updates(map[string]interface{}{
|
return app.Http.Database.DB.Model(&s).Updates(map[string]interface{}{
|
||||||
"username": s.Username,
|
"username": s.Username,
|
||||||
"api_token": s.ApiToken,
|
"api_token": s.ApiToken,
|
||||||
"sender": s.Sender,
|
"sender": s.Sender,
|
||||||
"notas": s.Notas,
|
"notas": s.Notas,
|
||||||
"activo": true,
|
"activo": true,
|
||||||
|
"secret_key": existing.SecretKey,
|
||||||
}).Error
|
}).Error
|
||||||
}
|
}
|
||||||
|
s.SecretKey = generateWebSmsSecret()
|
||||||
return app.Http.Database.DB.Create(&s).Error
|
return app.Http.Database.DB.Create(&s).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
|
"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
|
||||||
"github.com/sujit-baniya/fiber-boilerplate/pkg/services"
|
"github.com/sujit-baniya/fiber-boilerplate/pkg/services"
|
||||||
@@ -146,6 +148,57 @@ func WebSmsClickWebhook(c *fiber.Ctx) error {
|
|||||||
return c.SendStatus(200)
|
return c.SendStatus(200)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── API pública ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
// ApiSendSms envía un SMS. Requiere Authorization: Bearer <secret_key>.
|
||||||
|
// POST /api/sms/send { "numero": "573001234567", "mensaje": "Hola" }
|
||||||
|
func ApiSendSms(c *fiber.Ctx) error {
|
||||||
|
bearer := strings.TrimPrefix(c.Get("Authorization"), "Bearer ")
|
||||||
|
if bearer == "" {
|
||||||
|
return c.Status(401).JSON(fiber.Map{"error": "token requerido"})
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := models.GetWebSmsConfig()
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(503).JSON(fiber.Map{"error": "WebSMS no configurado"})
|
||||||
|
}
|
||||||
|
if cfg.SecretKey == "" || cfg.SecretKey != bearer {
|
||||||
|
return c.Status(401).JSON(fiber.Map{"error": "token inválido"})
|
||||||
|
}
|
||||||
|
|
||||||
|
type body struct {
|
||||||
|
Numero string `json:"numero"`
|
||||||
|
Mensaje string `json:"mensaje"`
|
||||||
|
}
|
||||||
|
var b body
|
||||||
|
if err := c.BodyParser(&b); err != nil || b.Numero == "" || b.Mensaje == "" {
|
||||||
|
return c.Status(400).JSON(fiber.Map{"error": "numero y mensaje son requeridos"})
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := services.SendWebSms(cfg, b.Numero, b.Mensaje)
|
||||||
|
errStr := ""
|
||||||
|
status := "ok"
|
||||||
|
if err != nil {
|
||||||
|
errStr = err.Error()
|
||||||
|
status = "error"
|
||||||
|
} else if resp != nil {
|
||||||
|
status = resp.Code
|
||||||
|
}
|
||||||
|
|
||||||
|
models.CreateWebSmsLog(&models.WebSmsLog{
|
||||||
|
Para: b.Numero,
|
||||||
|
Mensaje: b.Mensaje,
|
||||||
|
Status: status,
|
||||||
|
MsgID: func() string { if resp != nil { return resp.ID }; return "" }(),
|
||||||
|
Error: errStr,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(500).JSON(fiber.Map{"error": errStr})
|
||||||
|
}
|
||||||
|
return c.JSON(fiber.Map{"ok": true, "id": resp.ID})
|
||||||
|
}
|
||||||
|
|
||||||
func WebSmsIncomingWebhook(c *fiber.Ctx) error {
|
func WebSmsIncomingWebhook(c *fiber.Ctx) error {
|
||||||
var payload services.WebSmsIncomingPayload
|
var payload services.WebSmsIncomingPayload
|
||||||
if err := c.BodyParser(&payload); err != nil {
|
if err := c.BodyParser(&payload); err != nil {
|
||||||
|
|||||||
@@ -37,7 +37,8 @@ func RutasPublicas(web fiber.Router) {
|
|||||||
// Configurar en el bot: POST https://api.telegram.org/bot{TOKEN}/setWebhook?url={HOST}/webhooks/telegram-portal
|
// Configurar en el bot: POST https://api.telegram.org/bot{TOKEN}/setWebhook?url={HOST}/webhooks/telegram-portal
|
||||||
web.Post("/webhooks/telegram-portal", controllers.TelegramPortalWebhook)
|
web.Post("/webhooks/telegram-portal", controllers.TelegramPortalWebhook)
|
||||||
|
|
||||||
// ─── WebSMS (LabsMobile) — ACK de entrega, clics y mensajes entrantes ──
|
// ─── WebSMS (LabsMobile) — API de envío + webhooks ──────────────────
|
||||||
|
web.Post("/api/sms/send", controllers.ApiSendSms)
|
||||||
web.Post("/webhooks/websms/delivery", controllers.WebSmsDeliveryWebhook)
|
web.Post("/webhooks/websms/delivery", controllers.WebSmsDeliveryWebhook)
|
||||||
web.Post("/webhooks/websms/click", controllers.WebSmsClickWebhook)
|
web.Post("/webhooks/websms/click", controllers.WebSmsClickWebhook)
|
||||||
web.Post("/webhooks/websms/incoming", controllers.WebSmsIncomingWebhook)
|
web.Post("/webhooks/websms/incoming", controllers.WebSmsIncomingWebhook)
|
||||||
|
|||||||
Reference in New Issue
Block a user