RC-10-B
release-tag / release-image (push) Successful in 4m42s

This commit is contained in:
2026-08-11 22:46:15 +02:00
parent a88e75a8c4
commit d76f1c3d25
6 changed files with 204 additions and 23 deletions
+74 -16
View File
@@ -38,22 +38,22 @@ import (
)
type Server struct {
store *data.Store
auth *auth.Manager
settings *settings.Manager
hub *wsx.Hub
runtime *rtx.State
artifactWorker *artifact.Worker
lottery *guessLottery
adminUser, adminPass, staticDir, artifactDir string
internalServiceSecret string
customerServiceInternalURL string
customerServiceHTTP *http.Client
upgrader websocket.Upgrader
wsAllowedOrigins map[string]struct{}
maxUserWS, maxLeaderboardWS int64
userWSCount, leaderboardWSCount atomic.Int64
adminSessions sync.Map // sid -> exp unix seconds
store *data.Store
auth *auth.Manager
settings *settings.Manager
hub *wsx.Hub
runtime *rtx.State
artifactWorker *artifact.Worker
lottery *guessLottery
adminUser, adminPass, staticDir, artifactDir string
internalServiceSecret string
customerServiceInternalURL string
customerServiceHTTP *http.Client
upgrader websocket.Upgrader
wsAllowedOrigins map[string]struct{}
maxUserWS, maxLeaderboardWS int64
userWSCount, leaderboardWSCount, adminWSCount atomic.Int64
adminSessions sync.Map // sid -> exp unix seconds
}
func New(store *data.Store, a *auth.Manager, sm *settings.Manager, hub *wsx.Hub, runtimeState *rtx.State, artifactDir string, artifactWorker *artifact.Worker) *Server {
@@ -331,6 +331,7 @@ func (s *Server) Routes() http.Handler {
r.Group(func(r chi.Router) {
r.Use(func(n http.Handler) http.Handler { return s.require("admin", n) })
r.Get("/api/admin/session", s.adminSession)
r.Get("/api/admin/ws", s.adminWS)
r.Get("/api/admin/overview", s.adminOverview)
r.Get("/api/admin/performance", s.adminPerformance)
r.Get("/api/admin/profiles/cleanup-preview", s.adminProfileCleanupPreview)
@@ -869,6 +870,18 @@ func (s *Server) guess(w http.ResponseWriter, r *http.Request) {
p.Score = round(p.Score, s.settings.Get().PublicScorePrecision)
s.hub.PublishPoint(id, c.ClientID, p)
}
// Admin-only live telemetry: expose the score of every actually evaluated
// guess, even when it is below the client's personal best. This event is
// ephemeral and never reaches public/user websocket clients.
_ = s.hub.PublishAdmin(r.Context(), wsx.Event{Type: "guess_signal", TaskID: id, Data: map[string]any{
"client_id": c.ClientID,
"seq": in.Seq,
"score": round(score, 2),
"best_score": round(accepted.State.BestScore, 2),
"improved": accepted.Improved,
"correct": correct,
}})
if correct {
rewardOwner := s.store.RewardOwnerForWorker(r.Context(), c.ClientID)
dataOut := map[string]string{"winner_client_id": rewardOwner, "winner_worker_client_id": c.ClientID}
@@ -1190,6 +1203,51 @@ func (s *Server) publicArtifactPreview(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write(preview)
}
func (s *Server) adminWS(w http.ResponseWriter, r *http.Request) {
// The route itself is behind require("admin"), so the HttpOnly admin session
// cookie is validated before the websocket upgrade. Keep a small independent
// cap because this stream can contain one event for every evaluated guess.
if !acquireWSCap(&s.adminWSCount, 32) {
http.Error(w, "admin websocket capacity reached", http.StatusServiceUnavailable)
return
}
defer s.adminWSCount.Add(-1)
conn, err := s.upgrader.Upgrade(w, r, nil)
if err != nil {
return
}
cl := wsx.NewAdminClient(conn)
s.hub.Add(cl)
defer s.hub.Remove(cl)
conn.SetReadLimit(1024)
_ = conn.SetReadDeadline(time.Now().Add(90 * time.Second))
conn.SetPongHandler(func(string) error { return conn.SetReadDeadline(time.Now().Add(90 * time.Second)) })
ping := time.NewTicker(30 * time.Second)
defer ping.Stop()
done := make(chan struct{})
go func() {
defer close(done)
for {
if _, _, err := conn.ReadMessage(); err != nil {
return
}
}
}()
for {
select {
case <-done:
return
case <-r.Context().Done():
return
case <-ping.C:
if err := cl.Ping(); err != nil {
return
}
}
}
}
func (s *Server) leaderboardWS(w http.ResponseWriter, r *http.Request) {
if !acquireWSCap(&s.leaderboardWSCount, s.maxLeaderboardWS) {
http.Error(w, "leaderboard websocket capacity reached", http.StatusServiceUnavailable)