GroupVerify: Verification page, overrides and public Verified tags

Shown only where GroupVerify is: staff get the Verification page once its gv_* tables are in
the database, and the public site shows "Verified" only while it's on (enabled, with a group)
and not missing from css_plugins list, which the poller reads once a minute over RCON.

- Verification page: plugin state and version, Steam group, saved member list, overrides.
- Overrides (@zio/verify, not on yourself unless Root): give, change, remove; player drawer
  shows why a player is or isn't verified.
- Root sets the group and turns it on/off: saved to gv_settings, and sent as css_gv_* when the
  plugin is loaded so it applies at once.
- gv_* times use GroupVerify's own timezone setting.
- MySQL integration test (SAW_TEST_DSN), unit tests for plugin list parsing and the show rule.
This commit is contained in:
Astra 2026-10-02 17:59:26 +01:00
parent b2b4698776
commit 9e4bc63201
15 changed files with 1270 additions and 13 deletions

View file

@ -38,6 +38,8 @@ func toPublic(ps []store.Penalty) []publicPenalty {
type livePlayer struct {
live.Player
Rank *rankView `json:"rank,omitempty"`
// Verified by GroupVerify, when that's shown at all (see verifyInfo.Shown).
Verified bool `json:"verified,omitempty"`
// Staff-only fields, left empty on the public page.
Tags []string `json:"tags,omitempty"`
CanTarget bool `json:"canTarget,omitempty"`
@ -82,7 +84,9 @@ func (s *Server) publicServer(w http.ResponseWriter, r *http.Request) {
internal(w, r, err)
return
}
writeJSON(w, http.StatusOK, s.serverView(x, s.poll.Current()))
view := s.serverView(x, s.poll.Current())
s.markVerified(r.Context(), view.Players)
writeJSON(w, http.StatusOK, view)
}
// penaltyQuery reads ?status=&q=&page= (and for comms, a type in place of the status).
@ -286,8 +290,10 @@ func (s *Server) publicPlayer(w http.ResponseWriter, r *http.Request) {
return
}
_, online := s.poll.Current().Find(steamid)
// Only whether they're verified: why, and overrides' expiry and who gave them, are for staff.
st, verified := s.verifiedSet(r.Context(), []string{steamid})[steamid]
writeJSON(w, http.StatusOK, map[string]any{
"steamid": steamid, "name": d.Name, "online": online, "rank": s.rankOf(x, steamid),
"bans": toPublic(bans), "comms": toPublic(comms),
"verified": verified && st.Verified(), "bans": toPublic(bans), "comms": toPublic(comms),
})
}