4d5116644f
- Move status page template to package-level template.Must (panic on parse error at init instead of nil deref at runtime) - Fix XSS in import error responses (log detail server-side, return generic message to client) - Handle ListenAndServe errors in HTTP and SSH servers - Use defer resp.Body.Close() in all alert providers, check json.Marshal errors - Share HTTP clients across checks instead of creating per-request - Use http.NewRequestWithContext for per-site timeout control - Support HTTP method field (was always GET despite DB storing method) - Implement AcceptedCodes validation (was hardcoded >= 400 despite DB storing accepted code ranges) - Add defer tx.Rollback() to ImportData for transaction safety
154 lines
3.4 KiB
Go
154 lines
3.4 KiB
Go
package alert
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"go-upkeep/internal/models"
|
|
"net/http"
|
|
"net/smtp"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
var alertClient = &http.Client{Timeout: 10 * time.Second}
|
|
|
|
type Provider interface {
|
|
Send(title, message string) error
|
|
}
|
|
|
|
func GetProvider(cfg models.AlertConfig) Provider {
|
|
switch cfg.Type {
|
|
case "discord":
|
|
return &DiscordProvider{URL: cfg.Settings["url"]}
|
|
case "slack":
|
|
return &SlackProvider{URL: cfg.Settings["url"]}
|
|
case "webhook":
|
|
// Generic Webhook
|
|
return &WebhookProvider{URL: cfg.Settings["url"]}
|
|
case "email":
|
|
port := "25"
|
|
if p, ok := cfg.Settings["port"]; ok {
|
|
port = p
|
|
}
|
|
return &EmailProvider{
|
|
Host: cfg.Settings["host"],
|
|
Port: port,
|
|
User: cfg.Settings["user"],
|
|
Pass: cfg.Settings["pass"],
|
|
To: cfg.Settings["to"],
|
|
From: cfg.Settings["from"],
|
|
}
|
|
case "ntfy":
|
|
priority := "3"
|
|
if p, ok := cfg.Settings["priority"]; ok && p != "" {
|
|
priority = p
|
|
}
|
|
return &NtfyProvider{
|
|
ServerURL: cfg.Settings["url"],
|
|
Topic: cfg.Settings["topic"],
|
|
Priority: priority,
|
|
Username: cfg.Settings["username"],
|
|
Password: cfg.Settings["password"],
|
|
}
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// --- DISCORD ---
|
|
type DiscordProvider struct{ URL string }
|
|
|
|
func (d *DiscordProvider) Send(title, message string) error {
|
|
payload := map[string]string{"content": fmt.Sprintf("**%s**\n%s", title, message)}
|
|
jsonValue, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
resp, err := alertClient.Post(d.URL, "application/json", bytes.NewBuffer(jsonValue))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
return nil
|
|
}
|
|
|
|
// --- SLACK ---
|
|
type SlackProvider struct{ URL string }
|
|
|
|
func (s *SlackProvider) Send(title, message string) error {
|
|
payload := map[string]string{"text": fmt.Sprintf("*%s*\n%s", title, message)}
|
|
jsonValue, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
resp, err := alertClient.Post(s.URL, "application/json", bytes.NewBuffer(jsonValue))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
return nil
|
|
}
|
|
|
|
// --- GENERIC WEBHOOK ---
|
|
type WebhookProvider struct{ URL string }
|
|
|
|
func (w *WebhookProvider) Send(title, message string) error {
|
|
payload := map[string]string{
|
|
"title": title,
|
|
"message": message,
|
|
"status": "alert",
|
|
}
|
|
jsonValue, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
resp, err := alertClient.Post(w.URL, "application/json", bytes.NewBuffer(jsonValue))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
return nil
|
|
}
|
|
|
|
// --- EMAIL ---
|
|
type EmailProvider struct {
|
|
Host, Port, User, Pass, To, From string
|
|
}
|
|
|
|
func (e *EmailProvider) Send(title, message string) error {
|
|
auth := smtp.PlainAuth("", e.User, e.Pass, e.Host)
|
|
msg := []byte("To: " + e.To + "\r\n" +
|
|
"Subject: Go-Upkeep: " + title + "\r\n" +
|
|
"\r\n" +
|
|
message + "\r\n")
|
|
return smtp.SendMail(e.Host+":"+e.Port, auth, e.From, []string{e.To}, msg)
|
|
}
|
|
|
|
type NtfyProvider struct {
|
|
ServerURL string
|
|
Topic string
|
|
Priority string
|
|
Username string
|
|
Password string
|
|
}
|
|
|
|
func (n *NtfyProvider) Send(title, message string) error {
|
|
url := strings.TrimRight(n.ServerURL, "/") + "/" + n.Topic
|
|
req, err := http.NewRequest("POST", url, strings.NewReader(message))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
req.Header.Set("Title", title)
|
|
req.Header.Set("Priority", n.Priority)
|
|
if n.Username != "" && n.Password != "" {
|
|
req.SetBasicAuth(n.Username, n.Password)
|
|
}
|
|
resp, err := alertClient.Do(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
return nil
|
|
}
|