222 lines
6.2 KiB
Go
222 lines
6.2 KiB
Go
package db
|
|
|
|
import (
|
|
"context"
|
|
"sort"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/uptrace/bun"
|
|
)
|
|
|
|
type Team struct {
|
|
bun.BaseModel `bun:"table:teams,alias:t"`
|
|
ID int `bun:"id,pk,autoincrement" json:"id"`
|
|
Name string `bun:"name,unique,notnull" json:"name"`
|
|
ShortName string `bun:"short_name,notnull,unique:short_names" json:"short_name"`
|
|
AltShortName string `bun:"alt_short_name,notnull,unique:short_names" json:"alt_short_name"`
|
|
Color string `bun:"color" json:"color,omitempty"`
|
|
|
|
Seasons []Season `bun:"m2m:team_participations,join:Team=Season" json:"-"`
|
|
Leagues []League `bun:"m2m:team_participations,join:Team=League" json:"-"`
|
|
Players []Player `bun:"m2m:team_rosters,join:Team=Player" json:"-"`
|
|
}
|
|
|
|
func NewTeam(ctx context.Context, tx bun.Tx, name, shortName, altShortName, color string, audit *AuditMeta) (*Team, error) {
|
|
team := &Team{
|
|
Name: name,
|
|
ShortName: shortName,
|
|
AltShortName: altShortName,
|
|
Color: color,
|
|
}
|
|
err := Insert(tx, team).
|
|
WithAudit(audit, nil).Exec(ctx)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "db.Insert")
|
|
}
|
|
return team, nil
|
|
}
|
|
|
|
func ListTeams(ctx context.Context, tx bun.Tx, pageOpts *PageOpts) (*List[Team], error) {
|
|
defaults := &PageOpts{
|
|
1,
|
|
10,
|
|
bun.OrderAsc,
|
|
"name",
|
|
}
|
|
return GetList[Team](tx).GetPaged(ctx, pageOpts, defaults)
|
|
}
|
|
|
|
func GetTeam(ctx context.Context, tx bun.Tx, id int) (*Team, error) {
|
|
if id == 0 {
|
|
return nil, errors.New("id not provided")
|
|
}
|
|
return GetByID[Team](tx, id).Relation("Seasons").Relation("Leagues").Get(ctx)
|
|
}
|
|
|
|
func TeamShortNamesUnique(ctx context.Context, tx bun.Tx, shortName, altShortName string) (bool, error) {
|
|
// Check if this combination of short_name and alt_short_name exists
|
|
count, err := tx.NewSelect().
|
|
Model((*Team)(nil)).
|
|
Where("short_name = ? AND alt_short_name = ?", shortName, altShortName).
|
|
Count(ctx)
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "tx.Select")
|
|
}
|
|
return count == 0, nil
|
|
}
|
|
|
|
func (t *Team) InSeason(seasonID int) bool {
|
|
for _, season := range t.Seasons {
|
|
if season.ID == seasonID {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// TeamSeasonInfo holds information about a team's participation in a specific season+league.
|
|
type TeamSeasonInfo struct {
|
|
Season *Season
|
|
League *League
|
|
Record *TeamRecord
|
|
TotalTeams int
|
|
Position int
|
|
}
|
|
|
|
// GetTeamSeasonParticipation returns all season+league combos the team participated in,
|
|
// with computed records, positions, and total team counts.
|
|
func GetTeamSeasonParticipation(
|
|
ctx context.Context,
|
|
tx bun.Tx,
|
|
teamID int,
|
|
) ([]*TeamSeasonInfo, error) {
|
|
if teamID == 0 {
|
|
return nil, errors.New("teamID not provided")
|
|
}
|
|
|
|
// Get all participations for this team
|
|
var participations []*TeamParticipation
|
|
err := tx.NewSelect().
|
|
Model(&participations).
|
|
Where("team_id = ?", teamID).
|
|
Relation("Season", func(q *bun.SelectQuery) *bun.SelectQuery {
|
|
return q.Relation("Leagues")
|
|
}).
|
|
Relation("League").
|
|
Scan(ctx)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "tx.NewSelect participations")
|
|
}
|
|
|
|
var results []*TeamSeasonInfo
|
|
|
|
for _, p := range participations {
|
|
// Get all teams in this season+league for position calculation
|
|
var teams []*Team
|
|
err := tx.NewSelect().
|
|
Model(&teams).
|
|
Join("INNER JOIN team_participations AS tp ON tp.team_id = t.id").
|
|
Where("tp.season_id = ? AND tp.league_id = ?", p.SeasonID, p.LeagueID).
|
|
Scan(ctx)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "tx.NewSelect teams")
|
|
}
|
|
|
|
// Get all fixtures for this season+league
|
|
fixtures, err := GetAllocatedFixtures(ctx, tx, p.SeasonID, p.LeagueID)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "GetAllocatedFixtures")
|
|
}
|
|
|
|
fixtureIDs := make([]int, len(fixtures))
|
|
for i, f := range fixtures {
|
|
fixtureIDs[i] = f.ID
|
|
}
|
|
|
|
resultMap, err := GetFinalizedResultsForFixtures(ctx, tx, fixtureIDs)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "GetFinalizedResultsForFixtures")
|
|
}
|
|
|
|
// Compute leaderboard to get position
|
|
leaderboard := ComputeLeaderboard(teams, fixtures, resultMap)
|
|
|
|
var position int
|
|
var record *TeamRecord
|
|
for _, entry := range leaderboard {
|
|
if entry.Team.ID == teamID {
|
|
position = entry.Position
|
|
record = entry.Record
|
|
break
|
|
}
|
|
}
|
|
if record == nil {
|
|
record = &TeamRecord{}
|
|
}
|
|
|
|
results = append(results, &TeamSeasonInfo{
|
|
Season: p.Season,
|
|
League: p.League,
|
|
Record: record,
|
|
TotalTeams: len(teams),
|
|
Position: position,
|
|
})
|
|
}
|
|
|
|
// Sort by season start date descending (newest first)
|
|
sort.Slice(results, func(i, j int) bool {
|
|
return results[i].Season.StartDate.After(results[j].Season.StartDate)
|
|
})
|
|
|
|
return results, nil
|
|
}
|
|
|
|
// TeamAllTimePlayerStats holds aggregated all-time stats for a player on a team.
|
|
type TeamAllTimePlayerStats struct {
|
|
PlayerID int `bun:"player_id"`
|
|
PlayerName string `bun:"player_name"`
|
|
SeasonsPlayed int `bun:"seasons_played"`
|
|
PeriodsPlayed int `bun:"total_periods_played"`
|
|
Goals int `bun:"total_goals"`
|
|
Assists int `bun:"total_assists"`
|
|
Saves int `bun:"total_saves"`
|
|
}
|
|
|
|
// GetTeamAllTimePlayerStats returns aggregated all-time stats for all players
|
|
// who have ever played for a given team across all seasons.
|
|
func GetTeamAllTimePlayerStats(
|
|
ctx context.Context,
|
|
tx bun.Tx,
|
|
teamID int,
|
|
) ([]*TeamAllTimePlayerStats, error) {
|
|
if teamID == 0 {
|
|
return nil, errors.New("teamID not provided")
|
|
}
|
|
|
|
var stats []*TeamAllTimePlayerStats
|
|
err := tx.NewRaw(`
|
|
SELECT
|
|
frps.player_id AS player_id,
|
|
COALESCE(p.name, frps.player_username) AS player_name,
|
|
COUNT(DISTINCT s.id) AS seasons_played,
|
|
COALESCE(SUM(frps.periods_played), 0) AS total_periods_played,
|
|
COALESCE(SUM(frps.goals), 0) AS total_goals,
|
|
COALESCE(SUM(frps.assists), 0) AS total_assists,
|
|
COALESCE(SUM(frps.saves), 0) AS total_saves
|
|
FROM fixture_result_player_stats frps
|
|
JOIN fixture_results fr ON fr.id = frps.fixture_result_id
|
|
JOIN fixtures f ON f.id = fr.fixture_id
|
|
JOIN seasons s ON s.id = f.season_id
|
|
LEFT JOIN players p ON p.id = frps.player_id
|
|
WHERE fr.finalized = true
|
|
AND frps.team_id = ?
|
|
AND frps.period_num = 3
|
|
AND frps.player_id IS NOT NULL
|
|
GROUP BY frps.player_id, COALESCE(p.name, frps.player_username)
|
|
`, teamID).Scan(ctx, &stats)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "tx.NewRaw")
|
|
}
|
|
return stats, nil
|
|
}
|