mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-13 05:12:06 +00:00
feat(observability): log authenticated read routes
This commit is contained in:
+20
-3
@@ -124,7 +124,7 @@ type Service struct {
|
||||
TierPolicy domain.TierPolicy
|
||||
RateLimiter *RateLimiter
|
||||
// Log receives a credential-safe structured event for lifecycle-relevant
|
||||
// mutations (currently: server registration and result submission). Nil
|
||||
// reads and mutations. Nil
|
||||
// is a valid, silent no-op -- every call site must stay optional so
|
||||
// existing Service literals that don't set it keep working unchanged.
|
||||
Log func(observability.Event)
|
||||
@@ -601,15 +601,18 @@ func (s *Service) queueMutation(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
var ticket domain.QueueTicket
|
||||
var err error
|
||||
now := s.now()
|
||||
if s.QueueBackend != nil {
|
||||
ticket, err = s.QueueBackend.Get(r.Context(), playerID, parts[0], s.now())
|
||||
ticket, err = s.QueueBackend.Get(r.Context(), playerID, parts[0], now)
|
||||
} else {
|
||||
ticket, err = s.Queue.Get(playerID, parts[0], s.now())
|
||||
ticket, err = s.Queue.Get(playerID, parts[0], now)
|
||||
}
|
||||
if err != nil {
|
||||
s.logEvent(observability.Event{Event: "queue_get", QueueID: parts[0], Stage: "rejected", OccurredAt: now})
|
||||
writeDomainError(w, err)
|
||||
return
|
||||
}
|
||||
s.logEvent(observability.Event{Event: "queue_get", QueueID: ticket.TicketID, Stage: strings.ToLower(string(ticket.State)), OccurredAt: now})
|
||||
writeJSON(w, http.StatusOK, toQueueResponse(ticket))
|
||||
return
|
||||
}
|
||||
@@ -695,6 +698,7 @@ func (s *Service) proposalMutation(w http.ResponseWriter, r *http.Request) {
|
||||
if s.ProposalBackend != nil {
|
||||
proposalValue, providerErr := s.ProposalBackend.Get(r.Context(), playerID, parts[0], s.now())
|
||||
if providerErr != nil {
|
||||
s.logEvent(observability.Event{Event: "proposal_get", ProposalID: parts[0], Stage: "rejected", OccurredAt: s.now()})
|
||||
writeError(w, http.StatusNotFound, "not_found")
|
||||
return
|
||||
}
|
||||
@@ -702,6 +706,7 @@ func (s *Service) proposalMutation(w http.ResponseWriter, r *http.Request) {
|
||||
exists = true
|
||||
}
|
||||
if !exists || proposal == nil || !proposal.HasParticipant(playerID) {
|
||||
s.logEvent(observability.Event{Event: "proposal_get", ProposalID: parts[0], Stage: "rejected", OccurredAt: s.now()})
|
||||
writeError(w, http.StatusNotFound, "not_found")
|
||||
return
|
||||
}
|
||||
@@ -709,6 +714,7 @@ func (s *Service) proposalMutation(w http.ResponseWriter, r *http.Request) {
|
||||
if proposal.Expire(now) {
|
||||
s.publishProposalEvent(*proposal, now)
|
||||
}
|
||||
s.logEvent(observability.Event{Event: "proposal_get", ProposalID: proposal.ProposalID, Stage: strings.ToLower(string(proposal.State)), OccurredAt: now})
|
||||
writeJSON(w, http.StatusOK, toProposalResponse(*proposal))
|
||||
return
|
||||
}
|
||||
@@ -776,20 +782,24 @@ func (s *Service) assignment(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
if s.Assignment == nil {
|
||||
s.logEvent(observability.Event{Event: "assignment_get", MatchID: parts[0], Stage: "rejected", OccurredAt: s.now()})
|
||||
writeError(w, http.StatusServiceUnavailable, "assignment_unavailable")
|
||||
return
|
||||
}
|
||||
now := s.now()
|
||||
view, err := s.Assignment(r.Context(), playerID, parts[0], now)
|
||||
if err != nil || view.MatchID != parts[0] || view.PlayerID != playerID {
|
||||
s.logEvent(observability.Event{Event: "assignment_get", MatchID: parts[0], ServerID: view.ServerID, Stage: "rejected", OccurredAt: now})
|
||||
writeError(w, http.StatusNotFound, "not_found")
|
||||
return
|
||||
}
|
||||
if view.ServerID == "" || view.Slot < 0 || view.Slot > 5 || view.ProtocolVersion < 1 || (view.Transport != "enet" && view.Transport != "steam_sdr") || view.JoinAuthorisation == "" || !validAssignmentEndpoint(view.Endpoint) || view.ExpiresAt.IsZero() || !now.Before(view.ExpiresAt) {
|
||||
s.logEvent(observability.Event{Event: "assignment_get", MatchID: parts[0], ServerID: view.ServerID, Stage: "rejected", OccurredAt: now})
|
||||
writeError(w, http.StatusServiceUnavailable, "assignment_unavailable")
|
||||
return
|
||||
}
|
||||
_ = s.PublishControlPlaneEvent(assignmentChangedEvent(view, now))
|
||||
s.logEvent(observability.Event{Event: "assignment_get", MatchID: view.MatchID, ServerID: view.ServerID, Stage: "assignment_ready", OccurredAt: now})
|
||||
writeJSON(w, http.StatusOK, view)
|
||||
}
|
||||
|
||||
@@ -830,13 +840,16 @@ func (s *Service) profile(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
profile, exists, err := s.rankedProfileFor(r.Context(), playerID)
|
||||
if err != nil {
|
||||
s.logEvent(observability.Event{Event: "profile_get", Stage: "rejected", OccurredAt: s.now()})
|
||||
writeError(w, http.StatusServiceUnavailable, "ranked_profile_unavailable")
|
||||
return
|
||||
}
|
||||
if !exists {
|
||||
s.logEvent(observability.Event{Event: "profile_get", Stage: "not_found", OccurredAt: s.now()})
|
||||
writeError(w, http.StatusNotFound, "not_found")
|
||||
return
|
||||
}
|
||||
s.logEvent(observability.Event{Event: "profile_get", Stage: "ok", OccurredAt: s.now()})
|
||||
writeJSON(w, http.StatusOK, map[string]any{
|
||||
"player_id": playerID,
|
||||
"rating": profile.Value,
|
||||
@@ -856,18 +869,22 @@ func (s *Service) rankedProfile(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
profile, exists, err := s.rankedProfileFor(r.Context(), playerID)
|
||||
if err != nil {
|
||||
s.logEvent(observability.Event{Event: "ranked_profile_get", Stage: "rejected", OccurredAt: s.now()})
|
||||
writeError(w, http.StatusServiceUnavailable, "ranked_profile_unavailable")
|
||||
return
|
||||
}
|
||||
if !exists {
|
||||
s.logEvent(observability.Event{Event: "ranked_profile_get", Stage: "not_found", OccurredAt: s.now()})
|
||||
writeError(w, http.StatusNotFound, "not_found")
|
||||
return
|
||||
}
|
||||
tier, err := domain.RankedTier(profile, s.TierPolicy)
|
||||
if err != nil {
|
||||
s.logEvent(observability.Event{Event: "ranked_profile_get", Stage: "rejected", OccurredAt: s.now()})
|
||||
writeError(w, http.StatusServiceUnavailable, "ranked_profile_unavailable")
|
||||
return
|
||||
}
|
||||
s.logEvent(observability.Event{Event: "ranked_profile_get", Stage: "ok", OccurredAt: s.now()})
|
||||
writeJSON(w, http.StatusOK, rankedProfileResponse{Rating: profile.Value, RD: profile.RD, Volatility: profile.Volatility, RankedGames: profile.RankedGames, Tier: string(tier), Provisional: domain.RankedIsProvisional(profile), SeasonID: profile.LastSeasonID})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user