259 lines
6.3 KiB
Go
259 lines
6.3 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"github.com/pkg/errors"
|
|
"math/rand"
|
|
"time"
|
|
|
|
_ "github.com/lib/pq"
|
|
)
|
|
|
|
func init() {
|
|
rand.Seed(time.Now().UnixNano())
|
|
}
|
|
|
|
type dbLayer struct {
|
|
db *sql.DB
|
|
}
|
|
|
|
func connectToDataBase(host string, port uint32, user string, password string, dbName string) (*dbLayer, error) {
|
|
db, err := sql.Open("postgres",
|
|
fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
|
|
host, port, user, password, dbName,
|
|
),
|
|
)
|
|
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "Failed to connect to database")
|
|
}
|
|
|
|
return &dbLayer{
|
|
db: db,
|
|
}, nil
|
|
}
|
|
|
|
func (db *dbLayer) execInTransaction(ctx context.Context, query string, args ...interface{}) error {
|
|
tx, err := db.db.BeginTx(ctx, &sql.TxOptions{})
|
|
if err != nil {
|
|
return errors.Wrap(err, "Failed to begin transaction")
|
|
}
|
|
|
|
getLogger(ctx).Debugf("[Run sql query]: %s, [%v]", query, args)
|
|
|
|
_, err = tx.ExecContext(ctx, query, args...)
|
|
if err != nil {
|
|
_ = tx.Rollback()
|
|
|
|
return errors.Wrapf(err, "Failed to run db query [%s] [%v]", query, args)
|
|
}
|
|
|
|
err = tx.Commit()
|
|
if err != nil {
|
|
return errors.Wrap(err, "Failed to commit transaction")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type Player struct {
|
|
id string
|
|
name string
|
|
lastLogin time.Time
|
|
lastLogout time.Time
|
|
onlineDuration time.Duration
|
|
deaths int
|
|
level int
|
|
}
|
|
|
|
type scannable interface {
|
|
Scan(dest ...interface{}) error
|
|
}
|
|
|
|
func fetchPlayer(r scannable, p *Player) error {
|
|
var (
|
|
err error
|
|
lastLogin string
|
|
lastLogout *string
|
|
onlineDuration int
|
|
)
|
|
|
|
err = r.Scan(&p.id, &p.name, &lastLogin, &onlineDuration, &p.deaths, &p.level, &lastLogout)
|
|
if err != nil {
|
|
return errors.Wrap(err, "Failed to fetch data base")
|
|
}
|
|
|
|
p.lastLogin, err = time.Parse(time.RFC3339, lastLogin)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "Failed to parse date [%s]", lastLogin)
|
|
}
|
|
|
|
if lastLogout != nil {
|
|
p.lastLogout, err = time.Parse(time.RFC3339, lastLogin)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "Failed to parse date [%s]", lastLogout)
|
|
}
|
|
}
|
|
|
|
p.onlineDuration = time.Second * time.Duration(onlineDuration)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (db *dbLayer) getPlayers(ctx context.Context) ([]Player, error) {
|
|
var players []Player
|
|
|
|
query := "SELECT id, name, last_login, online_duration, deaths, level, last_logout FROM players"
|
|
|
|
rows, err := db.db.QueryContext(ctx, query)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "Failed to run db query [%s]", query)
|
|
}
|
|
|
|
defer rows.Close()
|
|
|
|
for rows.Next() {
|
|
p := Player{}
|
|
|
|
err = fetchPlayer(rows, &p)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
players = append(players, p)
|
|
}
|
|
|
|
return players, nil
|
|
}
|
|
|
|
func (db *dbLayer) getPlayerByID(ctx context.Context, id string) (*Player, error) {
|
|
query := "SELECT id, name, last_login, online_duration, deaths, level, last_logout FROM players WHERE id = $1"
|
|
|
|
row := db.db.QueryRowContext(ctx, query, id)
|
|
if err := row.Err(); err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return nil, nil
|
|
}
|
|
|
|
return nil, errors.Wrapf(err, "Failed to run db query [%s]", query)
|
|
}
|
|
|
|
var p Player
|
|
|
|
err := fetchPlayer(row, &p)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &p, nil
|
|
}
|
|
|
|
func (db *dbLayer) getPlayerByName(ctx context.Context, name string) (*Player, error) {
|
|
query := "SELECT id, name, last_login, online_duration, deaths, level, last_logout FROM players WHERE name = $1 LIMIT 1"
|
|
|
|
row := db.db.QueryRowContext(ctx, query, name)
|
|
if err := row.Err(); err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return nil, nil
|
|
}
|
|
|
|
return nil, errors.Wrapf(err, "Failed to run db query [%s]", query)
|
|
}
|
|
|
|
var p Player
|
|
|
|
err := fetchPlayer(row, &p)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &p, nil
|
|
}
|
|
|
|
func (db *dbLayer) getKillsByPlayerID(ctx context.Context, playerID string) (int, error) {
|
|
query := "SELECT SUM(count) FROM killings WHERE player_id = $1 GROUP BY player_id"
|
|
|
|
row := db.db.QueryRowContext(ctx, query, playerID)
|
|
if err := row.Err(); err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return 0, nil
|
|
}
|
|
|
|
return 0, errors.Wrapf(err, "Failed to run db query [%s]", query)
|
|
}
|
|
|
|
var killsNum int
|
|
|
|
err := row.Scan(&killsNum)
|
|
if err != nil {
|
|
return 0, errors.Wrap(err, "Failed to fetch data base")
|
|
}
|
|
|
|
return killsNum, nil
|
|
}
|
|
|
|
func (db *dbLayer) createPlayer(ctx context.Context, name string, lastLogin time.Time) (*Player, error) {
|
|
id := uuid()
|
|
|
|
err := db.execInTransaction(ctx, "INSERT INTO players (id, name, last_login) VALUES ($1, $2, $3)", id, name, lastLogin.Format(time.RFC3339))
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &Player{
|
|
id: id,
|
|
name: name,
|
|
lastLogin: lastLogin,
|
|
}, nil
|
|
}
|
|
|
|
func (db *dbLayer) increasePlayerOnlineDuration(ctx context.Context, playerID string, d time.Duration) error {
|
|
onlineDurationSecond := int(d / time.Second)
|
|
if onlineDurationSecond == 0 {
|
|
return nil
|
|
}
|
|
|
|
return db.execInTransaction(ctx, "UPDATE players SET online_duration = online_duration + $2 WHERE id = $1", playerID, onlineDurationSecond)
|
|
}
|
|
|
|
func (db *dbLayer) updatePlayerLastLogin(ctx context.Context, playerID string, lastOnline time.Time) error {
|
|
return db.execInTransaction(ctx, "UPDATE players SET last_login = $2 WHERE id = $1", playerID, lastOnline.Format(time.RFC3339))
|
|
}
|
|
|
|
func (db *dbLayer) updatePlayerLastLogout(ctx context.Context, playerID string, lastOnline time.Time) error {
|
|
return db.execInTransaction(ctx, "UPDATE players SET last_logout = $2 WHERE id = $1", playerID, lastOnline.Format(time.RFC3339))
|
|
}
|
|
|
|
func (db *dbLayer) increasePlayerDeath(ctx context.Context, playerID string, deaths int) error {
|
|
return db.execInTransaction(ctx, "UPDATE players SET deaths = deaths + $2 WHERE id = $1", playerID, deaths)
|
|
}
|
|
|
|
func (db *dbLayer) increasePlayerEntryKills(ctx context.Context, playerID string, entity string, count int) error {
|
|
return db.execInTransaction(ctx,
|
|
"INSERT INTO killings (player_id, entity, count) VALUES ($1, $2, $3) ON CONFLICT (player_id, entity) DO UPDATE SET count = killings.count + excluded.count",
|
|
playerID, entity, count,
|
|
)
|
|
}
|
|
|
|
func (db *dbLayer) updatePlayerLevel(ctx context.Context, playerID string, level int) error {
|
|
return db.execInTransaction(ctx, "UPDATE players SET level = $2 WHERE id = $1", playerID, level)
|
|
}
|
|
|
|
func uuid() string {
|
|
token := make([]byte, 18)
|
|
rand.Read(token)
|
|
result := make([]byte, 36)
|
|
hex.Encode(result, token)
|
|
|
|
result[8] = 45
|
|
result[13] = 45
|
|
result[18] = 45
|
|
result[23] = 45
|
|
|
|
return string(result)
|
|
}
|