craft-bot/dataBase.go
Timofey.Kovalev a57abdd94d
All checks were successful
continuous-integration/drone/push Build is passing
small fixes
2021-06-22 20:25:34 +03:00

247 lines
6.1 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 {
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 {
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 {
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)
}