Stage 17 round 6 (#18, PR D): admin Messages moderation section
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 11s
CI / ui (pull_request) Successful in 32s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m14s
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 11s
CI / ui (pull_request) Successful in 32s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m14s
A new /_gm/messages console page lists posted chat messages (nudges excluded) newest-first — time, source (guest/robot/oldest identity kind), sender (linked to the user card), IP, body, game (linked to the game card) — searchable by sender name / external-id glob masks and pinnable to one game (?game=) or sender (?user=), linked from the game and user cards. The list query lives in social (raw SQL, kind='message', source via a SQL CASE), reusing the now-exported account.LikePattern. Server-rendered adminconsole MessagesView + messages.gohtml, 50/page via the shared pager. Tests: adminconsole render case; backend integration AdminListMessages (real Postgres) — nudge exclusion, game/sender pins, glob masks, source. Docs: ARCHITECTURE section 8 chat moderation, PLAN round-6.
This commit is contained in:
@@ -19,6 +19,7 @@ import (
|
||||
"scrabble/backend/internal/engine"
|
||||
"scrabble/backend/internal/game"
|
||||
"scrabble/backend/internal/robot"
|
||||
"scrabble/backend/internal/social"
|
||||
)
|
||||
|
||||
// adminPageSize is the page size of the admin console's paginated lists.
|
||||
@@ -51,6 +52,7 @@ func (s *Server) registerConsole(router *gin.Engine) {
|
||||
gm.GET("/complaints", s.consoleComplaints)
|
||||
gm.GET("/complaints/:id", s.consoleComplaintDetail)
|
||||
gm.POST("/complaints/:id/resolve", s.consoleResolveComplaint)
|
||||
gm.GET("/messages", s.consoleMessages)
|
||||
gm.GET("/dictionary", s.consoleDictionary)
|
||||
gm.POST("/dictionary/reload", s.consoleReloadDictionary)
|
||||
gm.POST("/dictionary/changes/apply", s.consoleApplyChanges)
|
||||
@@ -130,6 +132,60 @@ func (s *Server) consoleUsers(c *gin.Context) {
|
||||
s.renderConsole(c, "users", "users", "Users", view)
|
||||
}
|
||||
|
||||
// consoleMessages renders the paginated chat-message moderation list, optionally pinned to
|
||||
// one game (?game=) or sender (?user=) and filtered by sender glob masks (?name / ?ext).
|
||||
func (s *Server) consoleMessages(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
page := consolePage(c)
|
||||
gameID, _ := uuid.Parse(strings.TrimSpace(c.Query("game")))
|
||||
userID, _ := uuid.Parse(strings.TrimSpace(c.Query("user")))
|
||||
filter := social.AdminMessageFilter{
|
||||
GameID: gameID,
|
||||
SenderID: userID,
|
||||
NameMask: c.Query("name"),
|
||||
ExtMask: c.Query("ext"),
|
||||
}
|
||||
total, _ := s.social.AdminCountMessages(ctx, filter)
|
||||
items, err := s.social.AdminListMessages(ctx, filter, adminPageSize, (page-1)*adminPageSize)
|
||||
if err != nil {
|
||||
s.consoleError(c, err)
|
||||
return
|
||||
}
|
||||
q := url.Values{}
|
||||
if filter.GameID != uuid.Nil {
|
||||
q.Set("game", filter.GameID.String())
|
||||
}
|
||||
if filter.SenderID != uuid.Nil {
|
||||
q.Set("user", filter.SenderID.String())
|
||||
}
|
||||
if strings.TrimSpace(filter.NameMask) != "" {
|
||||
q.Set("name", filter.NameMask)
|
||||
}
|
||||
if strings.TrimSpace(filter.ExtMask) != "" {
|
||||
q.Set("ext", filter.ExtMask)
|
||||
}
|
||||
view := adminconsole.MessagesView{
|
||||
Pager: adminconsole.NewPager(page, adminPageSize, total),
|
||||
NameMask: filter.NameMask,
|
||||
ExtMask: filter.ExtMask,
|
||||
FilterQuery: q.Encode(),
|
||||
}
|
||||
if filter.GameID != uuid.Nil {
|
||||
view.GameID = filter.GameID.String()
|
||||
}
|
||||
if filter.SenderID != uuid.Nil {
|
||||
view.UserID = filter.SenderID.String()
|
||||
}
|
||||
for _, m := range items {
|
||||
view.Items = append(view.Items, adminconsole.MessageRow{
|
||||
ID: m.ID.String(), SenderID: m.SenderID.String(), SenderName: m.SenderName,
|
||||
Source: m.Source, IP: m.SenderIP, Body: m.Body,
|
||||
GameID: m.GameID.String(), CreatedAt: fmtTime(m.CreatedAt),
|
||||
})
|
||||
}
|
||||
s.renderConsole(c, "messages", "messages", "Messages", view)
|
||||
}
|
||||
|
||||
// consoleUserDetail renders one account with its stats, identities and games.
|
||||
func (s *Server) consoleUserDetail(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
|
||||
Reference in New Issue
Block a user