feat: notify handler — parse, match, evaluate, render, route
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"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 {
|
||||
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)
|
||||
go func() {
|
||||
payloadJSON, _ := json.Marshal(msg.Data)
|
||||
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",
|
||||
}
|
||||
_ = h.store.CreateMessageLog(c.Request.Context(), ml)
|
||||
}
|
||||
}()
|
||||
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user