233 lines
5.9 KiB
Go
233 lines
5.9 KiB
Go
package notification
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"encoding/json"
|
||
|
|
"fmt"
|
||
|
|
"log"
|
||
|
|
"net/http"
|
||
|
|
"net/smtp"
|
||
|
|
"os"
|
||
"strings"
|
|||
)
|
|||
|
|
|
||
|
|
// SendMsgRequest represents a sendMsg API request.
|
||
|
|
type SendMsgRequest struct {
|
||
|
|
Format string `json:"format"` // "smtp" or "sms"
|
||
|
|
Title string `json:"title"`
|
||
|
|
Message string `json:"message"`
|
||
|
|
Priority int `json:"priority,omitempty"`
|
||
|
|
Extras map[string]string `json:"extras,omitempty"` // e.g., {"to_email": "[email protected]", "phone": "+1234567890"}
|
||
|
|
}
|
||
|
|
|
||
|
|
// SendMsgResponse represents a sendMsg API response.
|
||
|
|
type SendMsgResponse struct {
|
||
|
|
Status string `json:"status"`
|
||
|
|
MessageID string `json:"messageId,omitempty"`
|
||
|
|
Error string `json:"error,omitempty"`
|
||
|
|
}
|
||
|
|
|
||
|
|
// Handler handles sendMsg requests and forwards to appropriate channel (email, SMS, or Gotify push).
|
||
|
|
type Handler struct {
|
||
|
|
smtpHost string
|
||
|
|
smtpPort string
|
||
|
|
smtpFrom string
|
||
|
|
smtpUser string
|
||
|
|
smtpPass string
|
||
|
|
smsAPIURL string
|
||
|
|
smsAPIKey string
|
||
|
|
gotifyURL string
|
||
|
|
gotifyToken string
|
||
|
|
}
|
||
|
|
|
||
|
|
// NewHandler creates a new notification handler from environment variables.
|
||
|
|
func NewHandler() *Handler {
|
||
|
|
return &Handler{
|
||
|
|
smtpHost: os.Getenv("SMTP_HOST"),
|
||
|
|
smtpPort: os.Getenv("SMTP_PORT"),
|
||
|
|
smtpFrom: os.Getenv("SMTP_FROM"),
|
||
|
|
smtpUser: os.Getenv("SMTP_USER"),
|
||
|
|
smtpPass: os.Getenv("SMTP_PASS"),
|
||
|
|
smsAPIURL: os.Getenv("SMS_API_URL"),
|
||
|
|
smsAPIKey: os.Getenv("SMS_API_KEY"),
|
||
|
|
gotifyURL: os.Getenv("GOTIFY_URL"),
|
||
|
|
gotifyToken: os.Getenv("GOTIFY_TOKEN"),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ServeHTTP handles sendMsg requests.
|
||
|
|
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||
|
|
if r.Method != http.MethodPost {
|
||
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
var req SendMsgRequest
|
||
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||
|
|
w.Header().Set("Content-Type", "application/json")
|
||
|
|
w.WriteHeader(http.StatusBadRequest)
|
||
|
|
json.NewEncoder(w).Encode(SendMsgResponse{
|
||
|
|
Status: "error",
|
||
|
|
Error: "invalid request: " + err.Error(),
|
||
|
|
})
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
// Route based on format
|
||
|
|
var resp SendMsgResponse
|
||
|
|
switch req.Format {
|
||
|
|
case "smtp":
|
||
|
|
resp = h.sendEmail(req)
|
||
|
|
case "sms":
|
||
|
|
resp = h.sendSMS(req)
|
||
case "gotify":
|
|||
|
|
resp = h.sendGotify(req)
|
||
default:
|
|||
|
|
resp = SendMsgResponse{
|
||
|
|
Status: "error",
|
||
|
|
Error: "unsupported format: " + req.Format,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
w.Header().Set("Content-Type", "application/json")
|
||
|
|
if resp.Error != "" {
|
||
|
|
w.WriteHeader(http.StatusInternalServerError)
|
||
|
|
} else {
|
||
|
|
w.WriteHeader(http.StatusOK)
|
||
|
|
}
|
||
|
|
json.NewEncoder(w).Encode(resp)
|
||
|
|
}
|
||
|
|
|
||
|
|
// sendEmail sends an email via SMTP.
|
||
|
|
func (h *Handler) sendEmail(req SendMsgRequest) SendMsgResponse {
|
||
|
|
toEmail := req.Extras["to_email"]
|
||
|
|
if toEmail == "" {
|
||
|
|
return SendMsgResponse{
|
||
|
|
Status: "error",
|
||
|
|
Error: "missing to_email in extras",
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
subject := req.Title
|
||
|
|
if subject == "" {
|
||
|
|
subject = "Notification"
|
||
|
|
}
|
||
|
|
|
||
|
|
// Construct email body
|
||
|
|
body := req.Message
|
||
|
|
if req.Extras != nil {
|
||
|
|
if cc := req.Extras["cc"]; cc != "" {
|
||
|
|
body = fmt.Sprintf("CC: %s\n\n%s", cc, body)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
msg := fmt.Sprintf(
|
||
|
|
"From: %s\r\nTo: %s\r\nSubject: %s\r\nContent-Type: text/plain; charset=UTF-8\r\n\r\n%s",
|
||
|
|
h.smtpFrom, toEmail, subject, body,
|
||
|
|
)
|
||
|
|
|
||
|
|
// Send via SMTP
|
||
|
|
smtpAddr := fmt.Sprintf("%s:%s", h.smtpHost, h.smtpPort)
|
||
|
|
auth := smtp.PlainAuth("", h.smtpUser, h.smtpPass, h.smtpHost)
|
||
|
|
|
||
|
|
if err := smtp.SendMail(smtpAddr, auth, h.smtpFrom, []string{toEmail}, []byte(msg)); err != nil {
|
||
|
|
log.Printf("error sending email to %s: %v", toEmail, err)
|
||
|
|
return SendMsgResponse{
|
||
|
|
Status: "error",
|
||
|
|
Error: "failed to send email: " + err.Error(),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return SendMsgResponse{
|
||
|
|
Status: "success",
|
||
|
|
MessageID: fmt.Sprintf("email-%s", toEmail),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// sendSMS sends an SMS via configured provider.
|
||
|
|
// Placeholder: integrate with Twilio, AWS SNS, or similar.
|
||
|
|
func (h *Handler) sendSMS(req SendMsgRequest) SendMsgResponse {
|
||
|
|
phone := req.Extras["phone"]
|
||
|
|
if phone == "" {
|
||
|
|
return SendMsgResponse{
|
||
|
|
Status: "error",
|
||
|
|
Error: "missing phone in extras",
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// TODO: Implement SMS provider integration (Twilio, AWS SNS, etc.)
|
||
|
|
// For now, return error
|
||
|
|
return SendMsgResponse{
|
||
|
|
Status: "error",
|
||
|
|
Error: "SMS not implemented yet",
|
||
|
|
}
|
||
|
|
}
|
||
|
|||
|
|
// sendGotify sends a notification via Gotify server.
|
||
|
|
func (h *Handler) sendGotify(req SendMsgRequest) SendMsgResponse {
|
||
|
|
if h.gotifyURL == "" || h.gotifyToken == "" {
|
||
|
|
return SendMsgResponse{
|
||
|
|
Status: "error",
|
||
|
|
Error: "Gotify not configured (missing GOTIFY_URL or GOTIFY_TOKEN)",
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Construct Gotify message
|
||
|
|
gotifyReq := map[string]interface{}{
|
||
|
|
"title": req.Title,
|
||
|
|
"message": req.Message,
|
||
|
|
"priority": req.Priority,
|
||
|
|
}
|
||
|
|
|
||
|
|
// Marshal to JSON
|
||
|
|
body, err := json.Marshal(gotifyReq)
|
||
|
|
if err != nil {
|
||
|
|
log.Printf("error marshaling Gotify request: %v", err)
|
||
|
|
return SendMsgResponse{
|
||
|
|
Status: "error",
|
||
|
|
Error: "failed to marshal request: " + err.Error(),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// POST to Gotify
|
||
|
|
gotifyEndpoint := fmt.Sprintf("%s/message?token=%s", h.gotifyURL, h.gotifyToken)
|
||
|
|
resp, err := http.Post(gotifyEndpoint, "application/json", strings.NewReader(string(body)))
|
||
|
|
if err != nil {
|
||
|
|
log.Printf("error sending to Gotify: %v", err)
|
||
|
|
return SendMsgResponse{
|
||
|
|
Status: "error",
|
||
|
|
Error: "failed to send to Gotify: " + err.Error(),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
defer resp.Body.Close()
|
||
|
|
|
||
|
|
// Check response status
|
||
|
|
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
|
||
|
|
log.Printf("Gotify returned status %d", resp.StatusCode)
|
||
|
|
return SendMsgResponse{
|
||
|
|
Status: "error",
|
||
|
|
Error: fmt.Sprintf("Gotify returned status %d", resp.StatusCode),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Parse response
|
||
|
|
var gotifyResp map[string]interface{}
|
||
|
|
if err := json.NewDecoder(resp.Body).Decode(&gotifyResp); err != nil {
|
||
|
|
log.Printf("error decoding Gotify response: %v", err)
|
||
|
|
return SendMsgResponse{
|
||
|
|
Status: "success",
|
||
|
|
MessageID: "gotify-sent",
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Extract message ID if available
|
||
|
|
msgID := "gotify-sent"
|
||
|
|
if id, ok := gotifyResp["id"]; ok {
|
||
|
|
msgID = fmt.Sprintf("gotify-%v", id)
|
||
|
|
}
|
||
|
|
|
||
|
|
return SendMsgResponse{
|
||
|
|
Status: "success",
|
||
|
|
MessageID: msgID,
|
||
|
|
}
|
||
|
|
}
|