feat(adminalert): operator email on new feedback / word complaints
New adminalert worker polls for feedback + word complaints arriving since the last check and coalesces a burst into one digest email per interval (5 min), inert unless a distinct admin sender (BACKEND_SMTP_ADMIN_FROM) and recipient (BACKEND_ADMIN_EMAIL, comma-separated allowed) are configured. The mailer gains a per-message From override and splits a comma-separated To into separate recipients (go-mail needs them as a list). Feedback/game stores gain CountSince/CountComplaintsSince. Unit tests cover the digest, the skip-when- empty, and the recipient split.
This commit is contained in:
@@ -1040,6 +1040,19 @@ func (s *Store) CountComplaints(ctx context.Context, status string) (int, error)
|
||||
return int(dest.Count), nil
|
||||
}
|
||||
|
||||
// CountComplaintsSince counts word complaints filed strictly after since — the operator
|
||||
// alert worker's "new since the last check" signal.
|
||||
func (s *Store) CountComplaintsSince(ctx context.Context, since time.Time) (int, error) {
|
||||
stmt := postgres.SELECT(postgres.COUNT(table.Complaints.ComplaintID).AS("count")).
|
||||
FROM(table.Complaints).
|
||||
WHERE(table.Complaints.CreatedAt.GT(postgres.TimestampzT(since)))
|
||||
var dest struct{ Count int64 }
|
||||
if err := stmt.QueryContext(ctx, s.db, &dest); err != nil {
|
||||
return 0, fmt.Errorf("game: count complaints since: %w", err)
|
||||
}
|
||||
return int(dest.Count), nil
|
||||
}
|
||||
|
||||
// ActiveGames returns the turn clocks of every in-progress game; the sweeper
|
||||
// filters them against the per-move deadline and the player's away window.
|
||||
func (s *Store) ActiveGames(ctx context.Context) ([]activeGame, error) {
|
||||
|
||||
Reference in New Issue
Block a user