fix: throttle student answers and attempt starts per user
WR-03: every new or changed open answer in a learning quiz with a grader is a paid grading call. Add per-user buckets golem15.quizzes.answer (60/min) and golem15.quizzes.start (10/min), configurable under golem15.quizzes.throttle.*.
This commit is contained in:
@@ -52,6 +52,36 @@ func TestGraceFrom(cfg *compass.Config) time.Duration {
|
||||
return min(max(grace, 0), maxTestGrace)
|
||||
}
|
||||
|
||||
// Default per-user rate limits of the student write routes, per minute. In
|
||||
// a learning quiz with a grader every new or changed open answer is a paid
|
||||
// grading call, so answering and starting attempts are throttled (WR-03).
|
||||
const (
|
||||
DefaultAnswersPerMinute = 60
|
||||
DefaultAttemptsPerMinute = 10
|
||||
)
|
||||
|
||||
// maxPerMinute caps a configured per-minute limit.
|
||||
const maxPerMinute = 10000
|
||||
|
||||
// PerMinuteFrom reads a per-minute rate limit at key (for example
|
||||
// golem15.quizzes.throttle.answers_per_minute). A nil config, a missing
|
||||
// key or a value that is not a number of at least 1 give def; the value
|
||||
// is truncated to a whole number and capped at 10000.
|
||||
func PerMinuteFrom(cfg *compass.Config, key string, def int) int {
|
||||
if cfg == nil {
|
||||
return def
|
||||
}
|
||||
v, ok := cfg.Lookup(key)
|
||||
if !ok {
|
||||
return def
|
||||
}
|
||||
n, ok := number(v)
|
||||
if !ok || n < 1 || math.IsInf(n, 0) {
|
||||
return def
|
||||
}
|
||||
return int(min(n, maxPerMinute))
|
||||
}
|
||||
|
||||
// number is v as a float when it is a number or a numeric string.
|
||||
func number(v any) (float64, bool) {
|
||||
switch n := v.(type) {
|
||||
|
||||
Reference in New Issue
Block a user