Skip to content

Commit

Permalink
Merge branch 'master' into plus-normalization-emails
Browse files Browse the repository at this point in the history
  • Loading branch information
ornicar authored Jan 21, 2025
2 parents fdef0d8 + c6d7b20 commit 0b73e49
Show file tree
Hide file tree
Showing 728 changed files with 4,667 additions and 3,465 deletions.
5 changes: 4 additions & 1 deletion .git-blame-ignore-revs
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,7 @@ f617815e0a3a2d96de9aca3a04d589c78cbd14e8
35c26fbd9bc0895d0119e4e313003c220417d4bc

# back to prettier
02354d6ac319462e58f1016499912d342efb97d1
02354d6ac319462e58f1016499912d342efb97d1

# Reformat with scalafmt 3.8.4
4c06b75e1315f0cfcb3ebeb56a6d4c9cdcd6d062
4 changes: 4 additions & 0 deletions .github/workflows/server.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ jobs:
distribution: temurin
java-version: 21
cache: sbt
- name: Setup sbt
uses: sbt/setup-sbt@v1
- run: TZ=UTC git log -1 --date=iso-strict-local --pretty='format:app.version.commit = "%H"%napp.version.date = "%ad"%napp.version.message = """%s"""%n' | tee conf/version.conf
- run: ./lila.sh -Depoll=true "test;stage"
- run: cp LICENSE COPYING.md README.md target/universal/stage && git log -n 1 --pretty=oneline > target/universal/stage/commit.txt
Expand All @@ -61,6 +63,8 @@ jobs:
distribution: temurin
java-version: 21
cache: sbt
- name: Setup sbt
uses: sbt/setup-sbt@v1

- name: Check Formatting
run: sbt scalafmtCheckAll
2 changes: 1 addition & 1 deletion .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version = "3.8.3"
version = "3.8.5"
runner.dialect = scala3

align.preset = more
Expand Down
36 changes: 20 additions & 16 deletions app/Env.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,33 +11,36 @@ final class Env(
val config: Configuration,
val controllerComponents: ControllerComponents,
environment: Environment,
shutdown: akka.actor.CoordinatedShutdown
shutdown: akka.actor.CoordinatedShutdown,
cookieBaker: SessionCookieBaker
)(using val system: akka.actor.ActorSystem, val executor: Executor)(using
StandaloneWSClient,
akka.stream.Materializer,
SessionCookieBaker
akka.stream.Materializer
):
val net: NetConfig = config.get[NetConfig]("net")

export net.{ domain, baseUrl, assetBaseUrlInternal }
export net.{ baseUrl, assetBaseUrlInternal }

given mode: Mode = environment.mode
given translator: lila.core.i18n.Translator = lila.i18n.Translator
given scheduler: Scheduler = system.scheduler
given lila.core.config.RateLimit = net.rateLimit
given RateLimit = net.rateLimit
given NetDomain = net.domain

// wire all the lila modules in the right order
val i18n: lila.i18n.Env.type = lila.i18n.Env
val mongo: lila.db.Env = wire[lila.db.Env]
val memo: lila.memo.Env = wire[lila.memo.Env]
val socket: lila.socket.Env = wire[lila.socket.Env]
val user: lila.user.Env = wire[lila.user.Env]
val mailer: lila.mailer.Env = wire[lila.mailer.Env]
val oAuth: lila.oauth.Env = wire[lila.oauth.Env]
val security: lila.security.Env = wire[lila.security.Env]
val pref: lila.pref.Env = wire[lila.pref.Env]
val relation: lila.relation.Env = wire[lila.relation.Env]
val game: lila.game.Env = wire[lila.game.Env]
val i18n: lila.i18n.Env.type = lila.i18n.Env
val mongo: lila.db.Env = wire[lila.db.Env]
val memo: lila.memo.Env = wire[lila.memo.Env]
val socket: lila.socket.Env = wire[lila.socket.Env]
val user: lila.user.Env = wire[lila.user.Env]
import user.flairApi.given
val mailer: lila.mailer.Env = wire[lila.mailer.Env]
val oAuth: lila.oauth.Env = wire[lila.oauth.Env]
val security: lila.security.Env = wire[lila.security.Env]
val pref: lila.pref.Env = wire[lila.pref.Env]
val relation: lila.relation.Env = wire[lila.relation.Env]
val game: lila.game.Env = wire[lila.game.Env]
import game.given
val notifyM: lila.notify.Env = wire[lila.notify.Env]
val irc: lila.irc.Env = wire[lila.irc.Env]
val report: lila.report.Env = wire[lila.report.Env]
Expand All @@ -62,6 +65,7 @@ final class Env(
val forum: lila.forum.Env = wire[lila.forum.Env]
val forumSearch: lila.forumSearch.Env = wire[lila.forumSearch.Env]
val pool: lila.pool.Env = wire[lila.pool.Env]
import pool.isClockCompatible
val lobby: lila.lobby.Env = wire[lila.lobby.Env]
val setup: lila.setup.Env = wire[lila.setup.Env]
val simul: lila.simul.Env = wire[lila.simul.Env]
Expand Down
40 changes: 32 additions & 8 deletions app/controllers/Account.scala
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,7 @@ final class Account(
env.security.emailChange
.send(me, newUserEmail.email)
.inject(Redirect(routes.Account.email).flashSuccess:
lila.core.i18n.I18nKey.site.checkYourEmail.txt()
)
lila.core.i18n.I18nKey.site.checkYourEmail.txt())
}

def emailConfirm(token: String) = Open:
Expand Down Expand Up @@ -240,23 +239,48 @@ final class Account(
}

def close = Auth { _ ?=> me ?=>
env.clas.api.student.isManaged(me).flatMap { managed =>
env.security.forms.closeAccount.flatMap: form =>
Ok.page(pages.close(form, managed))
}
for
managed <- env.clas.api.student.isManaged(me)
form <- env.security.forms.closeAccount
res <- Ok.page(pages.close(form, managed))
yield res
}

def closeConfirm = AuthBody { ctx ?=> me ?=>
NotManaged:
auth.HasherRateLimit:
env.security.forms.closeAccount.flatMap: form =>
FormFuResult(form)(err => renderPage(pages.close(err, managed = false))): _ =>
env.api.accountClosure
.close(me.value)
env.api.accountTermination
.disable(me.value)
.inject:
Redirect(routes.User.show(me.username)).withCookies(env.security.lilaCookie.newSession)
}

def delete = Auth { _ ?=> me ?=>
for
managed <- env.clas.api.student.isManaged(me)
form <- env.security.forms.deleteAccount
res <- Ok.page(pages.delete(form, managed))
yield res
}

def deleteConfirm = AuthBody { ctx ?=> me ?=>
NotManaged:
auth.HasherRateLimit:
env.security.forms.deleteAccount.flatMap: form =>
FormFuResult(form)(err => renderPage(pages.delete(err, managed = false))): _ =>
env.api.accountTermination
.scheduleDelete(me.value)
.inject:
Redirect(routes.Account.deleteDone).withCookies(env.security.lilaCookie.newSession)
}

def deleteDone = Open { ctx ?=>
if ctx.isAuth then Redirect(routes.Lobby.home)
else FoundPage(env.cms.renderKey("delete-done"))(views.site.page.lone)
}

def kid = Auth { _ ?=> me ?=>
for
managed <- env.clas.api.student.isManaged(me)
Expand Down
8 changes: 7 additions & 1 deletion app/controllers/Api.scala
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ final class Api(

def perfStat(username: UserStr, perfKey: PerfKey) = ApiRequest:
env.perfStat.api
.data(username, perfKey)
.data(username, perfKey, computeIfNeeded = true)
.map:
_.fold[ApiResult](ApiResult.NoData) { data => ApiResult.Data(env.perfStat.jsonView(data)) }

Expand Down Expand Up @@ -384,6 +384,12 @@ final class Api(
ttl = 1.hour,
maxConcurrency = 4
)
val eventsForVerifiedUser = lila.web.ConcurrencyLimit[IpAddress](
name = "API verified events concurrency per IP",
key = "api.ip.events.verified",
ttl = 1.hour,
maxConcurrency = 12
)
val download = lila.web.ConcurrencyLimit[IpAddress](
name = "API download concurrency per IP",
key = "api.ip.download",
Expand Down
6 changes: 2 additions & 4 deletions app/controllers/Auth.scala
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ final class Auth(
lila.mon.user.register.confirmEmailResult(true).increment()
env.user.repo.email(user.id).flatMap {
_.so: email =>
authLog(user.username, email.some, s"Confirmed email ${email.value}")
authLog(user.username, email.some, s"Confirmed email")
welcome(user, email, sendWelcomeEmail = false)
} >> redirectNewUser(user)
}
Expand Down Expand Up @@ -417,9 +417,7 @@ final class Auth(
lila.mon.user.auth.magicLinkRequest("success").increment()
env.security.magicLink
.send(user, storedEmail)
.inject(Redirect:
routes.Auth.magicLinkSent
)
.inject(Redirect(routes.Auth.magicLinkSent))
case _ =>
lila.mon.user.auth.magicLinkRequest("no_email").increment()
Redirect(routes.Auth.magicLinkSent)
Expand Down
3 changes: 1 addition & 2 deletions app/controllers/Challenge.scala
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,7 @@ final class Challenge(env: Env) extends LilaController(env):
Redirect(routes.Round.watcher(c.gameId, color | Color.white)),
notFoundJson(invalid match
case Left(err) => err
case _ => "The challenge has already been accepted"
)
case _ => "The challenge has already been accepted")
)
)

Expand Down
9 changes: 4 additions & 5 deletions app/controllers/Clas.scala
Original file line number Diff line number Diff line change
Expand Up @@ -359,8 +359,8 @@ final class Clas(env: Env, authC: Auth) extends LilaController(env):
,
data =>
Found(env.user.repo.enabledById(data.username)): user =>
import lila.clas.ClasInvite.{ Feedback as F }
import lila.core.i18n.{ I18nKey as trans }
import lila.clas.ClasInvite.Feedback as F
import lila.core.i18n.I18nKey as trans
env.clas.api.invite.create(clas, user, data.realName).map { feedback =>
Redirect(routes.Clas.studentForm(clas.id)).flashing:
feedback match
Expand Down Expand Up @@ -446,8 +446,7 @@ final class Clas(env: Env, authC: Auth) extends LilaController(env):
env.security.emailChange
.send(s.user, newUserEmail.email)
.inject(Redirect(routes.Clas.studentShow(clas.id, s.user.username)).flashSuccess:
s"A confirmation email was sent to ${email}. ${s.student.realName} must click the link in the email to release the account."
)
s"A confirmation email was sent to ${email}. ${s.student.realName} must click the link in the email to release the account.")
)
else Redirect(routes.Clas.studentShow(clas.id, s.user.username))
}
Expand All @@ -465,7 +464,7 @@ final class Clas(env: Env, authC: Auth) extends LilaController(env):
WithStudent(clas, username): s =>
if s.student.managed then
(env.clas.api.student.closeAccount(s) >>
env.api.accountClosure.close(s.user)).inject(redirectTo(clas).flashSuccess)
env.api.accountTermination.disable(s.user)).inject(redirectTo(clas).flashSuccess)
else if s.student.isArchived then
env.clas.api.student.closeAccount(s) >>
redirectTo(clas).flashSuccess
Expand Down
21 changes: 11 additions & 10 deletions app/controllers/Export.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,29 @@ import lila.pref.{ PieceSet, Theme }

final class Export(env: Env) extends LilaController(env):

private def exportImageOf[A](fetch: Fu[Option[A]])(convert: A => Fu[Result]) = Anon:
private def exportImageOf[A](fetch: Fu[Option[A]])(convert: A => Fu[Result])(using Context) =
Found(fetch): res =>
limit.exportImage(((), req.ipAddress), rateLimited)(convert(res))

def gif(id: GameId, color: Color, theme: Option[String], piece: Option[String]) =
exportImageOf(env.game.gameRepo.gameWithInitialFen(id)) { g =>
env.game.gifExport
.fromPov(Pov(g.game, color), g.fen, Theme(theme).name, PieceSet.get(piece).name)
.pipe(stream(cacheSeconds = if g.game.finishedOrAborted then 3600 * 24 else 10))
}
def gif(id: GameId, color: Color, theme: Option[String], piece: Option[String]) = Anon:
NoCrawlers:
exportImageOf(env.game.gameRepo.gameWithInitialFen(id)) { g =>
env.game.gifExport
.fromPov(Pov(g.game, color), g.fen, Theme(theme).name, PieceSet.get(piece).name)
.pipe(stream(cacheSeconds = if g.game.finishedOrAborted then 3600 * 24 else 10))
}

def legacyGameThumbnail(id: GameId, theme: Option[String], piece: Option[String]) = Anon:
MovedPermanently(routes.Export.gameThumbnail(id, theme, piece).url)

def gameThumbnail(id: GameId, theme: Option[String], piece: Option[String]) =
def gameThumbnail(id: GameId, theme: Option[String], piece: Option[String]) = Anon:
exportImageOf(env.game.gameRepo.game(id)) { game =>
env.game.gifExport
.gameThumbnail(game, Theme(theme).name, PieceSet.get(piece).name)
.pipe(stream(cacheSeconds = if game.finishedOrAborted then 3600 * 24 else 10))
}

def puzzleThumbnail(id: PuzzleId, theme: Option[String], piece: Option[String]) =
def puzzleThumbnail(id: PuzzleId, theme: Option[String], piece: Option[String]) = Anon:
exportImageOf(env.puzzle.api.puzzle.find(id)) { puzzle =>
env.game.gifExport
.thumbnail(
Expand All @@ -54,7 +55,7 @@ final class Export(env: Env) extends LilaController(env):
variant: Option[Variant.LilaKey],
theme: Option[String],
piece: Option[String]
) =
) = Anon:
exportImageOf(fuccess(Fen.read(Variant.orDefault(variant), Fen.Full.clean(fen)))) { situation =>
env.game.gifExport
.thumbnail(
Expand Down
6 changes: 2 additions & 4 deletions app/controllers/Game.scala
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,7 @@ final class Game(env: Env, apiC: => Api) extends LilaController(env):
if ctx.is(UserId.explorer) then env.web.settings.apiExplorerGamesPerSecond.get()
else if ctx.is(user) then 60
else if ctx.isOAuth then 30 // bonus for oauth logged in only (not for CSRF)
else 25
)
else 25)
.flatMap: perSecond =>
val finished = getBoolOpt("finished") | true
val config = GameApiV2.ByUserConfig(
Expand Down Expand Up @@ -161,5 +160,4 @@ final class Game(env: Env, apiC: => Api) extends LilaController(env):
env.user.lightUserApi.preloadMany(game.userIds)
private[controllers] def preloadUsers(users: lila.core.user.GameUsers): Unit =
env.user.lightUserApi.preloadUsers(users.all.collect:
case Some(lila.core.user.WithPerf(u, _)) => u
)
case Some(lila.core.user.WithPerf(u, _)) => u)
2 changes: 1 addition & 1 deletion app/controllers/GameMod.scala
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ final class GameMod(env: Env)(using akka.stream.Materializer) extends LilaContro
),
lila.fishnet.Work.Origin.autoHunter.some
)
_ <- env.fishnet.awaiter(games.map(_.id), 2 minutes)
_ <- env.fishnet.awaiter(games.map(_.id), 2.minutes)
yield NoContent

private def downloadPgn(user: lila.user.User, gameIds: Seq[GameId])(using Context) =
Expand Down
4 changes: 3 additions & 1 deletion app/controllers/LilaController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,9 @@ abstract private[controllers] class LilaController(val env: Env)
oauthBodyContext(scoped).flatMap: ctx =>
f(using ctx)(using scoped.me)

private def handleScopedCommon(selectors: Seq[OAuthScope.Selector])(using req: RequestHeader)(
private def handleScopedCommon(selectors: Seq[OAuthScope.Selector])(using
req: RequestHeader
)(
f: OAuthScope.Scoped => Fu[Result]
) =
val accepted = OAuthScope.select(selectors).into(EndpointScopes)
Expand Down
19 changes: 8 additions & 11 deletions app/controllers/Mod.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ final class Mod(
withSuspect(username): sus =>
for
_ <- api.setAlt(sus, v)
_ <- (v && sus.user.enabled.yes).so(env.api.accountClosure.close(sus.user))
_ <- (v && sus.user.enabled.yes).so(env.api.accountTermination.disable(sus.user))
_ <- (!v && sus.user.enabled.no).so(api.reopenAccount(sus.user.id))
yield sus.some
}(reportC.onModAction)
Expand All @@ -40,7 +40,7 @@ final class Mod(
Source(ctx.body.body.split(' ').toList.flatMap(UserStr.read))
.mapAsync(1): username =>
withSuspect(username): sus =>
api.setAlt(sus, true) >> (sus.user.enabled.yes.so(env.api.accountClosure.close(sus.user)))
api.setAlt(sus, true) >> (sus.user.enabled.yes.so(env.api.accountTermination.disable(sus.user)))
.runWith(Sink.ignore)
.void
.inject(NoContent)
Expand Down Expand Up @@ -114,7 +114,7 @@ final class Mod(

def closeAccount(username: UserStr) = OAuthMod(_.CloseAccount) { _ ?=> me ?=>
meOrFetch(username).flatMapz: user =>
env.api.accountClosure.close(user).map(some)
env.api.accountTermination.disable(user).map(some)
}(actionResult(username))

def reopenAccount(username: UserStr) = OAuthMod(_.CloseAccount) { _ ?=> me ?=>
Expand Down Expand Up @@ -380,13 +380,10 @@ final class Mod(
env.user.noteApi.search(q.trim, page, withDox = true).map(views.mod.search.notes(q, _))
}

def gdprErase(username: UserStr) = Secure(_.GdprErase) { _ ?=> me ?=>
val res = Redirect(routes.User.show(username))
env.api.accountClosure
.closeThenErase(username)
.map:
case Right(msg) => res.flashSuccess(msg)
case Left(err) => res.flashFailure(err)
def gdprErase(username: UserStr) = Secure(_.GdprErase) { _ ?=> _ ?=>
Found(env.user.repo.byId(username)): user =>
for _ <- env.api.accountTermination.scheduleErase(user)
yield Redirect(routes.User.show(username)).flashSuccess("Erasure scheduled")
}

protected[controllers] def searchTerm(query: String)(using Context, Me) =
Expand All @@ -411,7 +408,7 @@ final class Mod(

def printBan(v: Boolean, fh: String) = Secure(_.PrintBan) { _ ?=> me ?=>
val hash = FingerHash(fh)
env.security.printBan.toggle(hash, v).inject(Redirect(routes.Mod.print(fh)))
for _ <- env.security.printBan.toggle(hash, v) yield Redirect(routes.Mod.print(fh))
}

def singleIp(ip: String) = SecureBody(_.ViewPrintNoIP) { ctx ?=> me ?=>
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/PlayApi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ final class PlayApi(env: Env)(using akka.stream.Materializer) extends LilaContro
}

private val botsCache = env.memo.cacheApi.unit[List[UserWithPerfs]]:
_.expireAfterWrite(10 seconds).buildAsyncFuture: _ =>
_.expireAfterWrite(10.seconds).buildAsyncFuture: _ =>
env.user.api.visibleBotsByIds(env.bot.onlineApiUsers.get)

def botOnline = Open:
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/Recap.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package controllers
import play.api.mvc.*

import lila.app.{ *, given }
import lila.recap.{ Recap as RecapModel }
import lila.recap.Recap as RecapModel
import lila.recap.Recap.Availability

final class Recap(env: Env) extends LilaController(env):
Expand Down
Loading

0 comments on commit 0b73e49

Please sign in to comment.