scaffolding for new seasons

This commit is contained in:
2026-02-02 19:12:14 +11:00
parent 9366481552
commit 9c70458b76
21 changed files with 520 additions and 41 deletions

2
internal/handlers/doc.go Normal file
View File

@@ -0,0 +1,2 @@
// Package handlers contains all the functions for handling http requests and serving content
package handlers

View File

@@ -6,6 +6,7 @@ import (
"net/http"
"git.haelnorr.com/h/golib/hws"
"github.com/a-h/templ"
"github.com/pkg/errors"
)
@@ -166,3 +167,10 @@ func parseErrorDetails(details string) (int, string) {
return errDetails.Code, errDetails.Stacktrace
}
func renderSafely(page templ.Component, s *hws.Server, r *http.Request, w http.ResponseWriter) {
err := page.Render(r.Context(), w)
if err != nil {
throwInternalServiceError(s, w, r, "Failed to render page", errors.Wrap(err, "page."))
}
}

View File

@@ -8,15 +8,15 @@ import (
"git.haelnorr.com/h/golib/hws"
)
// Handles responses to the / path. Also serves a 404 Page for paths that
// Index handles responses to the / path. Also serves a 404 Page for paths that
// don't have explicit handlers
func Index(server *hws.Server) http.Handler {
func Index(s *hws.Server) http.Handler {
return http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
throwNotFound(server, w, r, r.URL.Path)
throwNotFound(s, w, r, r.URL.Path)
}
page.Index().Render(r.Context(), w)
renderSafely(page.Index(), s, r, w)
},
)
}

View File

@@ -0,0 +1,105 @@
package handlers
import (
"context"
"net/http"
"time"
"git.haelnorr.com/h/golib/hws"
"git.haelnorr.com/h/oslstats/internal/db"
"git.haelnorr.com/h/oslstats/internal/view/page"
"github.com/pkg/errors"
"github.com/uptrace/bun"
)
func NewSeason(
s *hws.Server,
conn *bun.DB,
) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" {
renderSafely(page.NewSeason(), s, r, w)
return
}
})
}
func NewSeasonSubmit(
s *hws.Server,
conn *bun.DB,
) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
})
}
func IsSeasonNameUnique(
s *hws.Server,
conn *bun.DB,
) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err := r.ParseForm()
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
ctx, cancel := context.WithTimeout(r.Context(), 15*time.Second)
defer cancel()
tx, err := conn.BeginTx(ctx, nil)
if err != nil {
err = notifyInternalServiceError(s, r, "Database error", errors.Wrap(err, "conn.BeginTx"))
if err != nil {
throwInternalServiceError(s, w, r, "Error notifying client", err)
}
return
}
name := r.FormValue("name")
unique, err := db.IsSeasonNameUnique(ctx, tx, name)
if err != nil {
err = notifyInternalServiceError(s, r, "Database error", errors.Wrap(err, "db.IsSeasonNameUnique"))
if err != nil {
throwInternalServiceError(s, w, r, "Error notifying client", err)
}
return
}
if !unique {
w.WriteHeader(http.StatusConflict)
return
}
})
}
func IsSeasonShortNameUnique(
s *hws.Server,
conn *bun.DB,
) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err := r.ParseForm()
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
ctx, cancel := context.WithTimeout(r.Context(), 15*time.Second)
defer cancel()
tx, err := conn.BeginTx(ctx, nil)
if err != nil {
err = notifyInternalServiceError(s, r, "Database error", errors.Wrap(err, "conn.BeginTx"))
if err != nil {
throwInternalServiceError(s, w, r, "Error notifying client", err)
}
return
}
shortname := r.FormValue("short_name")
unique, err := db.IsSeasonShortNameUnique(ctx, tx, shortname)
if err != nil {
err = notifyInternalServiceError(s, r, "Database error", errors.Wrap(err, "db.IsSeasonShortNameUnique"))
if err != nil {
throwInternalServiceError(s, w, r, "Error notifying client", err)
}
return
}
if !unique {
w.WriteHeader(http.StatusConflict)
return
}
})
}

View File

@@ -76,7 +76,7 @@ func Register(
method := r.Method
if method == "GET" {
tx.Commit()
page.Register(details.DiscordUser.Username).Render(r.Context(), w)
renderSafely(page.Register(details.DiscordUser.Username), s, r, w)
return
}
if method == "POST" {

View File

@@ -0,0 +1,41 @@
package handlers
import (
"context"
"net/http"
"time"
"git.haelnorr.com/h/golib/hws"
"git.haelnorr.com/h/oslstats/internal/db"
"git.haelnorr.com/h/oslstats/internal/view/page"
"github.com/pkg/errors"
"github.com/uptrace/bun"
)
func SeasonPage(
s *hws.Server,
conn *bun.DB,
) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 15*time.Second)
defer cancel()
tx, err := conn.BeginTx(ctx, nil)
if err != nil {
throwInternalServiceError(s, w, r, "Database error", errors.Wrap(err, "conn.BeginTx"))
return
}
defer tx.Rollback()
seasonStr := r.PathValue("season_short_name")
season, err := db.GetSeason(ctx, tx, seasonStr)
if err != nil {
throwInternalServiceError(s, w, r, "Database error", errors.Wrap(err, "db.GetSeason"))
return
}
tx.Commit()
if season == nil {
throwNotFound(s, w, r, r.URL.Path)
return
}
renderSafely(page.SeasonPage(season), s, r, w)
})
}

View File

@@ -58,7 +58,7 @@ func SeasonsPage(
return
}
tx.Commit()
page.SeasonsPage(seasons).Render(r.Context(), w)
renderSafely(page.SeasonsList(seasons), s, r, w)
})
}
@@ -123,6 +123,6 @@ func SeasonsList(
tx.Commit()
// Return only the list component (hx-push-url handles URL update client-side)
page.SeasonsList(seasons).Render(r.Context(), w)
renderSafely(page.SeasonsList(seasons), s, r, w)
})
}

View File

@@ -1,13 +1,14 @@
package handlers
import (
"git.haelnorr.com/h/golib/hws"
"net/http"
"path/filepath"
"strings"
"git.haelnorr.com/h/golib/hws"
)
// Handles requests for static files, without allowing access to the
// StaticFS handles requests for static files, without allowing access to the
// directory viewer and returning 404 if an exact file is not found
func StaticFS(staticFS *http.FileSystem, server *hws.Server) http.Handler {
// Create the file server once, not on every request

View File

@@ -9,7 +9,7 @@ import (
"git.haelnorr.com/h/oslstats/internal/view/page"
)
// Handles responses to the / path. Also serves a 404 Page for paths that
// NotifyTester handles responses to the / path. Also serves a 404 Page for paths that
// don't have explicit handlers
func NotifyTester(s *hws.Server) http.Handler {
return http.HandlerFunc(
@@ -22,9 +22,9 @@ func NotifyTester(s *hws.Server) http.Handler {
// Error: testErr,
// })
// page.Render(r.Context(), w)
page.Test().Render(r.Context(), w)
renderSafely(page.Test(), s, r, w)
} else {
r.ParseForm()
_ = r.ParseForm()
// target := r.Form.Get("target")
title := r.Form.Get("title")
level := r.Form.Get("type")