343 lines
9.0 KiB
Go
343 lines
9.0 KiB
Go
package db
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/uptrace/bun"
|
|
)
|
|
|
|
// SeasonLeagueFreeAgent tracks players registered as free agents in a season_league.
|
|
type SeasonLeagueFreeAgent struct {
|
|
bun.BaseModel `bun:"table:season_league_free_agents,alias:slfa"`
|
|
|
|
SeasonID int `bun:",pk,notnull"`
|
|
LeagueID int `bun:",pk,notnull"`
|
|
PlayerID int `bun:",pk,notnull"`
|
|
RegisteredAt int64 `bun:",notnull"`
|
|
RegisteredByUserID int `bun:",notnull"`
|
|
|
|
Season *Season `bun:"rel:belongs-to,join:season_id=id"`
|
|
League *League `bun:"rel:belongs-to,join:league_id=id"`
|
|
Player *Player `bun:"rel:belongs-to,join:player_id=id"`
|
|
RegisteredBy *User `bun:"rel:belongs-to,join:registered_by_user_id=id"`
|
|
}
|
|
|
|
// FixtureFreeAgent tracks which free agents are nominated for specific fixtures.
|
|
type FixtureFreeAgent struct {
|
|
bun.BaseModel `bun:"table:fixture_free_agents,alias:ffa"`
|
|
|
|
FixtureID int `bun:",pk,notnull"`
|
|
PlayerID int `bun:",pk,notnull"`
|
|
TeamID int `bun:",notnull"`
|
|
NominatedByUserID int `bun:",notnull"`
|
|
NominatedAt int64 `bun:",notnull"`
|
|
|
|
Fixture *Fixture `bun:"rel:belongs-to,join:fixture_id=id"`
|
|
Player *Player `bun:"rel:belongs-to,join:player_id=id"`
|
|
Team *Team `bun:"rel:belongs-to,join:team_id=id"`
|
|
NominatedBy *User `bun:"rel:belongs-to,join:nominated_by_user_id=id"`
|
|
}
|
|
|
|
// RegisterFreeAgent registers a player as a free agent in a season_league.
|
|
func RegisterFreeAgent(
|
|
ctx context.Context,
|
|
tx bun.Tx,
|
|
seasonID, leagueID, playerID int,
|
|
audit *AuditMeta,
|
|
) error {
|
|
user := CurrentUser(ctx)
|
|
if user == nil {
|
|
return errors.New("user cannot be nil")
|
|
}
|
|
|
|
entry := &SeasonLeagueFreeAgent{
|
|
SeasonID: seasonID,
|
|
LeagueID: leagueID,
|
|
PlayerID: playerID,
|
|
RegisteredAt: time.Now().Unix(),
|
|
RegisteredByUserID: user.ID,
|
|
}
|
|
|
|
info := &AuditInfo{
|
|
Action: "free_agents.add",
|
|
ResourceType: "season_league_free_agent",
|
|
ResourceID: fmt.Sprintf("%d-%d-%d", seasonID, leagueID, playerID),
|
|
Details: map[string]any{
|
|
"season_id": seasonID,
|
|
"league_id": leagueID,
|
|
"player_id": playerID,
|
|
},
|
|
}
|
|
|
|
err := Insert(tx, entry).WithAudit(audit, info).Exec(ctx)
|
|
if err != nil {
|
|
return errors.Wrap(err, "Insert")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// UnregisterFreeAgent removes a player's free agent registration and all their nominations.
|
|
func UnregisterFreeAgent(
|
|
ctx context.Context,
|
|
tx bun.Tx,
|
|
seasonID, leagueID, playerID int,
|
|
audit *AuditMeta,
|
|
) error {
|
|
// First remove all nominations for this player
|
|
err := RemoveAllFreeAgentNominationsForPlayer(ctx, tx, playerID)
|
|
if err != nil {
|
|
return errors.Wrap(err, "RemoveAllFreeAgentNominationsForPlayer")
|
|
}
|
|
|
|
// Then remove the registration
|
|
_, err = tx.NewDelete().
|
|
Model((*SeasonLeagueFreeAgent)(nil)).
|
|
Where("season_id = ?", seasonID).
|
|
Where("league_id = ?", leagueID).
|
|
Where("player_id = ?", playerID).
|
|
Exec(ctx)
|
|
if err != nil {
|
|
return errors.Wrap(err, "tx.NewDelete")
|
|
}
|
|
|
|
info := &AuditInfo{
|
|
Action: "free_agents.remove",
|
|
ResourceType: "season_league_free_agent",
|
|
ResourceID: fmt.Sprintf("%d-%d-%d", seasonID, leagueID, playerID),
|
|
Details: map[string]any{
|
|
"season_id": seasonID,
|
|
"league_id": leagueID,
|
|
"player_id": playerID,
|
|
},
|
|
}
|
|
err = LogSuccess(ctx, tx, audit, info)
|
|
if err != nil {
|
|
return errors.Wrap(err, "LogSuccess")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetFreeAgentsForSeasonLeague returns all players registered as free agents in a season_league.
|
|
func GetFreeAgentsForSeasonLeague(
|
|
ctx context.Context,
|
|
tx bun.Tx,
|
|
seasonID, leagueID int,
|
|
) ([]*SeasonLeagueFreeAgent, error) {
|
|
entries := []*SeasonLeagueFreeAgent{}
|
|
err := tx.NewSelect().
|
|
Model(&entries).
|
|
Where("slfa.season_id = ?", seasonID).
|
|
Where("slfa.league_id = ?", leagueID).
|
|
Relation("Player", func(q *bun.SelectQuery) *bun.SelectQuery {
|
|
return q.Relation("User")
|
|
}).
|
|
Relation("RegisteredBy").
|
|
Order("slfa.registered_at ASC").
|
|
Scan(ctx)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "tx.NewSelect")
|
|
}
|
|
return entries, nil
|
|
}
|
|
|
|
// IsFreeAgentRegistered checks if a player is registered as a free agent in a season_league.
|
|
func IsFreeAgentRegistered(
|
|
ctx context.Context,
|
|
tx bun.Tx,
|
|
seasonID, leagueID, playerID int,
|
|
) (bool, error) {
|
|
count, err := tx.NewSelect().
|
|
Model((*SeasonLeagueFreeAgent)(nil)).
|
|
Where("season_id = ?", seasonID).
|
|
Where("league_id = ?", leagueID).
|
|
Where("player_id = ?", playerID).
|
|
Count(ctx)
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "tx.NewSelect")
|
|
}
|
|
return count > 0, nil
|
|
}
|
|
|
|
// NominateFreeAgent nominates a free agent for a specific fixture on behalf of a team.
|
|
func NominateFreeAgent(
|
|
ctx context.Context,
|
|
tx bun.Tx,
|
|
fixtureID, playerID, teamID int,
|
|
audit *AuditMeta,
|
|
) error {
|
|
user := CurrentUser(ctx)
|
|
if user == nil {
|
|
return errors.New("user cannot be nil")
|
|
}
|
|
|
|
// Check if already nominated by another team
|
|
existing := new(FixtureFreeAgent)
|
|
err := tx.NewSelect().
|
|
Model(existing).
|
|
Where("ffa.fixture_id = ?", fixtureID).
|
|
Where("ffa.player_id = ?", playerID).
|
|
Scan(ctx)
|
|
if err == nil {
|
|
// Found existing nomination
|
|
if existing.TeamID != teamID {
|
|
return BadRequest("Player already nominated for this fixture by another team")
|
|
}
|
|
return BadRequest("Player already nominated for this fixture")
|
|
}
|
|
if err.Error() != "sql: no rows in result set" {
|
|
return errors.Wrap(err, "tx.NewSelect")
|
|
}
|
|
|
|
// Check max 2 free agents per team per fixture
|
|
count, err := tx.NewSelect().
|
|
Model((*FixtureFreeAgent)(nil)).
|
|
Where("fixture_id = ?", fixtureID).
|
|
Where("team_id = ?", teamID).
|
|
Count(ctx)
|
|
if err != nil {
|
|
return errors.Wrap(err, "tx.NewSelect count")
|
|
}
|
|
if count >= 2 {
|
|
return BadRequest("Maximum of 2 free agents per team per fixture")
|
|
}
|
|
|
|
entry := &FixtureFreeAgent{
|
|
FixtureID: fixtureID,
|
|
PlayerID: playerID,
|
|
TeamID: teamID,
|
|
NominatedByUserID: user.ID,
|
|
NominatedAt: time.Now().Unix(),
|
|
}
|
|
|
|
info := &AuditInfo{
|
|
Action: "free_agents.nominate",
|
|
ResourceType: "fixture_free_agent",
|
|
ResourceID: fmt.Sprintf("%d-%d", fixtureID, playerID),
|
|
Details: map[string]any{
|
|
"fixture_id": fixtureID,
|
|
"player_id": playerID,
|
|
"team_id": teamID,
|
|
},
|
|
}
|
|
|
|
err = Insert(tx, entry).WithAudit(audit, info).Exec(ctx)
|
|
if err != nil {
|
|
return errors.Wrap(err, "Insert")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetNominatedFreeAgents returns all free agents nominated for a fixture.
|
|
func GetNominatedFreeAgents(
|
|
ctx context.Context,
|
|
tx bun.Tx,
|
|
fixtureID int,
|
|
) ([]*FixtureFreeAgent, error) {
|
|
entries := []*FixtureFreeAgent{}
|
|
err := tx.NewSelect().
|
|
Model(&entries).
|
|
Where("ffa.fixture_id = ?", fixtureID).
|
|
Relation("Player", func(q *bun.SelectQuery) *bun.SelectQuery {
|
|
return q.Relation("User")
|
|
}).
|
|
Relation("Team").
|
|
Relation("NominatedBy").
|
|
Order("ffa.nominated_at ASC").
|
|
Scan(ctx)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "tx.NewSelect")
|
|
}
|
|
return entries, nil
|
|
}
|
|
|
|
// GetNominatedFreeAgentsByTeam returns free agents nominated by a specific team for a fixture.
|
|
func GetNominatedFreeAgentsByTeam(
|
|
ctx context.Context,
|
|
tx bun.Tx,
|
|
fixtureID, teamID int,
|
|
) ([]*FixtureFreeAgent, error) {
|
|
entries := []*FixtureFreeAgent{}
|
|
err := tx.NewSelect().
|
|
Model(&entries).
|
|
Where("ffa.fixture_id = ?", fixtureID).
|
|
Where("ffa.team_id = ?", teamID).
|
|
Relation("Player", func(q *bun.SelectQuery) *bun.SelectQuery {
|
|
return q.Relation("User")
|
|
}).
|
|
Order("ffa.nominated_at ASC").
|
|
Scan(ctx)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "tx.NewSelect")
|
|
}
|
|
return entries, nil
|
|
}
|
|
|
|
// RemoveAllFreeAgentNominationsForPlayer deletes all nominations for a player.
|
|
// Used for cascade deletion on team join and unregister.
|
|
func RemoveAllFreeAgentNominationsForPlayer(
|
|
ctx context.Context,
|
|
tx bun.Tx,
|
|
playerID int,
|
|
) error {
|
|
_, err := tx.NewDelete().
|
|
Model((*FixtureFreeAgent)(nil)).
|
|
Where("player_id = ?", playerID).
|
|
Exec(ctx)
|
|
if err != nil {
|
|
return errors.Wrap(err, "tx.NewDelete")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// RemoveFreeAgentNomination removes a specific nomination.
|
|
func RemoveFreeAgentNomination(
|
|
ctx context.Context,
|
|
tx bun.Tx,
|
|
fixtureID, playerID int,
|
|
audit *AuditMeta,
|
|
) error {
|
|
_, err := tx.NewDelete().
|
|
Model((*FixtureFreeAgent)(nil)).
|
|
Where("fixture_id = ?", fixtureID).
|
|
Where("player_id = ?", playerID).
|
|
Exec(ctx)
|
|
if err != nil {
|
|
return errors.Wrap(err, "tx.NewDelete")
|
|
}
|
|
|
|
info := &AuditInfo{
|
|
Action: "free_agents.remove_nomination",
|
|
ResourceType: "fixture_free_agent",
|
|
ResourceID: fmt.Sprintf("%d-%d", fixtureID, playerID),
|
|
Details: map[string]any{
|
|
"fixture_id": fixtureID,
|
|
"player_id": playerID,
|
|
},
|
|
}
|
|
err = LogSuccess(ctx, tx, audit, info)
|
|
if err != nil {
|
|
return errors.Wrap(err, "LogSuccess")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// RemoveFreeAgentRegistrationForPlayer removes all free agent registrations for a player.
|
|
// Used on team join.
|
|
func RemoveFreeAgentRegistrationForPlayer(
|
|
ctx context.Context,
|
|
tx bun.Tx,
|
|
playerID int,
|
|
) error {
|
|
_, err := tx.NewDelete().
|
|
Model((*SeasonLeagueFreeAgent)(nil)).
|
|
Where("player_id = ?", playerID).
|
|
Exec(ctx)
|
|
if err != nil {
|
|
return errors.Wrap(err, "tx.NewDelete")
|
|
}
|
|
return nil
|
|
}
|