package server import ( "net/http" "time" "github.com/gin-gonic/gin" "scrabble/backend/internal/banview" ) // banSyncRequest mirrors the gateway's active-ban report: every entry is one // currently-enforced IP ban. type banSyncRequest struct { Active []banSyncEntry `json:"active"` } // banSyncEntry is one active ban in the sync request. type banSyncEntry struct { IP string `json:"ip"` Reason string `json:"reason"` Since time.Time `json:"since"` Expires time.Time `json:"expires"` } // banSyncResponse returns the IPs an operator has marked for unban for the gateway // to apply on its next sync. type banSyncResponse struct { Unban []string `json:"unban"` } // handleBanSync ingests the gateway's active-ban report into the ban view (the // admin console's active-bans panel) and returns the operator's pending unbans. // Internal, gateway-only: like the rate-limit report it trusts the network // segment and carries no user identity. func (s *Server) handleBanSync(c *gin.Context) { var req banSyncRequest if err := c.ShouldBindJSON(&req); err != nil { abortBadRequest(c, "invalid ban sync") return } bans := make([]banview.Ban, 0, len(req.Active)) for _, e := range req.Active { bans = append(bans, banview.Ban{IP: e.IP, Reason: e.Reason, Since: e.Since, Expires: e.Expires}) } s.banview.Ingest(bans) c.JSON(http.StatusOK, banSyncResponse{Unban: s.banview.DrainUnbans()}) }