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) }