package handler import ( "encoding/json" "fmt" "log/slog" "net/http" "strconv" "strings" "aiaa-notification-service/internal/adapter" "aiaa-notification-service/internal/cache" "aiaa-notification-service/internal/model" "aiaa-notification-service/internal/safew" "aiaa-notification-service/internal/store" "github.com/gin-gonic/gin" ) type ChannelHandler struct { store *store.Store cache *cache.Cache chats *safew.Watcher } func NewChannelHandler(s *store.Store, c *cache.Cache, w *safew.Watcher) *ChannelHandler { return &ChannelHandler{store: s, cache: c, chats: w} } type createChannelReq struct { Name string `json:"name" binding:"required"` Type string `json:"type" binding:"required"` Config json.RawMessage `json:"config" binding:"required"` Status int `json:"status"` } func (h *ChannelHandler) Create(c *gin.Context) { var req createChannelReq if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if req.Status == 0 { req.Status = 1 } raw := json.RawMessage(req.Config) ch := &model.Channel{Name: req.Name, Type: req.Type, Config: &raw, Status: req.Status} if err := h.store.CreateChannel(c.Request.Context(), ch); err != nil { c.JSON(http.StatusConflict, gin.H{"error": err.Error()}) return } c.JSON(http.StatusCreated, ch) } func (h *ChannelHandler) List(c *gin.Context) { var page store.PageFilter if err := c.ShouldBindQuery(&page); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } page.Normalize() channels, total, err := h.store.ListChannels(c.Request.Context(), page) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"data": channels, "total": total, "page": page.Page}) } func (h *ChannelHandler) Get(c *gin.Context) { id, _ := strconv.Atoi(c.Param("id")) ch, err := h.store.GetChannel(c.Request.Context(), id) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "channel not found"}) return } c.JSON(http.StatusOK, ch) } func (h *ChannelHandler) Update(c *gin.Context) { id, _ := strconv.Atoi(c.Param("id")) var req createChannelReq if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } raw := json.RawMessage(req.Config) ch := &model.Channel{Name: req.Name, Type: req.Type, Config: &raw, Status: req.Status} if err := h.store.UpdateChannel(c.Request.Context(), id, ch); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"ok": true}) } func (h *ChannelHandler) Delete(c *gin.Context) { id, _ := strconv.Atoi(c.Param("id")) if err := h.store.DeleteChannel(c.Request.Context(), id); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"ok": true}) } type listSafewChatsReq struct { Token string `json:"token"` Q string `json:"q"` } func (h *ChannelHandler) ListSafewChats(c *gin.Context) { var req listSafewChatsReq if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } h.respondSafewChats(c, strings.TrimSpace(req.Token), req.Q) } func (h *ChannelHandler) ListChannelSafewChats(c *gin.Context) { id, _ := strconv.Atoi(c.Param("id")) ch, err := h.store.GetChannel(c.Request.Context(), id) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "channel not found"}) return } token, err := safewTokenFromChannel(ch) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } h.respondSafewChats(c, token, c.Query("q")) } func (h *ChannelHandler) respondSafewChats(c *gin.Context, token, q string) { if token == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "safew token is required"}) return } if h.chats == nil { c.JSON(http.StatusServiceUnavailable, gin.H{"error": "safew chat list unavailable"}) return } h.chats.Ensure(token) if err := h.chats.Refresh(c.Request.Context(), token); err != nil { if _, ok := err.(*adapter.SafewAuthError); ok { c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()}) return } slog.Warn("safew refresh", "error", err) } list, err := h.chats.List(c.Request.Context(), token, q) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } if list == nil { list = []adapter.SafewChat{} } c.JSON(http.StatusOK, gin.H{"data": list, "total": len(list)}) } func safewTokenFromChannel(ch *model.Channel) (string, error) { if ch.Type != "safew" { return "", fmt.Errorf("channel is not safew") } if ch.Config == nil { return "", fmt.Errorf("safew token is required") } var cfg struct { Token string `json:"token"` } if err := json.Unmarshal(*ch.Config, &cfg); err != nil { return "", fmt.Errorf("safew token is required") } token := strings.TrimSpace(cfg.Token) if token == "" { return "", fmt.Errorf("safew token is required") } return token, nil }