Files
sm-bm-plugin/controllers/api/params.go
Jakub Zych 71b848a376 Adopt SummerCMS v0.1.1 admin features
Datepickers on native time.Time / lagoon.Date fields, file uploads on
materials, readings and class sessions managed from the course form
(course_id made nullable).
2026-10-03 06:03:34 +02:00

114 lines
3.4 KiB
Go

package api
import (
"net/http"
"strconv"
"time"
"git.golem15.com/golem15/summercms/modules/lagoon"
"git.golem15.com/jakub/sm-bm-plugin/models"
)
// Rule tables of the query parameters, in the order the errors are listed.
// lagoon.ParseRules panics on a bad rule, so a mistake here stops start-up.
var (
dateRule = `nullable|regex:/^\d{4}-\d{2}-\d{2}$/|date`
// rangeRules check from and to alone; rangeRulesFrom also orders them
// when both are given.
rangeRules = []lagoon.RequestRule{
{Field: "from", Rules: lagoon.ParseRules(dateRule)},
{Field: "to", Rules: lagoon.ParseRules(dateRule)},
}
rangeRulesFrom = []lagoon.RequestRule{
{Field: "from", Rules: lagoon.ParseRules(dateRule)},
{Field: "to", Rules: lagoon.ParseRules(dateRule + "|after_or_equal:from")},
}
courseFilterRules = []lagoon.RequestRule{
{Field: "course", Rules: lagoon.ParseRules("nullable|integer|min:1")},
}
coursesRules = []lagoon.RequestRule{
{Field: "semester", Rules: lagoon.ParseRules("nullable|integer|between:1,4")},
}
materialsRules = []lagoon.RequestRule{
{Field: "course", Rules: lagoon.ParseRules("nullable|integer|min:1")},
{Field: "tag", Rules: lagoon.ParseRules("nullable|string|max:100")},
{Field: "type", Rules: append(lagoon.ParseRules("nullable"), lagoon.In(models.MaterialTypes...))},
{Field: "page", Rules: lagoon.ParseRules("nullable|integer|min:1")},
{Field: "per_page", Rules: lagoon.ParseRules("nullable|integer|between:1,100")},
}
)
// query collects the named query parameters that are present into the input
// map lagoon.ValidateRequest takes. A repeated parameter keeps its first
// value.
func query(r *http.Request, names ...string) map[string]any {
q := r.URL.Query()
in := make(map[string]any, len(names))
for _, name := range names {
if vals, ok := q[name]; ok && len(vals) > 0 {
in[name] = vals[0]
}
}
return in
}
// validate checks in against rules and answers 422 itself when it fails.
func (h *Handlers) validate(w http.ResponseWriter, r *http.Request, in map[string]any, rules []lagoon.RequestRule) bool {
errs, err := lagoon.ValidateRequest(r.Context(), nil, in, rules, h.translator())
if err != nil {
writeOpaque500(w)
return false
}
if len(errs) > 0 {
writeValidation(w, errs)
return false
}
return true
}
// str returns a validated string parameter, or "" when it is absent.
func str(in map[string]any, name string) string {
s, _ := in[name].(string)
return s
}
// uintParam returns a validated positive integer parameter, or 0 when it is
// absent.
func uintParam(in map[string]any, name string) uint {
n, err := strconv.ParseUint(str(in, name), 10, 64)
if err != nil {
return 0
}
return uint(n)
}
// intParam returns a validated integer parameter, or def when it is absent.
func intParam(in map[string]any, name string, def int) int {
n, err := strconv.Atoi(str(in, name))
if err != nil {
return def
}
return n
}
// dayStart returns midnight in Europe/Warsaw of a validated YYYY-MM-DD
// parameter, and false when it is absent.
func dayStart(in map[string]any, name string) (time.Time, bool) {
s := str(in, name)
if s == "" {
return time.Time{}, false
}
d, err := models.ParseDate(s)
if err != nil {
return time.Time{}, false
}
return d.Time(models.Location), true
}
// today returns midnight of now's calendar day in Europe/Warsaw.
func today(now time.Time) time.Time {
y, m, d := now.In(models.Location).Date()
return time.Date(y, m, d, 0, 0, 0, 0, models.Location)
}