Allow printing match statistics
This commit is contained in:
parent
8cba945823
commit
caf4b3e2d8
3 changed files with 103 additions and 2 deletions
|
@ -3,6 +3,7 @@ package main
|
|||
import (
|
||||
"context"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"code.rocket9labs.com/tslocum/bgammon"
|
||||
"github.com/jackc/pgx/v5"
|
||||
|
@ -82,3 +83,71 @@ func recordGameResult(conn *pgx.Conn, g bgammon.Game) error {
|
|||
_, err = tx.Exec(context.Background(), "INSERT INTO game (started, ended, winner, player1, player2) VALUES ($1, $2, $3, $4, $5)", g.Started.Unix(), g.Ended.Unix(), g.Winner, g.Player1.Name, g.Player2.Name)
|
||||
return err
|
||||
}
|
||||
|
||||
type serverStatsEntry struct {
|
||||
Date string
|
||||
Games int
|
||||
}
|
||||
|
||||
type serverStatsResult struct {
|
||||
History []*serverStatsEntry
|
||||
}
|
||||
|
||||
func serverStats(conn *pgx.Conn, tz *time.Location) (*serverStatsResult, error) {
|
||||
tx, err := begin(conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer tx.Commit(context.Background())
|
||||
|
||||
var earliestGame int64
|
||||
rows, err := tx.Query(context.Background(), "SELECT started FROM game ORDER BY started ASC LIMIT 1")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for rows.Next() {
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
err = rows.Scan(&earliestGame)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := &serverStatsResult{}
|
||||
earliest := midnight(time.Unix(earliestGame, 0).In(tz))
|
||||
rangeStart, rangeEnd := earliest.Unix(), earliest.AddDate(0, 0, 1).Unix()
|
||||
var count int
|
||||
for {
|
||||
rows, err := tx.Query(context.Background(), "SELECT COUNT(*) FROM game WHERE started >= $1 AND started < $2", rangeStart, rangeEnd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for rows.Next() {
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
err = rows.Scan(&count)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result.History = append(result.History, &serverStatsEntry{
|
||||
Date: earliest.Format("2006-01-02"),
|
||||
Games: count,
|
||||
})
|
||||
|
||||
earliest = earliest.AddDate(0, 0, 1)
|
||||
rangeStart, rangeEnd = rangeEnd, earliest.AddDate(0, 0, 1).Unix()
|
||||
if rangeStart >= time.Now().Unix() {
|
||||
break
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func midnight(t time.Time) time.Time {
|
||||
return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ func main() {
|
|||
var (
|
||||
tcpAddress string
|
||||
wsAddress string
|
||||
tz string
|
||||
dataSource string
|
||||
debug int
|
||||
debugCommands bool
|
||||
|
@ -20,6 +21,7 @@ func main() {
|
|||
)
|
||||
flag.StringVar(&tcpAddress, "tcp", "localhost:1337", "TCP listen address")
|
||||
flag.StringVar(&wsAddress, "ws", "localhost:1338", "WebSocket listen address")
|
||||
flag.StringVar(&tz, "tz", "", "Time zone used when calculating statistics")
|
||||
flag.StringVar(&dataSource, "db", "", "Database data source (postgres://username:password@localhost:5432/database_name")
|
||||
flag.IntVar(&debug, "debug", 0, "print debug information and serve pprof on specified port")
|
||||
flag.BoolVar(&debugCommands, "debug-commands", false, "allow players to use restricted commands")
|
||||
|
@ -49,7 +51,7 @@ func main() {
|
|||
allowDebugCommands = debugCommands
|
||||
}
|
||||
|
||||
s := newServer(dataSource)
|
||||
s := newServer(tz, dataSource)
|
||||
if tcpAddress != "" {
|
||||
s.listen("tcp", tcpAddress)
|
||||
}
|
||||
|
|
|
@ -49,10 +49,12 @@ type server struct {
|
|||
gamesCacheTime time.Time
|
||||
gamesCacheLock sync.Mutex
|
||||
|
||||
tz *time.Location
|
||||
|
||||
db *pgx.Conn
|
||||
}
|
||||
|
||||
func newServer(dataSource string) *server {
|
||||
func newServer(tz string, dataSource string) *server {
|
||||
const bufferSize = 10
|
||||
s := &server{
|
||||
newGameIDs: make(chan int),
|
||||
|
@ -61,6 +63,16 @@ func newServer(dataSource string) *server {
|
|||
welcome: []byte("hello Welcome to bgammon.org! Please log in by sending the 'login' command. You may specify a username, otherwise you will be assigned a random username. If you specify a username, you may also specify a password. Have fun!"),
|
||||
}
|
||||
|
||||
if tz != "" {
|
||||
var err error
|
||||
s.tz, err = time.LoadLocation(tz)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to parse timezone %s: %s", tz, err)
|
||||
}
|
||||
} else {
|
||||
s.tz = time.UTC
|
||||
}
|
||||
|
||||
if dataSource != "" {
|
||||
var err error
|
||||
s.db, err = connectDB(dataSource)
|
||||
|
@ -123,6 +135,23 @@ func (s *server) handleListMatches(w http.ResponseWriter, r *http.Request) {
|
|||
w.Write(s.cachedMatches())
|
||||
}
|
||||
|
||||
func (s *server) handlePrintStats(w http.ResponseWriter, r *http.Request) {
|
||||
if s.db == nil {
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
stats, err := serverStats(s.db, s.tz)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to fetch server statistics: %s", err)
|
||||
}
|
||||
buf, err := json.Marshal(stats)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to fetch serialize statistics: %s", err)
|
||||
}
|
||||
w.Write(buf)
|
||||
}
|
||||
|
||||
func (s *server) handleWebSocket(w http.ResponseWriter, r *http.Request) {
|
||||
const bufferSize = 8
|
||||
commands := make(chan []byte, bufferSize)
|
||||
|
@ -151,6 +180,7 @@ func (s *server) listenWebSocket(address string) {
|
|||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/matches", s.handleListMatches)
|
||||
mux.HandleFunc("/stats", s.handlePrintStats)
|
||||
mux.HandleFunc("/", s.handleWebSocket)
|
||||
|
||||
err := http.ListenAndServe(address, mux)
|
||||
|
|
Loading…
Reference in a new issue