Seed from go-template-repo, renamed to simplexcalc
The template's files at a77fd30, without its history or LICENSE, after script/rename simplexcalc. Model: opus-5-5
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"sneak.berlin/go/simplexcalc/internal/render"
|
||||
)
|
||||
|
||||
// widgetListLimit bounds the index query. An unbounded SELECT is fine
|
||||
// on the day it is written and is the outage two years later.
|
||||
const widgetListLimit = 50
|
||||
|
||||
// maxWidgetNameLen matches the maxlength on the form input. The form is
|
||||
// a courtesy; this is the rule.
|
||||
const maxWidgetNameLen = 200
|
||||
|
||||
// Index renders the front page from the embedded template, listing the
|
||||
// most recent widgets.
|
||||
func (h *Handlers) Index() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
widgets, err := h.params.Database.ListWidgets(ctx, widgetListLimit)
|
||||
if err != nil {
|
||||
h.fail(w, r, http.StatusInternalServerError, "Could not load widgets.", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
count, err := h.params.Database.CountWidgets(ctx)
|
||||
if err != nil {
|
||||
h.fail(w, r, http.StatusInternalServerError, "Could not count widgets.", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
data := render.IndexPage{
|
||||
Page: h.page(r),
|
||||
WidgetCount: count,
|
||||
Widgets: widgets,
|
||||
}
|
||||
|
||||
err = h.params.Renderer.HTML(w, http.StatusOK, "index.html", data)
|
||||
if err != nil {
|
||||
h.fail(w, r, http.StatusInternalServerError, "Could not render the page.", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CreateWidget handles the form POST. State-changing, so it is behind
|
||||
// CSRF; see internal/server/routes.go for where that is applied.
|
||||
func (h *Handlers) CreateWidget() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
// ParseForm reads the body, which BodyLimit has already capped:
|
||||
// an oversized submission fails here rather than being buffered
|
||||
// in full first.
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
h.fail(w, r, http.StatusBadRequest, "Could not read the form.", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
name := strings.TrimSpace(r.PostFormValue("name"))
|
||||
if name == "" || len(name) > maxWidgetNameLen {
|
||||
h.fail(w, r, http.StatusBadRequest,
|
||||
"A widget needs a name of 1 to 200 characters.", errBadWidgetName)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
size, err := parseSize(r.PostFormValue("size"))
|
||||
if err != nil {
|
||||
h.fail(w, r, http.StatusBadRequest,
|
||||
"Size must be a byte count, like 4096 or 4KiB.", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
_, err = h.params.Database.CreateWidget(r.Context(), name, size)
|
||||
if err != nil {
|
||||
h.fail(w, r, http.StatusInternalServerError, "Could not save the widget.", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// POST/redirect/GET: a reload must not repeat the write.
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
}
|
||||
}
|
||||
|
||||
// parseSize accepts an empty value as zero and anything else as a
|
||||
// human-readable byte size.
|
||||
func parseSize(s string) (int64, error) {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
n, err := humanize.ParseBytes(s)
|
||||
if err != nil {
|
||||
return 0, errBadWidgetSize
|
||||
}
|
||||
|
||||
// A size beyond this is not a widget, it is a typo with a suffix.
|
||||
const maxWidgetSize = uint64(1) << 50
|
||||
if n > maxWidgetSize {
|
||||
return 0, errBadWidgetSize
|
||||
}
|
||||
|
||||
return int64(n), nil
|
||||
}
|
||||
Reference in New Issue
Block a user