we have fixtures ladies and gentleman
This commit is contained in:
282
internal/db/fixture.go
Normal file
282
internal/db/fixture.go
Normal file
@@ -0,0 +1,282 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
type Fixture struct {
|
||||
bun.BaseModel `bun:"table:fixtures,alias:f"`
|
||||
|
||||
ID int `bun:"id,pk,autoincrement"`
|
||||
SeasonID int `bun:",notnull,unique:round"`
|
||||
LeagueID int `bun:",notnull,unique:round"`
|
||||
HomeTeamID int `bun:",notnull,unique:round"`
|
||||
AwayTeamID int `bun:",notnull,unique:round"`
|
||||
Round int `bun:"round,unique:round"`
|
||||
GameWeek *int `bun:"game_week"`
|
||||
CreatedAt int64 `bun:"created_at,notnull"`
|
||||
UpdatedAt *int64 `bun:"updated_at"`
|
||||
|
||||
Season *Season `bun:"rel:belongs-to,join:season_id=id"`
|
||||
League *League `bun:"rel:belongs-to,join:league_id=id"`
|
||||
HomeTeam *Team `bun:"rel:belongs-to,join:home_team_id=id"`
|
||||
AwayTeam *Team `bun:"rel:belongs-to,join:away_team_id=id"`
|
||||
}
|
||||
|
||||
func NewFixture(ctx context.Context, tx bun.Tx, seasonShortName, leagueShortName string,
|
||||
homeTeamID, awayTeamID, round int, audit *AuditMeta,
|
||||
) (*Fixture, error) {
|
||||
season, league, teams, err := GetSeasonLeague(ctx, tx, seasonShortName, leagueShortName)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "GetSeasonLeague")
|
||||
}
|
||||
homeTeam, err := GetTeam(ctx, tx, homeTeamID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "GetTeam")
|
||||
}
|
||||
awayTeam, err := GetTeam(ctx, tx, awayTeamID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "GetTeam")
|
||||
}
|
||||
if err = checkTeamsAssociated(season, league, teams, []*Team{homeTeam, awayTeam}); err != nil {
|
||||
return nil, errors.Wrap(err, "checkTeamsAssociated")
|
||||
}
|
||||
fixture := newFixture(season, league, homeTeam, awayTeam, round, time.Now())
|
||||
err = Insert(tx, fixture).WithAudit(audit, nil).Exec(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Insert")
|
||||
}
|
||||
return fixture, nil
|
||||
}
|
||||
|
||||
func NewRound(ctx context.Context, tx bun.Tx, seasonShortName, leagueShortName string,
|
||||
round int, audit *AuditMeta,
|
||||
) ([]*Fixture, error) {
|
||||
season, league, teams, err := GetSeasonLeague(ctx, tx, seasonShortName, leagueShortName)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "GetSeasonLeague")
|
||||
}
|
||||
fixtures := generateRound(season, league, round, teams)
|
||||
err = InsertMultiple(tx, fixtures).WithAudit(audit, nil).Exec(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "InsertMultiple")
|
||||
}
|
||||
return fixtures, nil
|
||||
}
|
||||
|
||||
func GetFixtures(ctx context.Context, tx bun.Tx, seasonShortName, leagueShortName string) (*Season, *League, []*Fixture, error) {
|
||||
season, league, _, err := GetSeasonLeague(ctx, tx, seasonShortName, leagueShortName)
|
||||
if err != nil {
|
||||
return nil, nil, nil, errors.Wrap(err, "GetSeasonLeague")
|
||||
}
|
||||
fixtures, err := GetList[Fixture](tx).
|
||||
Where("season_id = ?", season.ID).
|
||||
Where("league_id = ?", league.ID).
|
||||
Order("game_week ASC NULLS FIRST", "round ASC", "id ASC").
|
||||
Relation("HomeTeam").
|
||||
Relation("AwayTeam").
|
||||
GetAll(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, nil, errors.Wrap(err, "GetList")
|
||||
}
|
||||
return season, league, fixtures, nil
|
||||
}
|
||||
|
||||
func GetFixture(ctx context.Context, tx bun.Tx, id int) (*Fixture, error) {
|
||||
return GetByID[Fixture](tx, id).
|
||||
Relation("Season").
|
||||
Relation("League").
|
||||
Relation("HomeTeam").
|
||||
Relation("AwayTeam").
|
||||
Get(ctx)
|
||||
}
|
||||
|
||||
func GetFixturesByGameWeek(ctx context.Context, tx bun.Tx, seasonID, leagueID, gameweek int) ([]*Fixture, error) {
|
||||
fixtures, err := GetList[Fixture](tx).
|
||||
Where("season_id = ?", seasonID).
|
||||
Where("league_id = ?", leagueID).
|
||||
Where("game_week = ?", gameweek).
|
||||
Order("round ASC", "id ASC").
|
||||
Relation("HomeTeam").
|
||||
Relation("AwayTeam").
|
||||
GetAll(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "GetList")
|
||||
}
|
||||
return fixtures, nil
|
||||
}
|
||||
|
||||
func GetUnallocatedFixtures(ctx context.Context, tx bun.Tx, seasonID, leagueID int) ([]*Fixture, error) {
|
||||
fixtures, err := GetList[Fixture](tx).
|
||||
Where("season_id = ?", seasonID).
|
||||
Where("league_id = ?", leagueID).
|
||||
Where("game_week IS NULL").
|
||||
Order("round ASC", "id ASC").
|
||||
Relation("HomeTeam").
|
||||
Relation("AwayTeam").
|
||||
GetAll(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "GetList")
|
||||
}
|
||||
return fixtures, nil
|
||||
}
|
||||
|
||||
func CountUnallocatedFixtures(ctx context.Context, tx bun.Tx, seasonID, leagueID int) (int, error) {
|
||||
count, err := GetList[Fixture](tx).
|
||||
Where("season_id = ?", seasonID).
|
||||
Where("league_id = ?", leagueID).
|
||||
Where("game_week IS NULL").
|
||||
Count(ctx)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "GetList")
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func GetMaxGameWeek(ctx context.Context, tx bun.Tx, seasonID, leagueID int) (int, error) {
|
||||
var maxGameWeek int
|
||||
err := tx.NewSelect().
|
||||
Model((*Fixture)(nil)).
|
||||
Column("game_week").
|
||||
Where("season_id = ?", seasonID).
|
||||
Where("league_id = ?", leagueID).
|
||||
Order("game_week DESC NULLS LAST").
|
||||
Limit(1).Scan(ctx, &maxGameWeek)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "tx.NewSelect")
|
||||
}
|
||||
return maxGameWeek, nil
|
||||
}
|
||||
|
||||
func UpdateFixtureGameWeeks(ctx context.Context, tx bun.Tx, fixtures []*Fixture, audit *AuditMeta) error {
|
||||
details := []any{}
|
||||
for _, fixture := range fixtures {
|
||||
err := UpdateByID(tx, fixture.ID, fixture).
|
||||
Column("game_week").
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "UpdateByID")
|
||||
}
|
||||
details = append(details, map[string]any{"fixture_id": fixture.ID, "game_week": fixture.GameWeek})
|
||||
}
|
||||
info := &AuditInfo{
|
||||
"fixtures.manage",
|
||||
"fixture",
|
||||
"multiple",
|
||||
map[string]any{"updated": details},
|
||||
}
|
||||
err := LogSuccess(ctx, tx, audit, info)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "LogSuccess")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DeleteAllFixtures(ctx context.Context, tx bun.Tx, seasonShortName, leagueShortName string, audit *AuditMeta) error {
|
||||
season, league, _, err := GetSeasonLeague(ctx, tx, seasonShortName, leagueShortName)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "GetSeasonLeague")
|
||||
}
|
||||
err = DeleteItem[Fixture](tx).
|
||||
Where("season_id = ?", season.ID).
|
||||
Where("league_id = ?", league.ID).
|
||||
WithAudit(audit, nil).
|
||||
Delete(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "DeleteItem")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DeleteFixture(ctx context.Context, tx bun.Tx, id int, audit *AuditMeta) error {
|
||||
err := DeleteByID[Fixture](tx, id).
|
||||
WithAudit(audit, nil).
|
||||
Delete(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "DeleteByID")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newFixture(season *Season, league *League, homeTeam, awayTeam *Team, round int, created time.Time) *Fixture {
|
||||
return &Fixture{
|
||||
SeasonID: season.ID,
|
||||
LeagueID: league.ID,
|
||||
HomeTeamID: homeTeam.ID,
|
||||
AwayTeamID: awayTeam.ID,
|
||||
Round: round,
|
||||
CreatedAt: created.Unix(),
|
||||
}
|
||||
}
|
||||
|
||||
func checkTeamsAssociated(season *Season, league *League, teamsIn []*Team, toCheck []*Team) error {
|
||||
badIDs := []string{}
|
||||
master := map[int]bool{}
|
||||
for _, team := range teamsIn {
|
||||
master[team.ID] = true
|
||||
}
|
||||
for _, team := range toCheck {
|
||||
if !master[team.ID] {
|
||||
badIDs = append(badIDs, strconv.Itoa(team.ID))
|
||||
}
|
||||
}
|
||||
ids := strings.Join(badIDs, ",")
|
||||
if len(ids) > 0 {
|
||||
return BadRequestNotAssociated("season_league", "team",
|
||||
"season_id,league_id", "ids",
|
||||
fmt.Sprintf("%v,%v", season.ID, league.ID),
|
||||
ids)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type versus struct {
|
||||
homeTeam *Team
|
||||
awayTeam *Team
|
||||
}
|
||||
|
||||
func generateRound(season *Season, league *League, round int, teams []*Team) []*Fixture {
|
||||
now := time.Now()
|
||||
numTeams := len(teams)
|
||||
numGames := numTeams * (numTeams - 1) / 2
|
||||
fixtures := make([]*Fixture, numGames)
|
||||
for i, matchup := range allTeamsPlay(teams, round) {
|
||||
fixtures[i] = newFixture(season, league, matchup.homeTeam, matchup.awayTeam, round, now)
|
||||
}
|
||||
return fixtures
|
||||
}
|
||||
|
||||
func allTeamsPlay(teams []*Team, round int) []*versus {
|
||||
matchups := []*versus{}
|
||||
if len(teams) < 2 {
|
||||
return matchups
|
||||
}
|
||||
team1 := teams[0]
|
||||
teams = teams[1:]
|
||||
matchups = append(matchups, playOtherTeams(team1, teams, round)...)
|
||||
matchups = append(matchups, allTeamsPlay(teams, round)...)
|
||||
return matchups
|
||||
}
|
||||
|
||||
func playOtherTeams(team *Team, teams []*Team, round int) []*versus {
|
||||
matchups := make([]*versus, len(teams))
|
||||
for i, opponent := range teams {
|
||||
versus := &versus{}
|
||||
if i%2+round%2 == 0 {
|
||||
versus.homeTeam = team
|
||||
versus.awayTeam = opponent
|
||||
} else {
|
||||
versus.homeTeam = opponent
|
||||
versus.awayTeam = team
|
||||
}
|
||||
matchups[i] = versus
|
||||
}
|
||||
return matchups
|
||||
}
|
||||
Reference in New Issue
Block a user