Skip to content

Commit 1b0300c

Browse files
committed
tutor builder limiter
1 parent a440aaa commit 1b0300c

File tree

8 files changed

+34
-12
lines changed

8 files changed

+34
-12
lines changed

app/controllers/Clas.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ final class Clas(env: Env, authC: Auth) extends LilaController(env):
121121
orDefault: Context => Fu[Result] = notFound(using _)
122122
)(using ctx: Context, me: Me): Fu[Result] =
123123
env.clas.api.clas
124-
.isTeacherOf(me, id)
124+
.isTeacherIn(me, id)
125125
.flatMap:
126126
if _ then forTeacher
127127
else

app/controllers/Tutor.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ final class Tutor(env: Env) extends LilaController(env):
2929
case TutorAvailability.Available(home) =>
3030
home.previews.headOption.ifTrue(getBool("waiting") && home.awaiting.isEmpty) match
3131
case Some(done) => Redirect(done.config.url.root).toFuccess
32-
case None => Ok.page(views.tutor.home(home, form))
32+
case None => Ok.page(views.tutor.home(home, form, env.tutor.limit.status(user.id)))
3333
yield res
3434

3535
def report(username: UserStr, range: String) = TutorReport(username, range) { _ ?=> full =>
@@ -73,9 +73,10 @@ final class Tutor(env: Env) extends LilaController(env):
7373
.get(config)
7474
.flatMap:
7575
case Some(report) => Redirect(report.url.root).toFuccess
76-
case None =>
76+
case _ if env.tutor.limit.zero(user.id)(true) =>
7777
for _ <- env.tutor.queue.enqueue(config)
7878
yield Redirect(routes.Tutor.user(user.username))
79+
case _ => Redirect(routes.Tutor.user(user.username)).toFuccess
7980
)
8081
}
8182

modules/clas/src/main/ClasApi.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ final class ClasApi(
9494
def teachers(clas: Clas): Fu[List[User]] =
9595
userRepo.byOrderedIds(clas.teachers.toList, readPref = _.sec)
9696

97-
def isTeacherOf(teacher: User, clasId: ClasId): Fu[Boolean] =
97+
def isTeacherIn(teacher: User, clasId: ClasId): Fu[Boolean] =
9898
Granter
9999
.of(_.Teacher)(teacher)
100100
.so:

modules/memo/src/main/RateLimit.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ final class RateLimit[K](
5353
def zero[A](k: K, cost: Cost = 1, msg: => String = "")(op: => A)(using default: Zero[A]): A =
5454
apply[A](k, default.zero, cost, msg)(op)
5555

56+
def status(k: K) = Status(storage.getIfPresent(k).so(_._1), credits)
57+
5658
def isLimited(k: K): Option[Instant] =
5759
enforce.yes
5860
.so(storage.getIfPresent(k))
@@ -75,6 +77,9 @@ object RateLimit:
7577

7678
case class Limited(key: String, msg: String, until: Instant)
7779

80+
case class Status(used: Int, max: Int):
81+
def reached = used >= max
82+
7883
trait RateLimiter[K]:
7984
def apply[A](k: K, default: => A, cost: Cost = 1, msg: => String = "")(op: => A): A
8085
def chargeable[A](k: K, default: => A, cost: Cost = 1, msg: => String = "")(op: ChargeWith => A): A

modules/tutor/src/main/Env.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import com.softwaremill.tagging.*
55

66
import lila.core.config
77
import lila.core.fishnet.{ AnalysisAwaiter, FishnetRequest }
8-
import lila.memo.CacheApi
8+
import lila.memo.{ RateLimit, CacheApi }
99
import lila.game.core.insight.InsightDb
1010
import lila.db.AsyncColl
1111

@@ -23,7 +23,7 @@ final class Env(
2323
lightUserApi: lila.core.user.LightUserApi,
2424
msgApi: lila.core.msg.MsgApi,
2525
routeUrl: config.RouteUrl
26-
)(using Executor, Scheduler, play.api.Mode):
26+
)(using Executor, Scheduler, play.api.Mode, config.RateLimit):
2727

2828
private val colls =
2929
TutorColls(insightDb(config.CollName("tutor_report")), insightDb(config.CollName("tutor_queue")))
@@ -46,6 +46,8 @@ final class Env(
4646

4747
val api = wire[TutorApi]
4848

49+
val limit = RateLimit[UserId](credits = 1, duration = 24.hour, key = "tutor.user")
50+
4951
final private class TutorColls(val report: AsyncColl, val queue: AsyncColl)
5052
trait NbAnalysis
5153
trait Parallelism

modules/tutor/src/main/ui/TutorHomeUi.scala

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@ import play.api.data.Form
55

66
import lila.ui.*
77
import lila.ui.ScalatagsTemplate.{ *, given }
8+
import lila.memo.RateLimit
89

910
final class TutorHomeUi(helpers: Helpers, bits: TutorBits, q: TutorQueueUi, rps: TutorReportsUi):
1011
import helpers.{ *, given }
1112

12-
def apply(home: TutorHome, form: Form[?])(using Context) =
13+
def apply(home: TutorHome, form: Form[?], limit: RateLimit.Status)(using Context) =
1314
Page("Lichess Tutor")
1415
.css("tutor.home")
1516
.js(Esm("bits.flatpickr"))
@@ -18,7 +19,7 @@ final class TutorHomeUi(helpers: Helpers, bits: TutorBits, q: TutorQueueUi, rps:
1819
main(cls := "page page-small tutor tutor-home"):
1920
if home.previews.isEmpty
2021
then newUser(home)
21-
else withReports(home, form)
22+
else withReports(home, form, limit)
2223

2324
private def newUser(home: TutorHome)(using Context) =
2425
import home.*
@@ -35,7 +36,7 @@ final class TutorHomeUi(helpers: Helpers, bits: TutorBits, q: TutorQueueUi, rps:
3536
)
3637
)
3738

38-
private def withReports(home: TutorHome, form: Form[?])(using Context) =
39+
private def withReports(home: TutorHome, form: Form[?], limit: RateLimit.Status)(using Context) =
3940
import home.*
4041
awaiting match
4142
case Some(a) =>
@@ -58,7 +59,7 @@ final class TutorHomeUi(helpers: Helpers, bits: TutorBits, q: TutorQueueUi, rps:
5859
)
5960
)
6061
),
61-
rps.newForm(user, form),
62+
rps.newForm(user, form, limit),
6263
rps.list(previews)
6364
)
6465

modules/tutor/src/main/ui/TutorReportsUi.scala

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,24 @@ import play.api.data.{ Form, Field }
77
import lila.ui.*
88
import lila.ui.ScalatagsTemplate.{ *, given }
99
import lila.insight.MeanRating
10+
import lila.memo.RateLimit
1011

1112
final class TutorReportsUi(helpers: Helpers, bits: TutorBits):
1213
import helpers.{ *, given }
1314

14-
def newForm(user: UserId, form: Form[?])(using Context) =
15+
def newForm(user: UserId, form: Form[?], limit: RateLimit.Status)(using Context) =
1516
postForm(cls := "form3 tutor__report-form", action := routes.Tutor.compute(user.id)):
1617
form3.fieldset("Request a new Tutor report", toggle = form.globalError.isDefined.some)(
1718
form3.split(
1819
form3.group(form("from"), "Start date")(datePickr)(cls := "form-third"),
1920
form3.group(form("to"), "End date")(datePickr)(cls := "form-third"),
20-
form3.submit("Compute my tutor report", icon = none)
21+
div(cls := "form-group")(
22+
span(cls := "tutor__report-form__limit")(s"${limit.used} / ${limit.max} daily reports used."),
23+
form3.submit("Compute my tutor report", icon = none)(
24+
limit.reached.option(disabled),
25+
cls := limit.reached.option("disabled")
26+
)
27+
)
2128
),
2229
form3.globalError(form)
2330
)

ui/tutor/css/_form.scss

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,10 @@
99
.form-group {
1010
margin: 0;
1111
}
12+
&__limit {
13+
display: block;
14+
margin: 0 0 0.5em 0;
15+
font-size: 0.9em;
16+
color: $c-font-dim;
17+
}
1218
}

0 commit comments

Comments
 (0)