RC-15-PLACE
release-tag / release-image (push) Successful in 3m56s

This commit is contained in:
2026-08-14 10:13:57 +02:00
parent 47c523dd98
commit 52d33b25fd
13 changed files with 1008 additions and 43 deletions
+60 -21
View File
@@ -38,22 +38,23 @@ 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, adminWSCount atomic.Int64
adminSessions sync.Map // sid -> exp unix seconds
store *data.Store
auth *auth.Manager
settings *settings.Manager
hub *wsx.Hub
placeHub *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, maxPlaceWS int64
userWSCount, leaderboardWSCount, placeWSCount, 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 {
@@ -62,6 +63,7 @@ func New(store *data.Store, a *auth.Manager, sm *settings.Manager, hub *wsx.Hub,
auth: a,
settings: sm,
hub: hub,
placeHub: wsx.New(),
runtime: runtimeState,
artifactWorker: artifactWorker,
lottery: newGuessLottery(func(d beaconDrawAudit) {
@@ -77,6 +79,7 @@ func New(store *data.Store, a *auth.Manager, sm *settings.Manager, hub *wsx.Hub,
wsAllowedOrigins: parseOriginAllowlist(os.Getenv("WS_ALLOWED_ORIGINS")),
maxUserWS: int64(envIntServer("WS_MAX_USER_CONNECTIONS", 5000)),
maxLeaderboardWS: int64(envIntServer("WS_MAX_LEADERBOARD_CONNECTIONS", 500)),
maxPlaceWS: int64(envIntServer("WS_MAX_PLACE_CONNECTIONS", 1500)),
}
s.upgrader = websocket.Upgrader{CheckOrigin: s.checkWSOrigin, Subprotocols: []string{"neuralhunt.v1"}}
return s
@@ -293,7 +296,7 @@ func (s *Server) Routes() http.Handler {
w.Header().Set("Strict-Transport-Security", "max-age=31536000")
w.Header().Set("Cross-Origin-Opener-Policy", "same-origin")
path := strings.ToLower(req.URL.Path)
if path == "/" || path == "/admin" || path == "/leaderboard" || strings.HasPrefix(path, "/api/admin") || strings.HasPrefix(path, "/api/auth") || strings.HasSuffix(path, ".html") || strings.HasSuffix(path, ".js") || strings.HasSuffix(path, ".css") {
if path == "/" || path == "/admin" || path == "/leaderboard" || path == "/place" || strings.HasPrefix(path, "/api/admin") || strings.HasPrefix(path, "/api/auth") || strings.HasSuffix(path, ".html") || strings.HasSuffix(path, ".js") || strings.HasSuffix(path, ".css") {
w.Header().Set("Cache-Control", "no-store")
}
next.ServeHTTP(w, req)
@@ -304,9 +307,12 @@ func (s *Server) Routes() http.Handler {
r.Get("/api/public/artifacts", s.publicArtifacts)
r.Get("/api/public/beacon/{id}/latest", s.latestBeaconDraw)
r.Get("/api/public/tasks", s.publicTaskCatalog)
r.Get("/api/public/place", s.publicPlace)
r.Get("/api/public/place/changes", s.publicPlaceChanges)
r.Get("/api/public/artifacts/{id}/preview", s.publicArtifactPreview)
r.Get("/api/public/tasks/{id}/style-reference", s.publicTaskStyleReference)
r.Get("/api/leaderboard/ws", s.leaderboardWS)
r.Get("/api/place/ws", s.placeWS)
r.Post("/api/auth/challenge", s.challenge)
r.Post("/api/auth/login", s.login)
r.Post("/api/admin/login", s.adminLogin)
@@ -323,6 +329,8 @@ func (s *Server) Routes() http.Handler {
r.Post("/api/tasks/{id}/guess", s.guess)
r.Get("/api/tasks/{id}/points", s.points)
r.Get("/api/me", s.me)
r.Get("/api/place/me", s.placeMe)
r.Post("/api/place/pixel", s.placePixel)
r.Get("/api/me/artifacts", s.myArtifacts)
r.Get("/api/me/artifacts/{id}/download", s.myArtifactDownload)
r.Post("/api/me/customer-link", s.customerLinkCode)
@@ -847,7 +855,11 @@ func (s *Server) guess(w http.ResponseWriter, r *http.Request) {
if correct && draw.BeaconEnabled {
beaconPath, beaconBoost, beaconRound = draw.ChosenPath, draw.BoostedPath, draw.BeaconRound
}
p, err := s.store.PersistImprovement(r.Context(), t, c.ClientID, rewardOwner, accepted.State.NextSeq, accepted.State.GuessCount, accepted.State.LastGuess, accepted.State.BestScore, in.Guess, in.Signature, correct, beaconPath, beaconBoost, beaconRound)
placePointsPerScore := 0
if placeCfg := s.settings.Get(); placeCfg.PlaceEnabled == 1 {
placePointsPerScore = placeCfg.PlacePointsPerScore
}
p, err := s.store.PersistImprovement(r.Context(), t, c.ClientID, rewardOwner, accepted.State.NextSeq, accepted.State.GuessCount, accepted.State.LastGuess, accepted.State.BestScore, in.Guess, in.Signature, correct, beaconPath, beaconBoost, beaconRound, placePointsPerScore)
if err != nil {
s.runtime.Restore(t.Task, c.ClientID, accepted.State.NextSeq, accepted.Previous)
switch {
@@ -1346,6 +1358,7 @@ type profileCleanupPreview struct {
InactiveForSeconds int64 `json:"inactive_for_seconds"`
Eligible int `json:"eligible"`
ProtectedWinners int64 `json:"protected_winners"`
ProtectedPlace int64 `json:"protected_place"`
ProtectedConnected int `json:"protected_connected"`
OldestEligibleMS int64 `json:"oldest_eligible_ms,omitempty"`
NewestEligibleMS int64 `json:"newest_eligible_ms,omitempty"`
@@ -1374,8 +1387,12 @@ func (s *Server) profileCleanupPreview(ctx context.Context, inactiveForSeconds i
if err != nil {
return profileCleanupPreview{}, nil, err
}
protectedPlace, err := s.store.OldPlaceParticipantCount(ctx, cutoff)
if err != nil {
return profileCleanupPreview{}, nil, err
}
ids := make([]string, 0, len(candidates))
out := profileCleanupPreview{CutoffMS: cutoff, InactiveForSeconds: inactiveForSeconds, ProtectedWinners: protectedWinners}
out := profileCleanupPreview{CutoffMS: cutoff, InactiveForSeconds: inactiveForSeconds, ProtectedWinners: protectedWinners, ProtectedPlace: protectedPlace}
for _, c := range candidates {
if s.runtime.IsConnected(c.ClientID) {
out.ProtectedConnected++
@@ -1437,6 +1454,7 @@ func (s *Server) adminProfileCleanup(w http.ResponseWriter, r *http.Request) {
"deleted": len(deleted),
"eligible_before": preview.Eligible,
"protected_winners": preview.ProtectedWinners,
"protected_place": preview.ProtectedPlace,
"protected_connected": preview.ProtectedConnected,
"cutoff_ms": preview.CutoffMS,
"inactive_for_seconds": seconds,
@@ -1457,11 +1475,16 @@ func (s *Server) adminOverview(w http.ResponseWriter, r *http.Request) {
func (s *Server) adminPerformance(w http.ResponseWriter, r *http.Request) {
rm := s.runtime.Metrics()
wm := s.hub.Metrics()
pm := wsx.Metrics{}
if s.placeHub != nil {
pm = s.placeHub.Metrics()
}
var ms gort.MemStats
gort.ReadMemStats(&ms)
jsonOut(w, 200, map[string]any{
"runtime": rm,
"websocket": wm,
"runtime": rm,
"websocket": wm,
"place_websocket": pm,
"process": map[string]any{
"goroutines": gort.NumGoroutine(),
"heap_bytes": ms.HeapAlloc,
@@ -1480,6 +1503,22 @@ func (s *Server) adminSettingsPut(w http.ResponseWriter, r *http.Request) {
jsonOut(w, 400, map[string]string{"error": "bad json"})
return
}
if err := settings.Validate(v); err != nil {
jsonOut(w, 400, map[string]string{"error": err.Error()})
return
}
current := s.settings.Get()
if v.PlaceWidth < current.PlaceWidth || v.PlaceHeight < current.PlaceHeight {
outside, err := s.store.PlacePixelsOutside(r.Context(), v.PlaceWidth, v.PlaceHeight)
if err != nil {
jsonOut(w, 500, map[string]string{"error": "place canvas bounds check failed"})
return
}
if outside > 0 {
jsonOut(w, 400, map[string]string{"error": fmt.Sprintf("place canvas cannot shrink: %d existing pixels would fall outside the new dimensions", outside)})
return
}
}
if err := s.settings.Update(r.Context(), v); err != nil {
jsonOut(w, 400, map[string]string{"error": err.Error()})
return