Files
soft_usite/pkg/models/url_monitor.go
T
Lizandro GuarnizoandClaude Opus 5 b2e2e83fe8 feat(umind): el agente programa recordatorios, y solo para su dueño
"Avisame el 15 de marzo que vence la póliza de Acme, y todos los años" queda
programado desde la conversación, visible y cancelable en el panel. Tres tools:
programar, listar, cancelar.

El aviso vuelve por donde se pidió — Telegram a ese chat, correo a esa casilla
— o al dueño del espacio por correo y campanita. Nunca a una dirección que
dicte la conversación: eso sería un cañón de spam con destinatario libre.

Y las tools de aviso solo se le OFRECEN al modelo cuando la sesión es interna:
el chat de prueba del panel (que corre autenticado) o un canal marcado como
línea privada del dueño. En el widget público escribe cualquiera, y cualquiera
no puede programarle recordatorios ni gastarle el plan a otro. El gate está en
dos capas: la tool no se declara, y si igual la pide, el ejecutor la rechaza.

Tres detalles que se pagan una sola vez:
- El cron corre en memoria del proceso, sin lock distribuido: con dos
  instancias cada aviso saldría dos veces. El reclamo es un UPDATE condicional
  — la base ya es el árbitro, no hace falta traer otro.
- Si el servidor estuvo caído, una repetición diaria se saltea los ciclos
  perdidos en vez de disparar diez avisos viejos de golpe.
- Un fallo de SMTP devuelve el aviso a pendiente: una caída de correo no puede
  perder un vencimiento de póliza.

Una fecha sin hora se entrega a las 9, no a medianoche, que es cuando nadie
mira el teléfono.

De paso: los archivos de los espacios uMind nunca se sirven por el estático de
/uploads. El guard genérico solo sabe si hay sesión de panel, no de quién es el
archivo — salen por su endpoint, que sí valida propiedad. Cerrado por
construcción y no por acordarse.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 21:32:48 -05:00

103 lines
3.3 KiB
Go

package models
import (
"time"
"github.com/sujit-baniya/fiber-boilerplate/app"
"gorm.io/gorm"
)
type UrlMonitor struct {
gorm.Model
Nombre string `json:"nombre" gorm:"column:nombre;size:100"`
URL string `json:"url" gorm:"column:url;size:500"`
IntervaloMin int `json:"intervalo_min" gorm:"column:intervalo_min;default:5"`
TimeoutSeg int `json:"timeout_seg" gorm:"column:timeout_seg;default:10"`
Activo bool `json:"activo" gorm:"column:activo;default:true"`
// Estado actual
UltimoStatus int `json:"ultimo_status" gorm:"column:ultimo_status"`
UltimaLatMs int64 `json:"ultima_lat_ms" gorm:"column:ultima_lat_ms"`
UltimoCheckAt *time.Time `json:"ultimo_check_at" gorm:"column:ultimo_check_at"`
UltimoOk *bool `json:"ultimo_ok" gorm:"column:ultimo_ok"`
}
func (UrlMonitor) TableName() string { return "url_monitor" }
type UrlMonitorLog struct {
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
MonitorID uint `json:"monitor_id" gorm:"column:monitor_id;index"`
CreatedAt time.Time `json:"created_at" gorm:"column:created_at;index"`
StatusCode int `json:"status_code" gorm:"column:status_code"`
LatenciaMs int64 `json:"latencia_ms" gorm:"column:latencia_ms"`
Ok bool `json:"ok" gorm:"column:ok"`
Error string `json:"error" gorm:"column:error;type:text"`
}
func (UrlMonitorLog) TableName() string { return "url_monitor_log" }
func GetAllUrlMonitors() ([]UrlMonitor, error) {
var items []UrlMonitor
err := app.Http.Database.DB.Order("nombre ASC").Find(&items).Error
return items, err
}
func GetActiveUrlMonitors() ([]UrlMonitor, error) {
var items []UrlMonitor
err := app.Http.Database.DB.Where("activo = true").Find(&items).Error
return items, err
}
func GetUrlMonitorByID(id uint) (*UrlMonitor, error) {
var m UrlMonitor
err := app.Http.Database.DB.First(&m, id).Error
return &m, err
}
func CreateUrlMonitor(m *UrlMonitor) error {
return app.Http.Database.DB.Create(m).Error
}
func SaveUrlMonitor(m *UrlMonitor) error {
return app.Http.Database.DB.Save(m).Error
}
func DeleteUrlMonitor(id uint) error {
return app.Http.Database.DB.Delete(&UrlMonitor{}, id).Error
}
func UpdateUrlMonitorStatus(id uint, statusCode int, latMs int64, ok bool) error {
now := time.Now()
return app.Http.Database.DB.Model(&UrlMonitor{}).Where("id = ?", id).Updates(map[string]interface{}{
"ultimo_status": statusCode,
"ultima_lat_ms": latMs,
"ultimo_check_at": &now,
"ultimo_ok": ok,
}).Error
}
func InsertUrlMonitorLog(monitorID uint, statusCode int, latMs int64, ok bool, errStr string) {
app.Http.Database.DB.Create(&UrlMonitorLog{
MonitorID: monitorID,
CreatedAt: time.Now(),
StatusCode: statusCode,
LatenciaMs: latMs,
Ok: ok,
Error: errStr,
})
}
func GetUrlMonitorLogs(monitorID uint, horas int) ([]UrlMonitorLog, error) {
var items []UrlMonitorLog
desde := time.Now().Add(-time.Duration(horas) * time.Hour)
err := app.Http.Database.DB.
Where("monitor_id = ? AND created_at >= ?", monitorID, desde).
Order("created_at ASC").
Find(&items).Error
return items, err
}
func PurgarUrlMonitorLogs(dias int) error {
corte := time.Now().AddDate(0, 0, -dias)
return app.Http.Database.DB.Where("created_at < ?", corte).Delete(&UrlMonitorLog{}).Error
}