Skip to content

Commit

Permalink
Merge branch 'master' into misc-typefixes
Browse files Browse the repository at this point in the history
  • Loading branch information
allanjoseph98 committed Aug 11, 2024
2 parents 0e62261 + f663b42 commit 13cec0a
Show file tree
Hide file tree
Showing 314 changed files with 1,673 additions and 1,013 deletions.
7 changes: 6 additions & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,15 @@ jobs:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
run_install: true
run_install: false
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'
- uses: github/codeql-action/init@v3
with:
languages: javascript
- uses: github/codeql-action/analyze@v3
- run: pnpm install
- run: pnpm run lint
- run: pnpm run check-format
45 changes: 26 additions & 19 deletions app/controllers/Opening.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,26 +22,33 @@ final class Opening(env: Env) extends LilaController(env):
views.opening.ui.index(page, _)
}

private val openingRateLimit =
env.security.ipTrust.rateLimit(50, 10.minutes, "opening.byKeyAndMoves", _.proxyMultiplier(3))

def byKeyAndMoves(key: String, moves: String) = Open:
val crawler = HTTPRequest.isCrawler(ctx.req)
if moves.sizeIs > 40 && crawler.yes then Forbidden
else
env.opening.api.lookup(queryFromUrl(key, moves.some), isGrantedOpt(_.OpeningWiki), crawler).flatMap {
case None => Redirect(routes.Opening.index(key.some))
case Some(page) =>
val query = page.query.query
if query.key.isEmpty then Redirect(routes.Opening.index(key.some))
else if query.key != key then Redirect(routes.Opening.byKeyAndMoves(query.key, moves))
else if moves.nonEmpty && page.query.pgnUnderscored != moves && !getBool("r") then
Redirect:
s"${routes.Opening.byKeyAndMoves(query.key, page.query.pgnUnderscored)}?r=1"
else
Ok.async:
page.query.exactOpening.so(env.puzzle.opening.getClosestTo).map { puzzle =>
val puzzleKey = puzzle.map(_.fold(_.family.key.value, _.opening.key.value))
views.opening.ui.show(page, puzzleKey)
}
}
Firewall:
val crawler = HTTPRequest.isCrawler(ctx.req)
if moves.sizeIs > 10 && crawler.yes then Forbidden
else
openingRateLimit(rateLimited):
env.opening.api
.lookup(queryFromUrl(key, moves.some), isGrantedOpt(_.OpeningWiki), crawler)
.flatMap {
case None => Redirect(routes.Opening.index(key.some))
case Some(page) =>
val query = page.query.query
if query.key.isEmpty then Redirect(routes.Opening.index(key.some))
else if query.key != key then Redirect(routes.Opening.byKeyAndMoves(query.key, moves))
else if moves.nonEmpty && page.query.pgnUnderscored != moves && !getBool("r") then
Redirect:
s"${routes.Opening.byKeyAndMoves(query.key, page.query.pgnUnderscored)}?r=1"
else
Ok.async:
page.query.exactOpening.so(env.puzzle.opening.getClosestTo).map { puzzle =>
val puzzleKey = puzzle.map(_.fold(_.family.key.value, _.opening.key.value))
views.opening.ui.show(page, puzzleKey)
}
}

def config(thenTo: String) = OpenBody:
NoCrawlers:
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/Practice.scala
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ final class Practice(
private def analysisJson(us: UserStudy)(using Context): Fu[(JsObject, JsObject)] = us match
case UserStudy(_, _, chapters, WithChapter(study, chapter), _) =>
for
studyJson <- env.study.jsonView(study, chapters, chapter, none, withMembers = false)
studyJson <- env.study.jsonView.full(study, chapters, chapter, none, withMembers = false)
initialFen = chapter.root.fen.some
pov = userAnalysisC.makePov(initialFen, chapter.setup.variant)
baseData = env.round.jsonView
Expand Down
67 changes: 36 additions & 31 deletions app/controllers/RelayRound.scala
Original file line number Diff line number Diff line change
Expand Up @@ -117,36 +117,38 @@ final class RelayRound(
def apiShow(ts: String, rs: String, id: RelayRoundId) = AnonOrScoped(_.Study.Read, _.Web.Mobile):
doApiShow(id)

def embedShow(ts: String, rs: String, id: RelayRoundId) =
def embedShow(ts: String, rs: String, id: RelayRoundId): EssentialAction =
Anon:
InEmbedContext:
Found(env.relay.api.byIdWithTour(id)): rt =>
env.study.preview
.firstId(rt.round.studyId)
.flatMapz(env.study.api.byIdWithChapterOrFallback(rt.round.studyId, _))
.orNotFound: oldSc =>
studyC.CanView(oldSc.study)(
for
(sc, studyData) <- studyC.getJsonData(oldSc)
rounds <- env.relay.api.byTourOrdered(rt.tour)
group <- env.relay.api.withTours.get(rt.tour.id)
data = env.relay.jsonView.makeData(
rt.tour.withRounds(rounds.map(_.round)),
rt.round.id,
studyData,
group,
canContribute = false,
isSubscribed = none,
videoUrls = none,
pinned = none
)
sVersion <- NoCrawlers(env.study.version(sc.study.id))
embed <- views.relay.embed(rt.withStudy(sc.study), data, sVersion)
yield Ok(embed).enforceCrossSiteIsolation
)(
studyC.privateUnauthorizedFu(oldSc.study),
studyC.privateForbiddenFu(oldSc.study)
)
Found(env.relay.api.byIdWithTour(id))(embedShow)

def embedShow(rt: RoundModel.WithTour)(using EmbedContext): Fu[Result] =
env.study.preview
.firstId(rt.round.studyId)
.flatMapz(env.study.api.byIdWithChapterOrFallback(rt.round.studyId, _))
.orNotFound: oldSc =>
studyC.CanView(oldSc.study)(
for
(sc, studyData) <- studyC.getJsonData(oldSc)
rounds <- env.relay.api.byTourOrdered(rt.tour)
group <- env.relay.api.withTours.get(rt.tour.id)
data = env.relay.jsonView.makeData(
rt.tour.withRounds(rounds.map(_.round)),
rt.round.id,
studyData,
group,
canContribute = false,
isSubscribed = none,
videoUrls = none,
pinned = none
)
sVersion <- NoCrawlers(env.study.version(sc.study.id))
embed <- views.relay.embed(rt.withStudy(sc.study), data, sVersion)
yield Ok(embed).enforceCrossSiteIsolation
)(
studyC.privateUnauthorizedFu(oldSc.study),
studyC.privateForbiddenFu(oldSc.study)
)

private def doApiShow(id: RelayRoundId)(using Context): Fu[Result] =
Found(env.relay.api.byIdWithTour(id)): rt =>
Expand Down Expand Up @@ -193,9 +195,8 @@ final class RelayRound(
def chapter(ts: String, rs: String, id: RelayRoundId, chapterId: StudyChapterId, embed: Option[String]) =
Open:
WithRoundAndTour(ts, rs, id, chapterId.some): rt =>
env.study.api.byIdWithChapterOrFallback(rt.round.studyId, chapterId).orNotFound {
Found(env.study.api.byIdWithChapterOrFallback(rt.round.studyId, chapterId)):
doShow(rt, _, embed)
}

def push(id: RelayRoundId) = ScopedBody(parse.tolerantText)(Seq(_.Study.Write)) { ctx ?=> me ?=>
Found(env.relay.api.byIdWithTourAndStudy(id)): rt =>
Expand Down Expand Up @@ -267,7 +268,11 @@ final class RelayRound(
env.relay.api.isSubscribed(rt.tour.id, me.userId)
videoUrls <-
if (~embed).isEmpty then
fuccess(rt.tour.pinnedStream.flatMap(_.upstream).map(_.urls(netDomain).toPair))
fuccess:
rt.tour.pinnedStream
.ifFalse(rt.round.finished)
.flatMap(_.upstream)
.map(_.urls(netDomain).toPair)
else if embed.contains("no") then fuccess(none)
else
embed
Expand Down
16 changes: 8 additions & 8 deletions app/controllers/RelayTour.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import lila.core.net.IpAddress
import lila.relay.{ JsonView, RelayTour as TourModel }
import lila.relay.ui.FormNavigation

final class RelayTour(env: Env, apiC: => Api) extends LilaController(env):
final class RelayTour(env: Env, apiC: => Api, roundC: => RelayRound) extends LilaController(env):

def index(page: Int, q: String) = Open:
indexResults(page, q)
Expand Down Expand Up @@ -168,13 +168,13 @@ final class RelayTour(env: Env, apiC: => Api) extends LilaController(env):
else emptyBroadcastPage(tour)
case Some(round) => Redirect(round.withTour(tour).path)

def embedShow(slug: String, id: RelayTourId) = Open:
Found(env.relay.api.tourById(id)): tour =>
env.relay.listing.defaultRoundToShow
.get(tour.id)
.flatMap:
_.fold(emptyBroadcastPage(tour)): round =>
Redirect(s"/embed${round.withTour(tour).path}")
def embedShow(slug: String, id: RelayTourId) = Anon:
InEmbedContext:
Found(env.relay.api.tourById(id)): tour =>
env.relay.listing.defaultRoundToShow
.get(tour.id)
.flatMap:
_.map(_.withTour(tour)).fold(emptyBroadcastPage(tour))(roundC.embedShow)

private def emptyBroadcastPage(tour: TourModel)(using Context) = for
owner <- env.user.lightUser(tour.ownerId)
Expand Down
14 changes: 9 additions & 5 deletions app/controllers/Report.scala
Original file line number Diff line number Diff line change
Expand Up @@ -147,12 +147,15 @@ final class Report(env: Env, userC: => User, modC: => Mod) extends LilaControlle
val form = env.report.forms.create
val filledForm: Form[lila.report.ReportSetup] = (user, get("postUrl")) match
case (Some(u), Some(pid)) =>
form.fill(lila.report.ReportSetup(u.light, reason = "", text = s"$pid\n\n"))
form.fill(lila.report.ReportSetup(u.light, reason = ~get("reason"), text = s"$pid\n\n"))
case _ => form
views.report.ui.form(filledForm, user, get("from"))
}
}

private val reportRateLimit =
env.security.ipTrust.rateLimit(30, 3.hours, "report.create", _.proxyMultiplier(3))

def create = AuthBody { _ ?=> me ?=>
bindForm(env.report.forms.create)(
err =>
Expand All @@ -163,10 +166,11 @@ final class Report(env: Env, userC: => User, modC: => Mod) extends LilaControlle
data =>
if me.is(data.user.id) then BadRequest("You cannot report yourself")
else
for
_ <- api.create(data, Reporter(me), Nil)
_ <- api.isAutoBlock(data).so(env.relation.api.block(me, data.user.id))
yield Redirect(routes.Report.thanks).flashing("reported" -> data.user.name.value)
reportRateLimit(rateLimited):
for
_ <- api.create(data, Reporter(me), Nil)
_ <- api.isAutoBlock(data).so(env.relation.api.block(me, data.user.id))
yield Redirect(routes.Report.thanks).flashing("reported" -> data.user.name.value)
)
}

Expand Down
100 changes: 53 additions & 47 deletions app/controllers/Round.scala
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ final class Round(
.tryRedirect(gameId.into(UserStr))
.getOrElse(challengeC.showId(gameId.into(lila.challenge.ChallengeId)))

private def isBlockedByPlayer(game: GameModel)(using Context) =
game.isBeingPlayed.so(env.relation.api.isBlockedByAny(game.userIds))

private[controllers] def watch(pov: Pov, userTv: Option[UserModel] = None)(using
ctx: Context
): Fu[Result] =
Expand All @@ -144,53 +147,56 @@ final class Round(
if userTv.isDefined then watch(!pov, userTv)
else Redirect(routes.Round.watcher(pov.gameId, Color.white))
case _ =>
negotiateApi(
html =
if pov.game.replayable then analyseC.replay(pov, userTv = userTv)
else if HTTPRequest.isHuman(ctx.req) then
for
users <- env.user.api.gamePlayers(pov.game.userIdPair, pov.game.perfKey)
tour <- env.tournament.api.gameView.watcher(pov.game)
simul <- pov.game.simulId.so(env.simul.repo.find)
chat <- getWatcherChat(pov.game)
crosstable <- ctx.noBlind.so(env.game.crosstableApi.withMatchup(pov.game))
bookmarked <- env.bookmark.api.exists(pov.game, ctx.me)
tv = userTv.map: u =>
lila.round.OnTv.User(u.id)
data <- env.api.roundApi.watcher(pov, users, tour, tv)
page <- renderPage:
views.round.watcher(
pov,
data,
tour.map(_.tourAndTeamVs),
simul,
crosstable,
userTv = userTv,
chatOption = chat,
bookmarked = bookmarked
)
yield Ok(page)
else
for // web crawlers don't need the full thing
initialFen <- env.game.gameRepo.initialFen(pov.gameId)
pgn <- env.api
.pgnDump(pov.game, initialFen, none, lila.game.PgnDump.WithFlags(clocks = false))
page <- renderPage(views.round.crawler(pov, initialFen, pgn))
yield Ok(page)
,
api = _ =>
for
users <- env.user.api.gamePlayers(pov.game.userIdPair, pov.game.perfKey)
tour <- env.tournament.api.gameView.watcher(pov.game)
data <- env.api.roundApi.watcher(pov, users, tour, tv = none)
analysis <- env.analyse.analyser.get(pov.game)
chat <- getWatcherChat(pov.game)
jsChat <- chat.map(_.chat).soFu(lila.chat.JsonView.asyncLines)
yield Ok:
data
.add("chat" -> jsChat)
.add("analysis" -> analysis.map(a => lila.analyse.JsonView.mobile(pov.game, a)))
).dmap(_.noCache)
isBlockedByPlayer(pov.game).flatMap:
if _ then notFound
else
negotiateApi(
html =
if pov.game.replayable then analyseC.replay(pov, userTv = userTv)
else if HTTPRequest.isHuman(ctx.req) then
for
users <- env.user.api.gamePlayers(pov.game.userIdPair, pov.game.perfKey)
tour <- env.tournament.api.gameView.watcher(pov.game)
simul <- pov.game.simulId.so(env.simul.repo.find)
chat <- getWatcherChat(pov.game)
crosstable <- ctx.noBlind.so(env.game.crosstableApi.withMatchup(pov.game))
bookmarked <- env.bookmark.api.exists(pov.game, ctx.me)
tv = userTv.map: u =>
lila.round.OnTv.User(u.id)
data <- env.api.roundApi.watcher(pov, users, tour, tv)
page <- renderPage:
views.round.watcher(
pov,
data,
tour.map(_.tourAndTeamVs),
simul,
crosstable,
userTv = userTv,
chatOption = chat,
bookmarked = bookmarked
)
yield Ok(page)
else
for // web crawlers don't need the full thing
initialFen <- env.game.gameRepo.initialFen(pov.gameId)
pgn <- env.api
.pgnDump(pov.game, initialFen, none, lila.game.PgnDump.WithFlags(clocks = false))
page <- renderPage(views.round.crawler(pov, initialFen, pgn))
yield Ok(page)
,
api = _ =>
for
users <- env.user.api.gamePlayers(pov.game.userIdPair, pov.game.perfKey)
tour <- env.tournament.api.gameView.watcher(pov.game)
data <- env.api.roundApi.watcher(pov, users, tour, tv = none)
analysis <- env.analyse.analyser.get(pov.game)
chat <- getWatcherChat(pov.game)
jsChat <- chat.map(_.chat).soFu(lila.chat.JsonView.asyncLines)
yield Ok:
data
.add("chat" -> jsChat)
.add("analysis" -> analysis.map(a => lila.analyse.JsonView.mobile(pov.game, a)))
).dmap(_.noCache)

private[controllers] def getWatcherChat(
game: GameModel
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/Study.scala
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ final class Study(
)
)
withMembers = !study.isRelay || isGrantedOpt(_.StudyAdmin) || ctx.me.exists(study.isMember)
studyJson <- env.study.jsonView(study, previews, chapter, fedNames.some, withMembers = withMembers)
studyJson <- env.study.jsonView.full(study, previews, chapter, fedNames.some, withMembers = withMembers)
yield WithChapter(study, chapter) -> JsData(
study = studyJson,
analysis = baseData
Expand Down
1 change: 1 addition & 0 deletions app/http/RequestContext.scala
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ trait RequestContext(using Executor):
pageDataBuilder.dmap(PageContext(ctx, _))

def InEmbedContext[A](f: EmbedContext ?=> A)(using ctx: Context): A =
if !env.net.isProd then env.web.manifest.update()
f(using EmbedContext(ctx.req))

private def makeUserContext(req: RequestHeader): Fu[LoginContext] =
Expand Down
8 changes: 5 additions & 3 deletions app/views/base/embed.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,19 @@ object embed:
)

private def bodyModifiers(using ctx: EmbedContext) = List(
cls := List("simple-board" -> ctx.pref.simpleBoard),
page.ui.dataSoundSet := lila.pref.SoundSet.silent.key,
page.ui.dataAssetUrl,
page.ui.dataAssetVersion := assetVersion.value,
page.ui.dataTheme := ctx.bg,
page.ui.dataPieceSet := ctx.pieceSet.name,
page.ui.dataBoard := ctx.boardClass,
page.ui.dataDev,
page.ui.dataSocketDomains
page.ui.dataSocketDomains,
style := page.boardStyle(zoomable = false)
)

/* a heavier embed that loads site.ts */
/* a heavier embed that loads site.ts and connects to WS */
def site(
title: String,
cssKeys: List[String] = Nil,
Expand All @@ -68,7 +70,7 @@ object embed:
page.ui.sitePreload(allModules, isInquiry = false),
page.ui.lichessFontFaceCss
),
st.body(bodyModifiers)(style := "---zoom:80")(
st.body(bodyModifiers)(
body,
page.ui.modulesInit(allModules, ctx.nonce.some),
pageModule.map { mod => frag(jsonScript(mod.data)) }
Expand Down
Loading

0 comments on commit 13cec0a

Please sign in to comment.