From 6cc0bfd1d79074df790272b8091b1f0226d14283 Mon Sep 17 00:00:00 2001 From: zwlucas Date: Fri, 29 May 2026 15:57:37 -0300 Subject: feat: upload project Signed-off-by: zwlucas --- backend/internal/monitor/worker.go | 148 +++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 backend/internal/monitor/worker.go (limited to 'backend/internal/monitor/worker.go') diff --git a/backend/internal/monitor/worker.go b/backend/internal/monitor/worker.go new file mode 100644 index 0000000..0cf980a --- /dev/null +++ b/backend/internal/monitor/worker.go @@ -0,0 +1,148 @@ +package monitor + +import ( + "context" + "encoding/json" + "log" + "time" + + "yaum/internal/alerts" + "yaum/internal/model" + "yaum/internal/sse" + "yaum/internal/store" +) + +type checkResult struct { + Heartbeat model.Heartbeat + Service model.Service +} + +type Worker struct { + store store.Store + interval time.Duration + timeout time.Duration + results chan checkResult + broadcaster *sse.Broadcaster + smtpCfg *alerts.SMTPConfig + quit chan struct{} +} + +func NewWorker(s store.Store, interval, timeout time.Duration, b *sse.Broadcaster) *Worker { + return &Worker{ + store: s, + interval: interval, + timeout: timeout, + results: make(chan checkResult, 100), + broadcaster: b, + quit: make(chan struct{}), + } +} + +func (w *Worker) WithSMTP(cfg *alerts.SMTPConfig) *Worker { + w.smtpCfg = cfg + return w +} + +func (w *Worker) Start(ctx context.Context) { + ticker := time.NewTicker(w.interval) + defer ticker.Stop() + + log.Printf("[worker] iniciado | intervalo=%v timeout=%v", w.interval, w.timeout) + + go w.resultConsumer() + + w.runChecks(ctx) + + for { + select { + case <-ticker.C: + w.runChecks(ctx) + case <-w.quit: + log.Println("[worker] encerrando...") + return + } + } +} + +func (w *Worker) resultConsumer() { + for { + select { + case res := <-w.results: + hb := res.Heartbeat + svc := res.Service + + w.checkTransition(svc, hb) + + if err := w.store.AddHeartbeat(hb); err != nil { + log.Printf("[worker] erro ao salvar heartbeat: %v", err) + } + + if w.broadcaster != nil { + data, err := json.Marshal(hb) + if err == nil { + w.broadcaster.Publish(data) + } + } + + status := "UP" + if !hb.IsUp { + status = "DOWN" + } + log.Printf("[worker] service=%d status=%s code=%d time=%dms", + hb.ServiceID, status, hb.StatusCode, hb.ResponseTimeMs) + case <-w.quit: + return + } + } +} + +func (w *Worker) checkTransition(svc model.Service, hb model.Heartbeat) { + prev, err := w.store.GetLastHeartbeat(svc.ID) + if err != nil || prev == nil { + return + } + + if prev.IsUp == hb.IsUp { + return + } + + log.Printf("[worker] transicao de status service=%d: %v -> %v", svc.ID, prev.IsUp, hb.IsUp) + + w.fireAlerts(svc, hb, prev.IsUp) +} + +func (w *Worker) fireAlerts(svc model.Service, hb model.Heartbeat, wasUp bool) { + if svc.DiscordWebhookURL != "" { + go alerts.SendDiscord(svc.DiscordWebhookURL, svc, hb, wasUp) + } + if svc.AlertEmail != "" && w.smtpCfg != nil { + go alerts.SendEmail(svc, hb, wasUp, w.smtpCfg) + } +} + +func (w *Worker) runChecks(ctx context.Context) { + services, err := w.store.ListActiveServices() + if err != nil { + log.Printf("[worker] erro ao listar servicos ativos: %v", err) + return + } + log.Printf("[worker] verificando %d servico(s)", len(services)) + + for _, svc := range services { + go w.checkService(ctx, svc) + } +} + +func (w *Worker) checkService(ctx context.Context, svc model.Service) { + checkCtx, cancel := context.WithTimeout(ctx, w.timeout) + defer cancel() + + hb := CheckHTTP(checkCtx, svc.URL, svc.KeywordToFind) + hb.ServiceID = svc.ID + + w.results <- checkResult{Heartbeat: hb, Service: svc} +} + +func (w *Worker) Stop() { + close(w.quit) +} -- cgit v1.2.3