Rank log: inactive ranks (Guardian) count as promotions to and from, demotion when lost; Guardian is staff

This commit is contained in:
Astra 2026-09-29 17:19:53 +01:00
parent 611b79cdba
commit 0af93d1eab
7 changed files with 76 additions and 24 deletions

View file

@ -214,6 +214,20 @@ type rankChangeView struct {
New *rankView `json:"new,omitempty"`
}
// inactiveKind overrides the stored kind (which compares immunity) for changes to or from an
// inactive rank: moving between an inactive rank and any other rank is a promotion either way,
// and losing an inactive rank altogether is a demotion.
func inactiveKind(kind string, old, new *rankView) string {
oldIn, newIn := old != nil && old.Inactive, new != nil && new.Inactive
switch {
case old != nil && new != nil && oldIn != newIn:
return "promoted"
case oldIn && new == nil:
return "demoted"
}
return kind
}
// publicRankChanges is the promotions and demotions list, with each rank's label and colour.
func (s *Server) publicRankChanges(w http.ResponseWriter, r *http.Request) {
lim, off, num := page(r)
@ -232,11 +246,13 @@ func (s *Server) publicRankChanges(w http.ResponseWriter, r *http.Request) {
return nil
}
rk := s.site.rank(g, x.groupIndex(g))
return &rankView{Name: g, Label: rk.Label, Color: rk.Color, Supporter: rk.Supporter}
return &rankView{Name: g, Label: rk.Label, Color: rk.Color, Supporter: rk.Supporter, Inactive: rk.Inactive}
}
out := make([]rankChangeView, 0, len(items))
for _, c := range items {
out = append(out, rankChangeView{RankChange: c, Old: view(c.OldRank), New: view(c.NewRank)})
v := rankChangeView{RankChange: c, Old: view(c.OldRank), New: view(c.NewRank)}
v.Kind = inactiveKind(v.Kind, v.Old, v.New)
out = append(out, v)
}
writeJSON(w, http.StatusOK, newList(out, total, num))
}

View file

@ -0,0 +1,25 @@
package web
import "testing"
func TestInactiveKind(t *testing.T) {
guardian := &rankView{Name: "#rank/guardian", Inactive: true}
mod := &rankView{Name: "#rank/mod"}
owner := &rankView{Name: "#rank/owner"}
for _, c := range []struct {
kind string
old, new *rankView
want string
}{
{"demoted", owner, guardian, "promoted"},
{"promoted", guardian, mod, "promoted"},
{"removed", guardian, nil, "demoted"},
{"added", nil, guardian, "added"},
{"demoted", owner, mod, "demoted"},
{"removed", mod, nil, "removed"},
} {
if got := inactiveKind(c.kind, c.old, c.new); got != c.want {
t.Errorf("%v -> %v: got %s, want %s", c.old, c.new, got, c.want)
}
}
}

View file

@ -20,20 +20,23 @@ type Rank struct {
About string `json:"about"`
// Supporter ranks (VIPs) are listed apart from staff and don't count as staff online.
Supporter bool `json:"supporter"`
// Inactive ranks are for staff who are stepping back. In the rank log, moving between an
// inactive rank and any other rank is a promotion either way, and losing one is a demotion.
Inactive bool `json:"inactive"`
}
func defaultSite() Site {
return Site{
Title: "zio.sh",
Ranks: map[string]Rank{
"#rank/owner": {Label: "Owner", Color: "#fe113d", About: "Runs the server."},
"#rank/senioradmin": {Label: "Senior admin", Color: "#ff7a45", About: "Manages the staff team and server settings."},
"#rank/admin": {Label: "Admin", Color: "#f0a63a", About: "Handles appeals and longer bans."},
"#rank/trialadmin": {Label: "Trial admin", Color: "#e8d25a", About: "Admins on trial."},
"#rank/mod": {Label: "Moderator", Color: "#37c58f", About: "Keeps matches fair. Can kick, gag and issue short bans."},
"#rank/trialmod": {Label: "Trial mod", Color: "#4fb3d9", About: "Moderators on trial."},
"#rank/helper": {Label: "Helper", Color: "#7c8cf5", About: "Answers questions and handles chat."},
"#rank/guardian": {Label: "Guardian", Color: "#c77dff", About: "Supporters of the server. Not staff, and can't punish anyone.", Supporter: true},
"#rank/owner": {Label: "Owner", Color: "#fe113d", About: "Owns the server and looks after everything behind the scenes."},
"#rank/senioradmin": {Label: "Senior Admin", Color: "#ff7a45", About: "Looks after the staff team and helps out wherever they're needed."},
"#rank/admin": {Label: "Admin", Color: "#f0a63a", About: "Handles bans and appeals, and steps in when things get out of hand."},
"#rank/trialadmin": {Label: "Trial Admin", Color: "#e8d25a", About: "New to the admin team and still finding their feet."},
"#rank/mod": {Label: "Moderator", Color: "#37c58f", About: "Keeps an eye on games and deals with anyone ruining them."},
"#rank/trialmod": {Label: "Trial Mod", Color: "#4fb3d9", About: "New to the mod team and learning how things work."},
"#rank/helper": {Label: "Helper", Color: "#7c8cf5", About: "Happy to answer questions and help you find your way around."},
"#rank/guardian": {Label: "Guardian", Color: "#c77dff", About: "Staff taking a break from being active, but still part of the team.", Inactive: true},
},
}
}

View file

@ -142,6 +142,7 @@ type rankView struct {
Label string `json:"label"`
Color string `json:"color"`
Supporter bool `json:"supporter,omitempty"`
Inactive bool `json:"inactive,omitempty"`
}
func (s *Server) rankOf(x *staffIndex, steamid string) *rankView {
@ -154,7 +155,7 @@ func (s *Server) rankOf(x *staffIndex, steamid string) *rankView {
return &rankView{Label: "Staff", Color: "#8a91a0"}
}
r := s.site.rank(g, x.groupIndex(g))
return &rankView{Name: g, Label: r.Label, Color: r.Color, Supporter: r.Supporter}
return &rankView{Name: g, Label: r.Label, Color: r.Color, Supporter: r.Supporter, Inactive: r.Inactive}
}
// isStaff is true for admins whose rank isn't a supporter rank.