Changeset 0.28.6 (#192)
This commit is contained in:
@@ -3,6 +3,7 @@ package handlers
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
@@ -11,6 +12,7 @@ import (
|
||||
|
||||
"git.gobha.me/xcaliber/chat-switchboard/events"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/models"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/notifications"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/store"
|
||||
)
|
||||
|
||||
@@ -279,3 +281,74 @@ func (h *NotificationHandler) DeletePreference(c *gin.Context) {
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"ok": true})
|
||||
}
|
||||
|
||||
// ── Admin Broadcast (v0.28.6) ───────────────
|
||||
|
||||
// POST /api/v1/admin/notifications/broadcast
|
||||
// Admin-only: sends a system.announcement notification to all active users.
|
||||
func (h *NotificationHandler) Broadcast(c *gin.Context) {
|
||||
var req struct {
|
||||
Title string `json:"title" binding:"required"`
|
||||
Message string `json:"message" binding:"required"`
|
||||
Level string `json:"level"` // info | warning | critical (default: info)
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
level := req.Level
|
||||
if level == "" {
|
||||
level = "info"
|
||||
}
|
||||
if level != "info" && level != "warning" && level != "critical" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "level must be info, warning, or critical"})
|
||||
return
|
||||
}
|
||||
|
||||
// Get all active user IDs
|
||||
userIDs, err := h.stores.Users.ListActiveUserIDs(c.Request.Context())
|
||||
if err != nil {
|
||||
log.Printf("[broadcast] failed to list active users: %v", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list users"})
|
||||
return
|
||||
}
|
||||
|
||||
if len(userIDs) == 0 {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "no active users", "count": 0})
|
||||
return
|
||||
}
|
||||
|
||||
// Fan out via notification service
|
||||
svc := notifications.Default()
|
||||
if svc == nil {
|
||||
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "notification service not available"})
|
||||
return
|
||||
}
|
||||
|
||||
template := models.Notification{
|
||||
Type: models.NotifTypeAnnouncement,
|
||||
Title: req.Title,
|
||||
Body: req.Message,
|
||||
}
|
||||
svc.NotifyMany(c.Request.Context(), userIDs, template)
|
||||
|
||||
// Also emit a real-time WS broadcast for immediate visibility
|
||||
if h.hub != nil {
|
||||
payload, _ := json.Marshal(map[string]string{
|
||||
"title": req.Title,
|
||||
"message": req.Message,
|
||||
"level": level,
|
||||
})
|
||||
for _, uid := range userIDs {
|
||||
h.hub.SendToUser(uid, events.Event{
|
||||
Label: "system.broadcast",
|
||||
Payload: payload,
|
||||
Ts: time.Now().UnixMilli(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("[broadcast] admin sent announcement to %d users: %s", len(userIDs), req.Title)
|
||||
c.JSON(http.StatusOK, gin.H{"message": "broadcast sent", "count": len(userIDs)})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user