added leagues

This commit is contained in:
2026-02-10 23:32:48 +11:00
parent dd382faa08
commit c05ecb66fe
28 changed files with 1544 additions and 89 deletions

View File

@@ -0,0 +1,34 @@
package handlers
import (
"context"
"net/http"
"git.haelnorr.com/h/golib/hws"
"github.com/pkg/errors"
"github.com/uptrace/bun"
"git.haelnorr.com/h/oslstats/internal/db"
"git.haelnorr.com/h/oslstats/internal/view/leaguesview"
)
func LeaguesList(
s *hws.Server,
conn *bun.DB,
) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var leagues []*db.League
if ok := db.WithReadTx(s, w, r, conn, func(ctx context.Context, tx bun.Tx) (bool, error) {
var err error
leagues, err = db.GetLeagues(ctx, tx)
if err != nil {
return false, errors.Wrap(err, "db.GetLeagues")
}
return true, nil
}); !ok {
return
}
renderSafely(leaguesview.ListPage(leagues), s, r, w)
})
}

View File

@@ -0,0 +1,96 @@
package handlers
import (
"context"
"fmt"
"net/http"
"git.haelnorr.com/h/golib/hws"
"github.com/pkg/errors"
"github.com/uptrace/bun"
"git.haelnorr.com/h/oslstats/internal/auditlog"
"git.haelnorr.com/h/oslstats/internal/db"
"git.haelnorr.com/h/oslstats/internal/notify"
"git.haelnorr.com/h/oslstats/internal/validation"
leaguesview "git.haelnorr.com/h/oslstats/internal/view/leaguesview"
)
func NewLeague(
s *hws.Server,
conn *bun.DB,
) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" {
renderSafely(leaguesview.NewPage(), s, r, w)
return
}
})
}
func NewLeagueSubmit(
s *hws.Server,
conn *bun.DB,
audit *auditlog.Logger,
) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
getter, ok := validation.ParseFormOrNotify(s, w, r)
if !ok {
return
}
name := getter.String("name").
TrimSpace().Required().
MaxLength(50).MinLength(3).Value
shortname := getter.String("short_name").
TrimSpace().Required().
MaxLength(10).MinLength(2).Value
description := getter.String("description").
TrimSpace().MaxLength(500).Value
if !getter.ValidateAndNotify(s, w, r) {
return
}
nameUnique := false
shortNameUnique := false
var league *db.League
if ok := db.WithNotifyTx(s, w, r, conn, func(ctx context.Context, tx bun.Tx) (bool, error) {
var err error
nameUnique, err = db.IsUnique(ctx, tx, (*db.League)(nil), "name", name)
if err != nil {
return false, errors.Wrap(err, "db.IsLeagueNameUnique")
}
shortNameUnique, err = db.IsUnique(ctx, tx, (*db.League)(nil), "short_name", shortname)
if err != nil {
return false, errors.Wrap(err, "db.IsLeagueShortNameUnique")
}
if !nameUnique || !shortNameUnique {
return true, nil
}
league = &db.League{
Name: name,
ShortName: shortname,
Description: description,
}
err = db.Insert(tx, league).WithAudit(r, audit.Callback()).Exec(ctx)
if err != nil {
return false, errors.Wrap(err, "db.Insert")
}
return true, nil
}); !ok {
return
}
if !nameUnique {
notify.Warn(s, w, r, "Duplicate Name", "This league name is already taken.", nil)
return
}
if !shortNameUnique {
notify.Warn(s, w, r, "Duplicate Short Name", "This short name is already taken.", nil)
return
}
w.Header().Set("HX-Redirect", fmt.Sprintf("/leagues/%s", league.ShortName))
w.WriteHeader(http.StatusOK)
notify.SuccessWithDelay(s, w, r, "League Created", fmt.Sprintf("Successfully created league: %s", name), nil)
})
}

View File

@@ -93,6 +93,10 @@ func notifyLoop(ctx context.Context, c *hws.Client, ws *websocket.Conn) error {
// Parse error code and stacktrace from Details field
code, stacktrace := parseErrorDetails(nt.Details)
err = popup.ErrorModalWS(code, stacktrace, nt, count).Render(ctx, w)
case notify.LevelInfo:
err = popup.Toast(nt, count, 6000).Render(ctx, w)
case notify.LevelSuccess:
err = popup.Toast(nt, count, 3000).Render(ctx, w)
default:
err = popup.Toast(nt, count, 6000).Render(ctx, w)
}

View File

@@ -24,12 +24,17 @@ func SeasonEditPage(
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
seasonStr := r.PathValue("season_short_name")
var season *db.Season
var allLeagues []*db.League
if ok := db.WithReadTx(s, w, r, conn, func(ctx context.Context, tx bun.Tx) (bool, error) {
var err error
season, err = db.GetSeason(ctx, tx, seasonStr)
if err != nil {
return false, errors.Wrap(err, "db.GetSeason")
}
allLeagues, err = db.GetLeagues(ctx, tx)
if err != nil {
return false, errors.Wrap(err, "db.GetLeagues")
}
return true, nil
}); !ok {
return
@@ -38,7 +43,7 @@ func SeasonEditPage(
throw.NotFound(s, w, r, r.URL.Path)
return
}
renderSafely(seasonsview.EditPage(season), s, r, w)
renderSafely(seasonsview.EditPage(season, allLeagues), s, r, w)
})
}
@@ -60,10 +65,12 @@ func SeasonEditSubmit(
MonthNumeric2().Slash().
Year4().Build()
startDate := getter.Time("start_date", format).Required().Value
endDate := getter.Time("end_date", format).Value
finalsStartDate := getter.Time("finals_start_date", format).Value
finalsEndDate := getter.Time("finals_end_date", format).Value
version := getter.String("slap_version").
TrimSpace().Required().AllowedValues([]string{"rebound", "slapshot1"}).Value
start := getter.Time("start_date", format).Required().Value
end := getter.Time("end_date", format).Value
finalsStart := getter.Time("finals_start_date", format).Value
finalsEnd := getter.Time("finals_end_date", format).Value
if !getter.ValidateAndNotify(s, w, r) {
return
@@ -79,9 +86,9 @@ func SeasonEditSubmit(
if season == nil {
return false, errors.New("season does not exist")
}
season.Update(startDate, endDate, finalsStartDate, finalsEndDate)
season.Update(version, start, end, finalsStart, finalsEnd)
err = db.Update(tx, season).WherePK().
Column("start_date", "end_date", "finals_start_date", "finals_end_date").
Column("slap_version", "start_date", "end_date", "finals_start_date", "finals_end_date").
WithAudit(r, audit.Callback()).Exec(ctx)
if err != nil {
return false, errors.Wrap(err, "db.Update")

View File

@@ -0,0 +1,134 @@
package handlers
import (
"context"
"net/http"
"git.haelnorr.com/h/golib/hws"
"github.com/pkg/errors"
"github.com/uptrace/bun"
"git.haelnorr.com/h/oslstats/internal/auditlog"
"git.haelnorr.com/h/oslstats/internal/db"
"git.haelnorr.com/h/oslstats/internal/notify"
"git.haelnorr.com/h/oslstats/internal/view/seasonsview"
)
func SeasonAddLeague(
s *hws.Server,
conn *bun.DB,
audit *auditlog.Logger,
) 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 allLeagues []*db.League
if ok := db.WithNotifyTx(s, w, r, conn, func(ctx context.Context, tx bun.Tx) (bool, error) {
var err error
season, err = db.GetSeason(ctx, tx, seasonStr)
if err != nil {
return false, errors.Wrap(err, "db.GetSeason")
}
if season == nil {
return false, errors.New("season not found")
}
league, err := db.GetLeague(ctx, tx, leagueStr)
if err != nil {
return false, errors.Wrap(err, "db.GetLeague")
}
if league == nil {
return false, errors.New("league not found")
}
// Create the many-to-many relationship
seasonLeague := &db.SeasonLeague{
SeasonID: season.ID,
LeagueID: league.ID,
}
err = db.Insert(tx, seasonLeague).WithAudit(r, audit.Callback()).Exec(ctx)
if err != nil {
return false, errors.Wrap(err, "db.Insert")
}
// Reload season with updated leagues
season, err = db.GetSeason(ctx, tx, seasonStr)
if err != nil {
return false, errors.Wrap(err, "db.GetSeason")
}
allLeagues, err = db.GetLeagues(ctx, tx)
if err != nil {
return false, errors.Wrap(err, "db.GetLeagues")
}
return true, nil
}); !ok {
return
}
notify.Success(s, w, r, "League Added", "League successfully added to season", nil)
renderSafely(seasonsview.LeaguesSection(season, allLeagues), s, r, w)
})
}
func SeasonRemoveLeague(
s *hws.Server,
conn *bun.DB,
audit *auditlog.Logger,
) 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 allLeagues []*db.League
if ok := db.WithNotifyTx(s, w, r, conn, func(ctx context.Context, tx bun.Tx) (bool, error) {
var err error
season, err = db.GetSeason(ctx, tx, seasonStr)
if err != nil {
return false, errors.Wrap(err, "db.GetSeason")
}
if season == nil {
return false, errors.New("season not found")
}
league, err := db.GetLeague(ctx, tx, leagueStr)
if err != nil {
return false, errors.Wrap(err, "db.GetLeague")
}
if league == nil {
return false, errors.New("league not found")
}
// Delete the many-to-many relationship
err = db.DeleteItem[db.SeasonLeague](tx).
Where("season_id = ? AND league_id = ?", season.ID, league.ID).
WithAudit(r, audit.Callback()).
Delete(ctx)
if err != nil {
return false, errors.Wrap(err, "db.DeleteItem")
}
// Reload season with updated leagues
season, err = db.GetSeason(ctx, tx, seasonStr)
if err != nil {
return false, errors.Wrap(err, "db.GetSeason")
}
allLeagues, err = db.GetLeagues(ctx, tx)
if err != nil {
return false, errors.Wrap(err, "db.GetLeagues")
}
return true, nil
}); !ok {
return
}
notify.Success(s, w, r, "League Removed", "League successfully removed from season", nil)
renderSafely(seasonsview.LeaguesSection(season, allLeagues), s, r, w)
})
}

View File

@@ -11,6 +11,7 @@ import (
"github.com/uptrace/bun"
)
// SeasonsPage renders the full page with the seasons list, for use with GET requests
func SeasonsPage(
s *hws.Server,
conn *bun.DB,
@@ -35,6 +36,7 @@ func SeasonsPage(
})
}
// SeasonsList renders just the seasons list, for use with POST requests and HTMX
func SeasonsList(
s *hws.Server,
conn *bun.DB,

View File

@@ -41,14 +41,16 @@ func NewSeasonSubmit(
name := getter.String("name").
TrimSpace().Required().
MaxLength(20).MinLength(5).Value
shortName := getter.String("short_name").
shortname := getter.String("short_name").
TrimSpace().ToUpper().Required().
MaxLength(6).MinLength(2).Value
version := getter.String("slap_version").
TrimSpace().Required().AllowedValues([]string{"rebound", "slapshot1"}).Value
format := timefmt.NewBuilder().
DayNumeric2().Slash().
MonthNumeric2().Slash().
Year4().Build()
startDate := getter.Time("start_date", format).Required().Value
start := getter.Time("start_date", format).Required().Value
if !getter.ValidateAndNotify(s, w, r) {
return
}
@@ -62,14 +64,14 @@ func NewSeasonSubmit(
if err != nil {
return false, errors.Wrap(err, "db.IsSeasonNameUnique")
}
shortNameUnique, err = db.IsUnique(ctx, tx, (*db.Season)(nil), "short_name", shortName)
shortNameUnique, err = db.IsUnique(ctx, tx, (*db.Season)(nil), "short_name", shortname)
if err != nil {
return false, errors.Wrap(err, "db.IsSeasonShortNameUnique")
}
if !nameUnique || !shortNameUnique {
return true, nil
}
season = db.NewSeason(name, shortName, startDate)
season = db.NewSeason(name, version, shortname, start)
err = db.Insert(tx, season).WithAudit(r, audit.Callback()).Exec(ctx)
if err != nil {
return false, errors.Wrap(err, "db.Insert")