146 lines
3.7 KiB
Go
146 lines
3.7 KiB
Go
package handler
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"log/slog"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"aiaa-notification-service/internal/cache"
|
|
"aiaa-notification-service/internal/condition"
|
|
"aiaa-notification-service/internal/engine"
|
|
"aiaa-notification-service/internal/model"
|
|
"aiaa-notification-service/internal/parser"
|
|
"aiaa-notification-service/internal/store"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
type NotifyHandler struct {
|
|
store *store.Store
|
|
cache *cache.Cache // used by engine
|
|
matcher *engine.Matcher
|
|
renderer *engine.Renderer
|
|
router *engine.Router
|
|
}
|
|
|
|
func NewNotifyHandler(s *store.Store, c *cache.Cache, m *engine.Matcher, r *engine.Renderer, rt *engine.Router) *NotifyHandler {
|
|
return &NotifyHandler{store: s, cache: c, matcher: m, renderer: r, router: rt}
|
|
}
|
|
|
|
func (h *NotifyHandler) Handle(c *gin.Context) {
|
|
src := c.MustGet("source").(*model.Source)
|
|
|
|
// 1. Read raw body
|
|
body, err := io.ReadAll(c.Request.Body)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "read body failed"})
|
|
return
|
|
}
|
|
|
|
// 2. Parse message
|
|
p, err := parser.NewParser(src.ParseMode, src.ParsePattern)
|
|
if err != nil {
|
|
c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "parser setup: " + err.Error()})
|
|
return
|
|
}
|
|
msg, err := p.Parse(body)
|
|
if err != nil {
|
|
c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "parse failed: " + err.Error()})
|
|
return
|
|
}
|
|
|
|
// 3. Match rule
|
|
rule, err := h.matcher.Match(c.Request.Context(), src.ID, msg.Event)
|
|
if err != nil {
|
|
// No matching rule → 200 with matched: false
|
|
c.JSON(http.StatusOK, gin.H{"matched": false})
|
|
return
|
|
}
|
|
|
|
// 4. Evaluate conditions
|
|
if rule.Conditions != nil {
|
|
var conds []model.Condition
|
|
if err := json.Unmarshal(*rule.Conditions, &conds); err != nil {
|
|
slog.Error("failed to unmarshal rule conditions", "rule_id", rule.ID, "error", err)
|
|
c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "invalid rule conditions"})
|
|
return
|
|
}
|
|
if !condition.Evaluate(conds, msg.Data) {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"matched": true,
|
|
"filtered": true,
|
|
"reason": "condition not met",
|
|
})
|
|
return
|
|
}
|
|
}
|
|
|
|
// 5. Get template content
|
|
tmpl, err := h.store.GetTemplate(c.Request.Context(), rule.TemplateID)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "template not found"})
|
|
return
|
|
}
|
|
|
|
// 6. Render template
|
|
content, err := h.renderer.Render(tmpl.Content, msg.Data)
|
|
if err != nil {
|
|
c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "template render failed: " + err.Error()})
|
|
return
|
|
}
|
|
|
|
// 7. Route to channels
|
|
title := src.Name + ": " + msg.Event
|
|
channels := h.router.Route(c.Request.Context(), rule, title, content)
|
|
|
|
// 8. Log message (best effort, detached context)
|
|
go func() {
|
|
payloadJSON, _ := json.Marshal(msg.Data)
|
|
ctx := context.Background()
|
|
for _, chName := range channels {
|
|
chID := parseChannelID(chName)
|
|
ml := &model.MessageLog{
|
|
RuleID: rule.ID,
|
|
ChannelID: chID,
|
|
Source: src.Name,
|
|
Event: msg.Event,
|
|
Payload: payloadJSON,
|
|
Content: content,
|
|
Status: "pending",
|
|
}
|
|
if err := h.store.CreateMessageLog(ctx, ml); err != nil {
|
|
slog.Warn("failed to create message log", "error", err)
|
|
}
|
|
}
|
|
}()
|
|
|
|
slog.Info("notification accepted",
|
|
"source", src.Name,
|
|
"event", msg.Event,
|
|
"channels", channels,
|
|
)
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"matched": true,
|
|
"channels": channels,
|
|
"accepted": true,
|
|
})
|
|
}
|
|
|
|
// parseChannelID extracts the numeric channel ID from a channel name formatted as "type:id".
|
|
func parseChannelID(chName string) int {
|
|
idx := strings.LastIndex(chName, ":")
|
|
if idx < 0 || idx == len(chName)-1 {
|
|
return 0
|
|
}
|
|
id, err := strconv.Atoi(chName[idx+1:])
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
return id
|
|
}
|