Stage 17 (#15): admin users people/robots toggle + display-name & external-id glob filters
CI / changes (pull_request) Successful in 1s
CI / unit (pull_request) Successful in 8s
CI / integration (pull_request) Successful in 13s
CI / ui (pull_request) Successful in 27s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m11s

- account.ListUsers/CountUsers with a UserFilter: people vs robots (by a robot identity),
  case-insensitive '*'/'?' glob masks on display_name and any identity's external_id
- admin users list shows the real kind (robot/guest/registered), defaults to people,
  with a People/Robots toggle + a filter form; pager preserves the filter
- integration test for the filter; SQL verified against the live contour DB
This commit is contained in:
Ilia Denisov
2026-06-06 14:14:28 +02:00
parent b15fd30c4f
commit 54497374e4
5 changed files with 230 additions and 11 deletions
@@ -4,6 +4,7 @@ import (
"bytes"
"fmt"
"net/http"
"net/url"
"path/filepath"
"strconv"
"strings"
@@ -75,24 +76,45 @@ func (s *Server) consoleDashboard(c *gin.Context) {
func (s *Server) consoleUsers(c *gin.Context) {
ctx := c.Request.Context()
page := consolePage(c)
total, _ := s.accounts.CountAccounts(ctx)
accs, err := s.accounts.ListAccounts(ctx, adminPageSize, (page-1)*adminPageSize)
filter := account.UserFilter{
Robots: c.Query("kind") == "robots",
NameMask: c.Query("name"),
ExternalIDMask: c.Query("ext"),
}
total, _ := s.accounts.CountUsers(ctx, filter)
items, err := s.accounts.ListUsers(ctx, filter, adminPageSize, (page-1)*adminPageSize)
if err != nil {
s.consoleError(c, err)
return
}
view := adminconsole.UsersView{Pager: adminconsole.NewPager(page, adminPageSize, total)}
ids := make([]uuid.UUID, 0, len(accs))
for _, a := range accs {
q := url.Values{}
if filter.Robots {
q.Set("kind", "robots")
}
if strings.TrimSpace(filter.NameMask) != "" {
q.Set("name", filter.NameMask)
}
if strings.TrimSpace(filter.ExternalIDMask) != "" {
q.Set("ext", filter.ExternalIDMask)
}
view := adminconsole.UsersView{
Pager: adminconsole.NewPager(page, adminPageSize, total),
Robots: filter.Robots, NameMask: filter.NameMask, ExternalIDMask: filter.ExternalIDMask,
FilterQuery: q.Encode(),
}
ids := make([]uuid.UUID, 0, len(items))
for _, it := range items {
kind := "registered"
if a.IsGuest {
if it.IsRobot {
kind = "robot"
} else if it.IsGuest {
kind = "guest"
}
view.Items = append(view.Items, adminconsole.UserRow{
ID: a.ID.String(), DisplayName: a.DisplayName, Kind: kind,
Language: a.PreferredLanguage, Guest: a.IsGuest, CreatedAt: fmtTime(a.CreatedAt),
ID: it.ID.String(), DisplayName: it.DisplayName, Kind: kind,
Language: it.PreferredLanguage, Guest: it.IsGuest, CreatedAt: fmtTime(it.CreatedAt),
})
ids = append(ids, a.ID)
ids = append(ids, it.ID)
}
if stats, err := s.games.MoveDurationStats(ctx, ids); err == nil {
for i := range view.Items {