357 lines
11 KiB
Go
357 lines
11 KiB
Go
package handlers
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"git.haelnorr.com/h/golib/hws"
|
|
"git.haelnorr.com/h/oslstats/internal/contexts"
|
|
"git.haelnorr.com/h/oslstats/internal/db"
|
|
"git.haelnorr.com/h/oslstats/internal/notify"
|
|
"git.haelnorr.com/h/oslstats/internal/permissions"
|
|
"git.haelnorr.com/h/oslstats/internal/respond"
|
|
"git.haelnorr.com/h/oslstats/internal/throw"
|
|
"git.haelnorr.com/h/oslstats/internal/validation"
|
|
"git.haelnorr.com/h/oslstats/internal/view/seasonsview"
|
|
"github.com/pkg/errors"
|
|
"github.com/uptrace/bun"
|
|
)
|
|
|
|
// FreeAgentsListPage renders the free agents tab of a season league page
|
|
func FreeAgentsListPage(
|
|
s *hws.Server,
|
|
conn *db.DB,
|
|
) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
seasonStr := r.PathValue("season_short_name")
|
|
leagueStr := r.PathValue("league_short_name")
|
|
|
|
var season *db.Season
|
|
var league *db.League
|
|
var freeAgents []*db.SeasonLeagueFreeAgent
|
|
var availablePlayers []*db.Player
|
|
|
|
if ok := conn.WithReadTx(s, w, r, func(ctx context.Context, tx bun.Tx) (bool, error) {
|
|
var err error
|
|
sl, err := db.GetSeasonLeague(ctx, tx, seasonStr, leagueStr)
|
|
if err != nil {
|
|
if db.IsBadRequest(err) {
|
|
throw.NotFound(s, w, r, r.URL.Path)
|
|
return false, nil
|
|
}
|
|
return false, errors.Wrap(err, "db.GetSeasonLeague")
|
|
}
|
|
season = sl.Season
|
|
league = sl.League
|
|
|
|
freeAgents, err = db.GetFreeAgentsForSeasonLeague(ctx, tx, season.ID, league.ID)
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "db.GetFreeAgentsForSeasonLeague")
|
|
}
|
|
|
|
availablePlayers, err = db.GetPlayersNotOnTeam(ctx, tx, season.ID, league.ID)
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "db.GetPlayersNotOnTeam")
|
|
}
|
|
|
|
// Filter out players already registered as free agents
|
|
faMap := make(map[int]bool, len(freeAgents))
|
|
for _, fa := range freeAgents {
|
|
faMap[fa.PlayerID] = true
|
|
}
|
|
filtered := make([]*db.Player, 0, len(availablePlayers))
|
|
for _, p := range availablePlayers {
|
|
if !faMap[p.ID] {
|
|
filtered = append(filtered, p)
|
|
}
|
|
}
|
|
availablePlayers = filtered
|
|
|
|
return true, nil
|
|
}); !ok {
|
|
return
|
|
}
|
|
|
|
if r.Method == "GET" {
|
|
renderSafely(seasonsview.SeasonLeagueFreeAgentsPage(season, league, freeAgents, availablePlayers), s, r, w)
|
|
} else {
|
|
renderSafely(seasonsview.SeasonLeagueFreeAgents(season, league, freeAgents, availablePlayers), s, r, w)
|
|
}
|
|
})
|
|
}
|
|
|
|
// RegisterFreeAgent handles POST to register a player as a free agent
|
|
func RegisterFreeAgent(
|
|
s *hws.Server,
|
|
conn *db.DB,
|
|
) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
seasonStr := r.PathValue("season_short_name")
|
|
leagueStr := r.PathValue("league_short_name")
|
|
|
|
getter, ok := validation.ParseFormOrNotify(s, w, r)
|
|
if !ok {
|
|
respond.BadRequest(w, errors.New("failed to parse form"))
|
|
return
|
|
}
|
|
|
|
playerID := getter.Int("player_id").Required().Value
|
|
if !getter.ValidateAndNotify(s, w, r) {
|
|
respond.BadRequest(w, errors.New("invalid form data"))
|
|
return
|
|
}
|
|
|
|
if ok := conn.WithNotifyTx(s, w, r, func(ctx context.Context, tx bun.Tx) (bool, error) {
|
|
sl, err := db.GetSeasonLeague(ctx, tx, seasonStr, leagueStr)
|
|
if err != nil {
|
|
if db.IsBadRequest(err) {
|
|
throw.NotFound(s, w, r, r.URL.Path)
|
|
return false, nil
|
|
}
|
|
return false, errors.Wrap(err, "db.GetSeasonLeague")
|
|
}
|
|
|
|
// Verify player is not on a team in this season_league
|
|
players, err := db.GetPlayersNotOnTeam(ctx, tx, sl.Season.ID, sl.League.ID)
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "db.GetPlayersNotOnTeam")
|
|
}
|
|
playerFound := false
|
|
for _, p := range players {
|
|
if p.ID == playerID {
|
|
playerFound = true
|
|
break
|
|
}
|
|
}
|
|
if !playerFound {
|
|
notify.Warn(s, w, r, "Cannot Register", "Player is already on a team in this league.", nil)
|
|
return false, nil
|
|
}
|
|
|
|
// Check if already registered
|
|
isRegistered, err := db.IsFreeAgentRegistered(ctx, tx, sl.Season.ID, sl.League.ID, playerID)
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "db.IsFreeAgentRegistered")
|
|
}
|
|
if isRegistered {
|
|
notify.Warn(s, w, r, "Already Registered", "Player is already registered as a free agent.", nil)
|
|
return false, nil
|
|
}
|
|
|
|
err = db.RegisterFreeAgent(ctx, tx, sl.Season.ID, sl.League.ID, playerID, db.NewAuditFromRequest(r))
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "db.RegisterFreeAgent")
|
|
}
|
|
|
|
return true, nil
|
|
}); !ok {
|
|
return
|
|
}
|
|
|
|
notify.Success(s, w, r, "Free Agent Registered", "Player has been registered as a free agent.", nil)
|
|
respond.HXRedirect(w, "/seasons/%s/leagues/%s/free-agents", seasonStr, leagueStr)
|
|
})
|
|
}
|
|
|
|
// UnregisterFreeAgent handles POST to unregister a player as a free agent
|
|
func UnregisterFreeAgent(
|
|
s *hws.Server,
|
|
conn *db.DB,
|
|
) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
seasonStr := r.PathValue("season_short_name")
|
|
leagueStr := r.PathValue("league_short_name")
|
|
|
|
getter, ok := validation.ParseFormOrNotify(s, w, r)
|
|
if !ok {
|
|
respond.BadRequest(w, errors.New("failed to parse form"))
|
|
return
|
|
}
|
|
|
|
playerID := getter.Int("player_id").Required().Value
|
|
if !getter.ValidateAndNotify(s, w, r) {
|
|
respond.BadRequest(w, errors.New("invalid form data"))
|
|
return
|
|
}
|
|
|
|
if ok := conn.WithNotifyTx(s, w, r, func(ctx context.Context, tx bun.Tx) (bool, error) {
|
|
sl, err := db.GetSeasonLeague(ctx, tx, seasonStr, leagueStr)
|
|
if err != nil {
|
|
if db.IsBadRequest(err) {
|
|
throw.NotFound(s, w, r, r.URL.Path)
|
|
return false, nil
|
|
}
|
|
return false, errors.Wrap(err, "db.GetSeasonLeague")
|
|
}
|
|
|
|
err = db.UnregisterFreeAgent(ctx, tx, sl.Season.ID, sl.League.ID, playerID, db.NewAuditFromRequest(r))
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "db.UnregisterFreeAgent")
|
|
}
|
|
|
|
return true, nil
|
|
}); !ok {
|
|
return
|
|
}
|
|
|
|
notify.Success(s, w, r, "Free Agent Removed", "Player has been unregistered as a free agent.", nil)
|
|
respond.HXRedirect(w, "/seasons/%s/leagues/%s/free-agents", seasonStr, leagueStr)
|
|
})
|
|
}
|
|
|
|
// NominateFreeAgentHandler handles POST to nominate a free agent for a fixture
|
|
func NominateFreeAgentHandler(
|
|
s *hws.Server,
|
|
conn *db.DB,
|
|
) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
fixtureID, err := strconv.Atoi(r.PathValue("fixture_id"))
|
|
if err != nil {
|
|
throw.BadRequest(s, w, r, "Invalid fixture ID", err)
|
|
return
|
|
}
|
|
|
|
getter, ok := validation.ParseFormOrNotify(s, w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
playerID := getter.Int("player_id").Required().Value
|
|
teamID := getter.Int("team_id").Required().Value
|
|
if !getter.ValidateAndNotify(s, w, r) {
|
|
return
|
|
}
|
|
|
|
if ok := conn.WithNotifyTx(s, w, r, func(ctx context.Context, tx bun.Tx) (bool, error) {
|
|
// Verify fixture exists and user is a manager
|
|
fixture, err := db.GetFixture(ctx, tx, fixtureID)
|
|
if err != nil {
|
|
if db.IsBadRequest(err) {
|
|
respond.NotFound(w, errors.Wrap(err, "db.GetFixture"))
|
|
return false, nil
|
|
}
|
|
return false, errors.Wrap(err, "db.GetFixture")
|
|
}
|
|
|
|
// Check if user can nominate: either a manager of the nominating team,
|
|
// or has fixtures.manage permission (can nominate for either team)
|
|
user := db.CurrentUser(ctx)
|
|
canManage := contexts.Permissions(ctx).HasPermission(permissions.FixturesManage)
|
|
if !canManage {
|
|
canSchedule, userTeamID, err := fixture.CanSchedule(ctx, tx, user)
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "fixture.CanSchedule")
|
|
}
|
|
if !canSchedule || userTeamID != teamID {
|
|
throw.Forbidden(s, w, r, "You must be a manager of the nominating team", nil)
|
|
return false, nil
|
|
}
|
|
}
|
|
// Verify the team_id is actually one of the fixture's teams
|
|
if teamID != fixture.HomeTeamID && teamID != fixture.AwayTeamID {
|
|
throw.BadRequest(s, w, r, "Invalid team for this fixture", nil)
|
|
return false, nil
|
|
}
|
|
|
|
// Verify player is a registered free agent in this season_league
|
|
isRegistered, err := db.IsFreeAgentRegistered(ctx, tx, fixture.SeasonID, fixture.LeagueID, playerID)
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "db.IsFreeAgentRegistered")
|
|
}
|
|
if !isRegistered {
|
|
notify.Warn(s, w, r, "Not Registered", "Player is not a registered free agent in this league.", nil)
|
|
return false, nil
|
|
}
|
|
|
|
err = db.NominateFreeAgent(ctx, tx, fixtureID, playerID, teamID, db.NewAuditFromRequest(r))
|
|
if err != nil {
|
|
if db.IsBadRequest(err) {
|
|
notify.Warn(s, w, r, "Cannot Nominate", err.Error(), nil)
|
|
return false, nil
|
|
}
|
|
return false, errors.Wrap(err, "db.NominateFreeAgent")
|
|
}
|
|
|
|
return true, nil
|
|
}); !ok {
|
|
return
|
|
}
|
|
|
|
notify.Success(s, w, r, "Free Agent Nominated", "Free agent has been nominated for this fixture.", nil)
|
|
respond.HXRedirect(w, "/fixtures/%d", fixtureID)
|
|
})
|
|
}
|
|
|
|
// RemoveFreeAgentNominationHandler handles POST to remove a free agent nomination
|
|
func RemoveFreeAgentNominationHandler(
|
|
s *hws.Server,
|
|
conn *db.DB,
|
|
) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
fixtureID, err := strconv.Atoi(r.PathValue("fixture_id"))
|
|
if err != nil {
|
|
throw.BadRequest(s, w, r, "Invalid fixture ID", err)
|
|
return
|
|
}
|
|
|
|
playerID, err := strconv.Atoi(r.PathValue("player_id"))
|
|
if err != nil {
|
|
throw.BadRequest(s, w, r, "Invalid player ID", err)
|
|
return
|
|
}
|
|
|
|
if ok := conn.WithNotifyTx(s, w, r, func(ctx context.Context, tx bun.Tx) (bool, error) {
|
|
// Check if user can remove: either has fixtures.manage permission,
|
|
// or is a manager of the team that nominated the free agent
|
|
canManage := contexts.Permissions(ctx).HasPermission(permissions.FixturesManage)
|
|
if !canManage {
|
|
fixture, err := db.GetFixture(ctx, tx, fixtureID)
|
|
if err != nil {
|
|
if db.IsBadRequest(err) {
|
|
respond.NotFound(w, errors.Wrap(err, "db.GetFixture"))
|
|
return false, nil
|
|
}
|
|
return false, errors.Wrap(err, "db.GetFixture")
|
|
}
|
|
user := db.CurrentUser(ctx)
|
|
canSchedule, userTeamID, err := fixture.CanSchedule(ctx, tx, user)
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "fixture.CanSchedule")
|
|
}
|
|
if !canSchedule {
|
|
throw.Forbidden(s, w, r, "You must be a team manager to remove nominations", nil)
|
|
return false, nil
|
|
}
|
|
// Verify the nomination belongs to the user's team
|
|
nominations, err := db.GetNominatedFreeAgentsByTeam(ctx, tx, fixtureID, userTeamID)
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "db.GetNominatedFreeAgentsByTeam")
|
|
}
|
|
found := false
|
|
for _, n := range nominations {
|
|
if n.PlayerID == playerID {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
throw.Forbidden(s, w, r, "You can only remove nominations made by your team", nil)
|
|
return false, nil
|
|
}
|
|
}
|
|
|
|
err := db.RemoveFreeAgentNomination(ctx, tx, fixtureID, playerID, db.NewAuditFromRequest(r))
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "db.RemoveFreeAgentNomination")
|
|
}
|
|
return true, nil
|
|
}); !ok {
|
|
return
|
|
}
|
|
|
|
notify.Success(s, w, r, "Nomination Removed", "Free agent nomination has been removed.", nil)
|
|
respond.HXRedirect(w, "/fixtures/%d", fixtureID)
|
|
})
|
|
}
|