Skip to content

Commit 7efd54a

Browse files
authored
Merge branch 'master' into resume-practice-end-states
2 parents d95c28f + 45cff6a commit 7efd54a

File tree

422 files changed

+2000
-816
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

422 files changed

+2000
-816
lines changed

.github/workflows/server.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,15 @@ env:
3333
jobs:
3434
build:
3535
runs-on: ubuntu-latest
36+
strategy:
37+
matrix:
38+
java-version: [21, 25]
3639
steps:
3740
- uses: actions/checkout@v5
3841
- uses: actions/setup-java@v5
3942
with:
4043
distribution: temurin
41-
java-version: 21
44+
java-version: ${{ matrix.java-version }}
4245
cache: sbt
4346
- name: Setup sbt
4447
uses: sbt/setup-sbt@v1
@@ -49,6 +52,7 @@ jobs:
4952
env:
5053
ZSTD_LEVEL: 1 # most files are already zipped
5154
- uses: actions/upload-artifact@v4
55+
if: ${{ matrix.java-version == 21 }}
5256
with:
5357
name: lila-server
5458
path: lila-3.0.tar.zst

app/controllers/Auth.scala

Lines changed: 33 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -202,37 +202,39 @@ final class Auth(env: Env, accountC: => Account) extends LilaController(env):
202202
Firewall:
203203
WithProxy: proxy ?=>
204204
limit.enumeration.signup(rateLimited):
205-
proxy.no.so(forms.preloadEmailDns()) >> negotiateApi(
206-
html = env.security.signup
207-
.website(ctx.blind)
208-
.flatMap:
209-
case Signup.Result.RateLimited | Signup.Result.ForbiddenNetwork => rateLimited
210-
case Signup.Result.MissingCaptcha =>
211-
forms.signup.website.flatMap: form =>
212-
BadRequest.page(views.auth.signup(form))
213-
case Signup.Result.Bad(err) =>
214-
forms.signup.website.flatMap: baseForm =>
215-
BadRequest.page(views.auth.signup(baseForm.withForm(err)))
216-
case Signup.Result.ConfirmEmail(user, email) =>
217-
Redirect(routes.Auth.checkYourEmail).withCookies:
218-
EmailConfirm.cookie.make(env.security.lilaCookie, user, email)(using ctx.req)
219-
case Signup.Result.AllSet(user, email) =>
220-
welcome(user, email, sendWelcomeEmail = true) >> redirectNewUser(user)
221-
,
222-
api = apiVersion =>
223-
env.security.signup
224-
.mobile(apiVersion)
225-
.flatMap:
226-
case Signup.Result.RateLimited => rateLimited
227-
case Signup.Result.ForbiddenNetwork =>
228-
BadRequest(jsonError("This network cannot create new accounts."))
229-
case Signup.Result.MissingCaptcha => BadRequest(jsonError("Missing captcha?!"))
230-
case Signup.Result.Bad(err) => doubleJsonFormError(err)
231-
case Signup.Result.ConfirmEmail(_, _) => Ok(Json.obj("email_confirm" -> true))
232-
case Signup.Result.AllSet(user, email) =>
233-
welcome(user, email, sendWelcomeEmail = true) >>
234-
authenticateUser(user, remember = true, pwned = IsPwned.No)
235-
)
205+
proxy.no.so(forms.preloadEmailDns()) >>
206+
HTTPRequest
207+
.apiVersion(ctx.req)
208+
.match
209+
case None =>
210+
env.security.signup
211+
.website(ctx.blind)
212+
.flatMap:
213+
case Signup.Result.RateLimited | Signup.Result.ForbiddenNetwork => rateLimited
214+
case Signup.Result.MissingCaptcha =>
215+
forms.signup.website.flatMap: form =>
216+
BadRequest.page(views.auth.signup(form))
217+
case Signup.Result.Bad(err) =>
218+
forms.signup.website.flatMap: baseForm =>
219+
BadRequest.page(views.auth.signup(baseForm.withForm(err)))
220+
case Signup.Result.ConfirmEmail(user, email) =>
221+
Redirect(routes.Auth.checkYourEmail).withCookies:
222+
EmailConfirm.cookie.make(env.security.lilaCookie, user, email)(using ctx.req)
223+
case Signup.Result.AllSet(user, email) =>
224+
welcome(user, email, sendWelcomeEmail = true) >> redirectNewUser(user)
225+
case Some(apiVersion) =>
226+
env.security.signup
227+
.mobile(apiVersion)
228+
.flatMap:
229+
case Signup.Result.RateLimited => rateLimited
230+
case Signup.Result.ForbiddenNetwork =>
231+
BadRequest(jsonError("This network cannot create new accounts."))
232+
case Signup.Result.MissingCaptcha => BadRequest(jsonError("Missing captcha?!"))
233+
case Signup.Result.Bad(err) => doubleJsonFormError(err)
234+
case Signup.Result.ConfirmEmail(_, _) => Ok(Json.obj("email_confirm" -> true))
235+
case Signup.Result.AllSet(user, email) =>
236+
welcome(user, email, sendWelcomeEmail = true) >>
237+
authenticateUser(user, remember = true, pwned = IsPwned.No)
236238

237239
private def welcome(user: UserModel, email: EmailAddress, sendWelcomeEmail: Boolean)(using
238240
ctx: Context

app/controllers/Dev.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ final class Dev(env: Env) extends LilaController(env):
88
env.security.ugcArmedSetting,
99
env.security.spamKeywordsSetting,
1010
env.security.proxy2faSetting,
11-
env.security.mobileSignupProxy,
1211
env.security.alwaysCaptcha,
1312
env.oAuth.originBlocklistSetting,
1413
env.mailer.mailerSecondaryPermilleSetting,

app/controllers/LilaController.scala

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -369,4 +369,18 @@ abstract private[controllers] class LilaController(val env: Env)
369369
def anyCaptcha = env.game.captcha.any
370370

371371
def bindForm[T, R](form: Form[T])(error: Form[T] => R, success: T => R)(using Request[?], FormBinding): R =
372-
form.bindFromRequest().fold(error, success)
372+
val bound =
373+
if getBool("patch")
374+
then bindPatchForm(form)
375+
else form.bindFromRequest()
376+
bound.fold(error, success)
377+
378+
private def bindPatchForm[T](form: Form[T])(using req: Request[?], formBinding: FormBinding): Form[T] =
379+
form.bind:
380+
// combine pre-filled data with request data
381+
formBinding(req).foldLeft(form.data) { case (s, (key, values)) =>
382+
if key.endsWith("[]") then
383+
val k = key.dropRight(2)
384+
s ++ values.zipWithIndex.map { (v, i) => s"$k[$i]" -> v }
385+
else s + (key -> ~values.headOption)
386+
}

app/controllers/Main.scala

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -137,21 +137,23 @@ final class Main(
137137
Redirect(url)
138138

139139
def uploadImage(rel: String) = AuthBody(parse.multipartFormData) { ctx ?=> me ?=>
140-
(rel == "ublogBody" || lila.core.security.canUploadImages).so:
141-
limit.imageUpload(rateLimited):
142-
ctx.body.body.file("image") match
143-
case None => JsonBadRequest("Image content only")
144-
case Some(image) =>
145-
val meta = lila.memo.PicfitApi.form.upload.bindFromRequest().value
146-
val moreRel = s"$rel:${scalalib.ThreadLocalRandom.nextString(12)}"
147-
for
148-
image <- env.memo.picfitApi.uploadFile(moreRel, image, me, meta)
149-
maxWidth = lila.ui.bits.imageDesignWidth(rel)
150-
url = meta match
151-
case Some(info) if maxWidth.exists(dw => info.dim.width > dw) =>
152-
maxWidth.map(dw => env.memo.picfitUrl.resize(image.id, Left(dw)))
153-
case _ => env.memo.picfitUrl.raw(image.id).some
154-
yield JsonOk(Json.obj("imageUrl" -> url))
140+
lila.core.security
141+
.canUploadImages(rel)
142+
.so:
143+
limit.imageUpload(rateLimited):
144+
ctx.body.body.file("image") match
145+
case None => JsonBadRequest("Image content only")
146+
case Some(image) =>
147+
val meta = lila.memo.PicfitApi.form.upload.bindFromRequest().value
148+
val moreRel = s"$rel:${scalalib.ThreadLocalRandom.nextString(12)}"
149+
for
150+
image <- env.memo.picfitApi.uploadFile(moreRel, image, me, meta)
151+
maxWidth = lila.ui.bits.imageDesignWidth(rel)
152+
url = meta match
153+
case Some(info) if maxWidth.exists(dw => info.dim.width > dw) =>
154+
maxWidth.map(dw => env.memo.picfitUrl.resize(image.id, Left(dw)))
155+
case _ => env.memo.picfitUrl.raw(image.id).some
156+
yield JsonOk(Json.obj("imageUrl" -> url))
155157
}
156158

157159
def imageUrl(id: ImageId, width: Int) = Auth { _ ?=> _ ?=>

app/controllers/RelayTour.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ final class RelayTour(env: Env, apiC: => Api, roundC: => RelayRound) extends Lil
5353
.map:
5454
views.relay.tour.byOwner(_, owner)
5555

56-
def apiBy(owner: UserStr, page: Int) = Open:
56+
def apiBy(owner: UserStr, page: Int) = AnonOrScoped(_.Study.Read, _.Web.Mobile):
5757
Reasonable(page, Max(20)):
5858
Found(env.user.lightUser(owner.id)): owner =>
5959
env.relay.pager

app/controllers/Study.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ final class Study(
174174
chat <- noCrawler.so(chatOf(sc.study))
175175
sVersion <- noCrawler.so(env.study.version(sc.study.id))
176176
streamers <- noCrawler.so(streamerCache.get(sc.study.id))
177-
page <- renderPage(views.study.show(sc.study, data, chat, sVersion, streamers))
177+
page <- renderPage(views.study.show(sc.study, sc.chapter, data, chat, sVersion, streamers))
178178
yield Ok(page)
179179
.withCanonical(routes.Study.chapter(sc.study.id, sc.chapter.id))
180180
.enforceCrossSiteIsolation

app/http/CtrlFilters.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,11 @@ trait CtrlFilters(using Executor) extends ControllerHelpers with ResponseBuilder
3333
if isGrantedOpt(perm) then f
3434
else negotiate(authorizationFailed, authorizationFailed)
3535

36-
def Firewall[A <: Result](a: => Fu[A])(using ctx: Context): Fu[Result] =
36+
def Firewall(a: => Fu[Result])(using ctx: Context): Fu[Result] =
3737
if env.security.firewall.accepts(ctx.req) then a
3838
else keyPages.blacklisted
3939

40-
def WithProxy(res: IsProxy ?=> Fu[Result])(using req: RequestHeader): Fu[Result] =
40+
def WithProxy[A](res: IsProxy ?=> Fu[A])(using req: RequestHeader): Fu[A] =
4141
env.security.ip2proxy.ofIp(req.ipAddress).flatMap(res(using _))
4242

4343
def NoTor(res: => Fu[Result])(using ctx: Context): Fu[Result] =

app/views/game/side.scala

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,14 @@ def meta(
102102
chess.variant.Chess960
103103
.positionNumber(initialFen | chess.format.Fen.initial)
104104
.map: number =>
105-
val url = routes.UserAnalysis
106-
.parseArg(s"chess960/${underscoreFen(initialFen | chess.format.Fen.initial)}")
107-
st.section(trans.site.chess960StartPosition(a(href := url)(number)))
105+
st.section(
106+
trans.site.chess960StartPosition(
107+
a(
108+
targetBlank,
109+
href := "https://chess960.net/wp-content/uploads/2018/02/chess960-starting-positions.pdf"
110+
)(number)
111+
)
112+
)
108113
,
109114
userTv.map: u =>
110115
st.section(cls := "game__tv"):

app/views/study.scala

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ def create(
4242

4343
def show(
4444
s: lila.study.Study,
45+
chapter: lila.study.Chapter,
4546
data: lila.study.JsonView.JsData,
4647
chatOption: Option[lila.chat.UserChat.Mine],
4748
socketVersion: SocketVersion,
@@ -85,9 +86,16 @@ def show(
8586
.flag(_.zoom)
8687
.csp(views.analyse.ui.bits.cspExternalEngine.compose(_.withPeer.withExternalAnalysisApis))
8788
.graph(
88-
title = s.name.value,
89-
url = routeUrl(routes.Study.show(s.id)),
90-
description = s"A chess study by ${titleNameOrId(s.ownerId)}"
89+
OpenGraph(
90+
title = s.name.value,
91+
url = routeUrl(routes.Study.show(s.id)),
92+
description = s"A chess study by ${titleNameOrId(s.ownerId)}",
93+
image = fenThumbnailUrl(
94+
chapter.root.fen.opening,
95+
chapter.setup.orientation.some,
96+
chapter.setup.variant
97+
).some
98+
)
9199
):
92100
frag(
93101
main(cls := "analyse"),

0 commit comments

Comments
 (0)