scalafmt 2.7.1

initial-glicko
Thibault Duplessis 2020-09-21 09:28:28 +02:00
parent c794d88af9
commit aaf88bc62c
310 changed files with 3654 additions and 3979 deletions

View File

@ -1,4 +1,4 @@
version = "2.6.3" version = "2.7.1"
align.preset = more align.preset = more
maxColumn = 110 maxColumn = 110
spaces.inImportCurlyBraces = true spaces.inImportCurlyBraces = true

View File

@ -130,10 +130,9 @@ final class Env(
Future { Future {
puzzle.daily.get puzzle.daily.get
}.flatMap(identity) }.flatMap(identity)
.withTimeoutDefault(50.millis, none) recover { .withTimeoutDefault(50.millis, none) recover { case e: Exception =>
case e: Exception => lila.log("preloader").warn("daily puzzle", e)
lila.log("preloader").warn("daily puzzle", e) none
none
} }
def scheduler = system.scheduler def scheduler = system.scheduler
@ -163,14 +162,13 @@ final class Env(
} }
} yield Bus.publish(lila.hub.actorApi.security.CloseAccount(u.id), "accountClose") } yield Bus.publish(lila.hub.actorApi.security.CloseAccount(u.id), "accountClose")
Bus.subscribeFun("garbageCollect") { Bus.subscribeFun("garbageCollect") { case lila.hub.actorApi.security.GarbageCollect(userId) =>
case lila.hub.actorApi.security.GarbageCollect(userId) => // GC can be aborted by reverting the initial SB mark
// GC can be aborted by reverting the initial SB mark user.repo.isTroll(userId) foreach { troll =>
user.repo.isTroll(userId) foreach { troll => if (troll) scheduler.scheduleOnce(1.second) {
if (troll) scheduler.scheduleOnce(1.second) { closeAccount(userId, self = false)
closeAccount(userId, self = false)
}
} }
}
} }
system.actorOf(Props(new actor.Renderer), name = config.get[String]("app.renderer.name")) system.actorOf(Props(new actor.Renderer), name = config.get[String]("app.renderer.name"))

View File

@ -44,10 +44,9 @@ final class Account(
} { username => } { username =>
env.user.repo env.user.repo
.setUsernameCased(me.id, username) inject .setUsernameCased(me.id, username) inject
Redirect(routes.User show me.username).flashSuccess recover { Redirect(routes.User show me.username).flashSuccess recover { case e =>
case e =>
BadRequest(html.account.username(me, env.user.forms.username(me))).flashFailure(e.getMessage) BadRequest(html.account.username(me, env.user.forms.username(me))).flashFailure(e.getMessage)
} }
} }
} }
@ -61,21 +60,21 @@ final class Account(
env.round.proxyRepo.urgentGames(me) zip env.round.proxyRepo.urgentGames(me) zip
env.challenge.api.countInFor.get(me.id) zip env.challenge.api.countInFor.get(me.id) zip
env.playban.api.currentBan(me.id) map { env.playban.api.currentBan(me.id) map {
case nbFollowers ~ prefs ~ povs ~ nbChallenges ~ playban => case nbFollowers ~ prefs ~ povs ~ nbChallenges ~ playban =>
Ok { Ok {
import lila.pref.JsonView._ import lila.pref.JsonView._
env.user.jsonView(me) ++ Json env.user.jsonView(me) ++ Json
.obj( .obj(
"prefs" -> prefs, "prefs" -> prefs,
"nowPlaying" -> JsArray(povs take 50 map env.api.lobbyApi.nowPlaying), "nowPlaying" -> JsArray(povs take 50 map env.api.lobbyApi.nowPlaying),
"nbFollowers" -> nbFollowers, "nbFollowers" -> nbFollowers,
"nbChallenges" -> nbChallenges "nbChallenges" -> nbChallenges
) )
.add("kid" -> me.kid) .add("kid" -> me.kid)
.add("troll" -> me.marks.troll) .add("troll" -> me.marks.troll)
.add("playban" -> playban) .add("playban" -> playban)
}.withHeaders(CACHE_CONTROL -> s"max-age=15") }.withHeaders(CACHE_CONTROL -> s"max-age=15")
} }
} }
) )
} }
@ -147,8 +146,8 @@ final class Account(
env.security.store.closeAllSessionsOf(me.id) >> env.security.store.closeAllSessionsOf(me.id) >>
env.push.webSubscriptionApi.unsubscribeByUser(me) >> env.push.webSubscriptionApi.unsubscribeByUser(me) >>
env.security.api.saveAuthentication(me.id, ctx.mobileApiVersion) map { sessionId => env.security.api.saveAuthentication(me.id, ctx.mobileApiVersion) map { sessionId =>
result.withCookies(env.lilaCookie.session(env.security.api.sessionIdKey, sessionId)) result.withCookies(env.lilaCookie.session(env.security.api.sessionIdKey, sessionId))
} }
private def emailForm(user: UserModel) = private def emailForm(user: UserModel) =
env.user.repo email user.id flatMap { env.user.repo email user.id flatMap {
@ -201,17 +200,16 @@ final class Account(
def emailConfirm(token: String) = def emailConfirm(token: String) =
Open { implicit ctx => Open { implicit ctx =>
env.security.emailChange.confirm(token) flatMap { env.security.emailChange.confirm(token) flatMap {
_ ?? { _ ?? { case (user, prevEmail) =>
case (user, prevEmail) => (prevEmail.exists(_.isNoReply) ?? env.clas.api.student.release(user)) >>
(prevEmail.exists(_.isNoReply) ?? env.clas.api.student.release(user)) >> auth.authenticateUser(
auth.authenticateUser( user,
user, result =
result = if (prevEmail.exists(_.isNoReply))
if (prevEmail.exists(_.isNoReply)) Some(_ => Redirect(routes.User.show(user.username)).flashSuccess)
Some(_ => Redirect(routes.User.show(user.username)).flashSuccess) else
else Some(_ => Redirect(routes.Account.email()).flashSuccess)
Some(_ => Redirect(routes.Account.email()).flashSuccess) )
)
} }
} }
} }
@ -354,8 +352,8 @@ final class Account(
Auth { implicit ctx => me => Auth { implicit ctx => me =>
env.security.api.dedup(me.id, ctx.req) >> env.security.api.dedup(me.id, ctx.req) >>
env.security.api.locatedOpenSessions(me.id, 50) map { sessions => env.security.api.locatedOpenSessions(me.id, 50) map { sessions =>
Ok(html.account.security(me, sessions, currentSessionId)) Ok(html.account.security(me, sessions, currentSessionId))
} }
} }
def signout(sessionId: String) = def signout(sessionId: String) =

View File

@ -50,8 +50,7 @@ final class Analyse(
initialFen, initialFen,
analysis = none, analysis = none,
PgnDump.WithFlags(clocks = false) PgnDump.WithFlags(clocks = false)
) flatMap { ) flatMap { case analysis ~ analysisInProgress ~ simul ~ chat ~ crosstable ~ bookmarked ~ pgn =>
case analysis ~ analysisInProgress ~ simul ~ chat ~ crosstable ~ bookmarked ~ pgn =>
env.api.roundApi.review( env.api.roundApi.review(
pov, pov,
lila.api.Mobile.Api.currentVersion, lila.api.Mobile.Api.currentVersion,
@ -87,7 +86,7 @@ final class Analyse(
) )
) )
} }
} }
} }
} }

View File

@ -40,12 +40,12 @@ final class Auth(
private def goodReferrer(referrer: String): Boolean = private def goodReferrer(referrer: String): Boolean =
referrer.nonEmpty && referrer.nonEmpty &&
!sillyLoginReferrers(referrer) && Try { !sillyLoginReferrers(referrer) && Try {
val url = Url.parse(referrer) val url = Url.parse(referrer)
url.schemeOption.fold(true)(scheme => scheme == "http" || scheme == "https") && url.schemeOption.fold(true)(scheme => scheme == "http" || scheme == "https") &&
url.hostOption.fold(true)(host => url.hostOption.fold(true)(host =>
s".${host.value}".endsWith(s".${AbsoluteUrl.parse(env.net.baseUrl.value).host.value}") s".${host.value}".endsWith(s".${AbsoluteUrl.parse(env.net.baseUrl.value).host.value}")
) )
}.getOrElse(false) }.getOrElse(false)
def authenticateUser(u: UserModel, result: Option[String => Result] = None)(implicit def authenticateUser(u: UserModel, result: Option[String => Result] = None)(implicit
ctx: Context ctx: Context
@ -106,39 +106,38 @@ final class Auth(
), ),
usernameOrEmail => usernameOrEmail =>
HasherRateLimit(usernameOrEmail, ctx.req) { chargeIpLimiter => HasherRateLimit(usernameOrEmail, ctx.req) { chargeIpLimiter =>
api.loadLoginForm(usernameOrEmail) flatMap { api.loadLoginForm(usernameOrEmail) flatMap { loginForm =>
loginForm => loginForm
loginForm .bindFromRequest()
.bindFromRequest() .fold(
.fold( err => {
err => { chargeIpLimiter(1)
chargeIpLimiter(1) negotiate(
negotiate( html = fuccess {
html = fuccess { err.errors match {
err.errors match { case List(FormError("", List(err), _)) if is2fa(err) => Ok(err)
case List(FormError("", List(err), _)) if is2fa(err) => Ok(err) case _ => Unauthorized(html.auth.login(err, referrer))
case _ => Unauthorized(html.auth.login(err, referrer)) }
} },
}, api = _ =>
api = _ => Unauthorized(ridiculousBackwardCompatibleJsonError(errorsAsJson(err))).fuccess
Unauthorized(ridiculousBackwardCompatibleJsonError(errorsAsJson(err))).fuccess )
) },
}, result =>
result => result.toOption match {
result.toOption match { case None => InternalServerError("Authentication error").fuccess
case None => InternalServerError("Authentication error").fuccess case Some(u) if u.disabled =>
case Some(u) if u.disabled => negotiate(
negotiate( html = redirectTo(routes.Account.reopen().url).fuccess,
html = redirectTo(routes.Account.reopen().url).fuccess, api = _ => Unauthorized(jsonError("This account is closed.")).fuccess
api = _ => Unauthorized(jsonError("This account is closed.")).fuccess )
) case Some(u) =>
case Some(u) => env.user.repo.email(u.id) foreach {
env.user.repo.email(u.id) foreach { _ foreach { garbageCollect(u, _) }
_ foreach { garbageCollect(u, _) } }
} authenticateUser(u, Some(redirectTo))
authenticateUser(u, Some(redirectTo)) }
} )
)
} }
}(rateLimitedFu) }(rateLimitedFu)
) )
@ -247,22 +246,21 @@ final class Auth(
err => BadRequest(html.auth.checkYourEmail(userEmail.some, err.some)).fuccess, err => BadRequest(html.auth.checkYourEmail(userEmail.some, err.some)).fuccess,
email => email =>
env.user.repo.named(userEmail.username) flatMap { env.user.repo.named(userEmail.username) flatMap {
_.fold(Redirect(routes.Auth.signup()).fuccess) { _.fold(Redirect(routes.Auth.signup()).fuccess) { user =>
user => env.user.repo.mustConfirmEmail(user.id) flatMap {
env.user.repo.mustConfirmEmail(user.id) flatMap { case false => Redirect(routes.Auth.login()).fuccess
case false => Redirect(routes.Auth.login()).fuccess case _ =>
case _ => val newUserEmail = userEmail.copy(email = EmailAddress(email))
val newUserEmail = userEmail.copy(email = EmailAddress(email)) EmailConfirmRateLimit(newUserEmail, ctx.req) {
EmailConfirmRateLimit(newUserEmail, ctx.req) { lila.mon.email.send.fix.increment()
lila.mon.email.send.fix.increment() env.user.repo.setEmail(user.id, newUserEmail.email) >>
env.user.repo.setEmail(user.id, newUserEmail.email) >> env.security.emailConfirm.send(user, newUserEmail.email) inject {
env.security.emailConfirm.send(user, newUserEmail.email) inject {
Redirect(routes.Auth.checkYourEmail()) withCookies Redirect(routes.Auth.checkYourEmail()) withCookies
lila.security.EmailConfirm.cookie lila.security.EmailConfirm.cookie
.make(env.lilaCookie, user, newUserEmail.email)(ctx.req) .make(env.lilaCookie, user, newUserEmail.email)(ctx.req)
} }
}(rateLimitedFu) }(rateLimitedFu)
} }
} }
} }
) )

View File

@ -239,43 +239,42 @@ final class Challenge(
val cost = if (me.isApiHog) 0 else 1 val cost = if (me.isApiHog) 0 else 1
ChallengeIpRateLimit(HTTPRequest lastRemoteAddress req, cost = cost) { ChallengeIpRateLimit(HTTPRequest lastRemoteAddress req, cost = cost) {
ChallengeUserRateLimit(me.id, cost = cost) { ChallengeUserRateLimit(me.id, cost = cost) {
env.user.repo enabledById userId.toLowerCase flatMap { env.user.repo enabledById userId.toLowerCase flatMap { destUser =>
destUser => import lila.challenge.Challenge._
import lila.challenge.Challenge._ val timeControl = config.clock map {
val timeControl = config.clock map { TimeControl.Clock.apply
TimeControl.Clock.apply } orElse config.days.map {
} orElse config.days.map { TimeControl.Correspondence.apply
TimeControl.Correspondence.apply } getOrElse TimeControl.Unlimited
} getOrElse TimeControl.Unlimited val challenge = lila.challenge.Challenge
val challenge = lila.challenge.Challenge .make(
.make( variant = config.variant,
variant = config.variant, initialFen = config.position,
initialFen = config.position, timeControl = timeControl,
timeControl = timeControl, mode = config.mode,
mode = config.mode, color = config.color.name,
color = config.color.name, challenger = ChallengeModel.toRegistered(config.variant, timeControl)(me),
challenger = ChallengeModel.toRegistered(config.variant, timeControl)(me), destUser = destUser,
destUser = destUser, rematchOf = none
rematchOf = none )
) (destUser, config.acceptByToken) match {
(destUser, config.acceptByToken) match { case (Some(dest), Some(strToken)) => apiChallengeAccept(dest, challenge, strToken)
case (Some(dest), Some(strToken)) => apiChallengeAccept(dest, challenge, strToken) case _ =>
case _ => destUser ?? { env.challenge.granter(me.some, _, config.perfType) } flatMap {
destUser ?? { env.challenge.granter(me.some, _, config.perfType) } flatMap { case Some(denied) =>
case Some(denied) => BadRequest(jsonError(lila.challenge.ChallengeDenied.translated(denied))).fuccess
BadRequest(jsonError(lila.challenge.ChallengeDenied.translated(denied))).fuccess case _ =>
case _ => (env.challenge.api create challenge) map {
(env.challenge.api create challenge) map { case true =>
case true => JsonOk(
JsonOk( env.challenge.jsonView
env.challenge.jsonView .show(challenge, SocketVersion(0), lila.challenge.Direction.Out.some)
.show(challenge, SocketVersion(0), lila.challenge.Direction.Out.some) )
) case false =>
case false => BadRequest(jsonError("Challenge not created"))
BadRequest(jsonError("Challenge not created")) }
} } map (_ as JSON)
} map (_ as JSON) }
}
} }
}(rateLimitedFu) }(rateLimitedFu)
}(rateLimitedFu) }(rateLimitedFu)

View File

@ -29,9 +29,9 @@ final class Clas(
case _ => case _ =>
env.clas.api.student.clasIdsOfUser(me.id) flatMap env.clas.api.student.clasIdsOfUser(me.id) flatMap
env.clas.api.clas.byIds map { env.clas.api.clas.byIds map {
case List(single) => Redirect(routes.Clas.show(single.id.value)) case List(single) => Redirect(routes.Clas.show(single.id.value))
case many => Ok(views.html.clas.clas.studentIndex(many)) case many => Ok(views.html.clas.clas.studentIndex(many))
} }
} }
} }
} }
@ -204,10 +204,9 @@ final class Clas(
val studentIds = students.map(_.user.id) val studentIds = students.map(_.user.id)
env.learn.api.completionPercent(studentIds) zip env.learn.api.completionPercent(studentIds) zip
env.practice.api.progress.completionPercent(studentIds) zip env.practice.api.progress.completionPercent(studentIds) zip
env.coordinate.api.bestScores(studentIds) map { env.coordinate.api.bestScores(studentIds) map { case basic ~ practice ~ coords =>
case basic ~ practice ~ coords =>
views.html.clas.teacherDashboard.learn(clas, students, basic, practice, coords) views.html.clas.teacherDashboard.learn(clas, students, basic, practice, coords)
} }
} }
} }
} }
@ -321,11 +320,10 @@ final class Clas(
case _ => none case _ => none
} }
} }
.map { .map { case (u, p) =>
case (u, p) => env.clas.api.student
env.clas.api.student .get(clas, u)
.get(clas, u) .map2(lila.clas.Student.WithPassword(_, lila.user.User.ClearPassword(p)))
.map2(lila.clas.Student.WithPassword(_, lila.user.User.ClearPassword(p)))
} }
.sequenceFu .sequenceFu
.map(_.flatten) .map(_.flatten)
@ -389,21 +387,19 @@ final class Clas(
}, },
data => data =>
env.user.repo named data.username flatMap { env.user.repo named data.username flatMap {
_ ?? { _ ?? { user =>
user => import lila.clas.ClasInvite.{ Feedback => F }
import lila.clas.ClasInvite.{ Feedback => F } env.clas.api.invite.create(clas, user, data.realName, me) map { feedback =>
env.clas.api.invite.create(clas, user, data.realName, me) map { Redirect(routes.Clas.studentForm(clas.id.value)).flashing {
feedback => feedback match {
Redirect(routes.Clas.studentForm(clas.id.value)).flashing { case F.Already => "success" -> s"${user.username} is now a student of the class"
feedback match { case F.Invited => "success" -> s"An invitation has been sent to ${user.username}"
case F.Already => "success" -> s"${user.username} is now a student of the class" case F.Found => "warning" -> s"${user.username} already has a pending invitation"
case F.Invited => "success" -> s"An invitation has been sent to ${user.username}" case F.CantMsgKid(url) =>
case F.Found => "warning" -> s"${user.username} already has a pending invitation" "warning" -> s"${user.username} is a kid account and can't receive your message. You must give them the invitation URL manually: $url"
case F.CantMsgKid(url) => }
"warning" -> s"${user.username} is a kid account and can't receive your message. You must give them the invitation URL manually: $url"
}
}
} }
}
} }
} }
) )
@ -474,9 +470,9 @@ final class Clas(
WithStudent(clas, username) { s => WithStudent(clas, username) { s =>
env.security.store.closeAllSessionsOf(s.user.id) >> env.security.store.closeAllSessionsOf(s.user.id) >>
env.clas.api.student.resetPassword(s.student) map { password => env.clas.api.student.resetPassword(s.student) map { password =>
Redirect(routes.Clas.studentShow(clas.id.value, username)) Redirect(routes.Clas.studentShow(clas.id.value, username))
.flashing("password" -> password.value) .flashing("password" -> password.value)
} }
} }
} }
} }
@ -529,8 +525,8 @@ final class Clas(
def invitation(id: String) = def invitation(id: String) =
Auth { implicit ctx => me => Auth { implicit ctx => me =>
OptionOk(env.clas.api.invite.view(lila.clas.ClasInvite.Id(id), me)) { OptionOk(env.clas.api.invite.view(lila.clas.ClasInvite.Id(id), me)) { case (invite -> clas) =>
case (invite -> clas) => views.html.clas.invite.show(clas, invite) views.html.clas.invite.show(clas, invite)
} }
} }

View File

@ -127,8 +127,8 @@ final class Coach(env: Env) extends LilaController(env) {
OptionFuResult(api findOrInit me) { c => OptionFuResult(api findOrInit me) { c =>
ctx.body.body.file("picture") match { ctx.body.body.file("picture") match {
case Some(pic) => case Some(pic) =>
api.uploadPicture(c, pic) recover { api.uploadPicture(c, pic) recover { case e: lila.base.LilaException =>
case e: lila.base.LilaException => BadRequest(html.coach.picture(c, e.message.some)) BadRequest(html.coach.picture(c, e.message.some))
} inject Redirect(routes.Coach.edit()) } inject Redirect(routes.Coach.edit())
case None => fuccess(Redirect(routes.Coach.edit())) case None => fuccess(Redirect(routes.Coach.edit()))
} }

View File

@ -28,12 +28,11 @@ final class Export(env: Env) extends LilaController(env) {
Open { implicit ctx => Open { implicit ctx =>
OnlyHumansAndFacebookOrTwitter { OnlyHumansAndFacebookOrTwitter {
ExportGifRateLimitGlobal("-", msg = HTTPRequest.lastRemoteAddress(ctx.req).value) { ExportGifRateLimitGlobal("-", msg = HTTPRequest.lastRemoteAddress(ctx.req).value) {
OptionFuResult(env.game.gameRepo gameWithInitialFen id) { OptionFuResult(env.game.gameRepo gameWithInitialFen id) { case (game, initialFen) =>
case (game, initialFen) => val pov = Pov(game, Color(color) | Color.white)
val pov = Pov(game, Color(color) | Color.white) env.game.gifExport.fromPov(pov, initialFen) map
env.game.gifExport.fromPov(pov, initialFen) map stream("image/gif") map
stream("image/gif") map gameImageCacheSeconds(game)
gameImageCacheSeconds(game)
} }
}(rateLimitedFu) }(rateLimitedFu)
} }

View File

@ -21,13 +21,12 @@ final class ForumCateg(env: Env) extends LilaController(env) with ForumControlle
Open { implicit ctx => Open { implicit ctx =>
NotForKids { NotForKids {
Reasonable(page, 50, errorPage = notFound) { Reasonable(page, 50, errorPage = notFound) {
OptionFuOk(categApi.show(slug, page, ctx.me)) { OptionFuOk(categApi.show(slug, page, ctx.me)) { case (categ, topics) =>
case (categ, topics) => for {
for { canWrite <- isGrantedWrite(categ.slug)
canWrite <- isGrantedWrite(categ.slug) stickyPosts <- (page == 1) ?? env.forum.topicApi.getSticky(categ, ctx.me)
stickyPosts <- (page == 1) ?? env.forum.topicApi.getSticky(categ, ctx.me) _ <- env.user.lightUserApi preloadMany topics.currentPageResults.flatMap(_.lastPostUserId)
_ <- env.user.lightUserApi preloadMany topics.currentPageResults.flatMap(_.lastPostUserId) } yield html.forum.categ.show(categ, topics, canWrite, stickyPosts)
} yield html.forum.categ.show(categ, topics, canWrite, stickyPosts)
} }
} }
} }

View File

@ -23,31 +23,30 @@ final class ForumPost(env: Env) extends LilaController(env) with ForumController
NoBot { NoBot {
CategGrantWrite(categSlug) { CategGrantWrite(categSlug) {
implicit val req = ctx.body implicit val req = ctx.body
OptionFuResult(topicApi.show(categSlug, slug, page, ctx.me)) { OptionFuResult(topicApi.show(categSlug, slug, page, ctx.me)) { case (categ, topic, posts) =>
case (categ, topic, posts) => if (topic.closed) fuccess(BadRequest("This topic is closed"))
if (topic.closed) fuccess(BadRequest("This topic is closed")) else if (topic.isOld) fuccess(BadRequest("This topic is archived"))
else if (topic.isOld) fuccess(BadRequest("This topic is archived")) else
else forms
forms .post(me)
.post(me) .bindFromRequest()
.bindFromRequest() .fold(
.fold( err =>
err => for {
for { captcha <- forms.anyCaptcha
captcha <- forms.anyCaptcha unsub <- env.timeline.status(s"forum:${topic.id}")(me.id)
unsub <- env.timeline.status(s"forum:${topic.id}")(me.id) canModCateg <- isGrantedMod(categ.slug)
canModCateg <- isGrantedMod(categ.slug) } yield BadRequest(
} yield BadRequest( html.forum.topic
html.forum.topic .show(categ, topic, posts, Some(err -> captcha), unsub, canModCateg = canModCateg)
.show(categ, topic, posts, Some(err -> captcha), unsub, canModCateg = canModCateg) ),
), data =>
data => CreateRateLimit(HTTPRequest lastRemoteAddress ctx.req) {
CreateRateLimit(HTTPRequest lastRemoteAddress ctx.req) { postApi.makePost(categ, topic, data, me) map { post =>
postApi.makePost(categ, topic, data, me) map { post => Redirect(routes.ForumPost.redirect(post.id))
Redirect(routes.ForumPost.redirect(post.id)) }
} }(rateLimitedFu)
}(rateLimitedFu) )
)
} }
} }
} }
@ -88,9 +87,8 @@ final class ForumPost(env: Env) extends LilaController(env) with ForumController
def redirect(id: String) = def redirect(id: String) =
Open { implicit ctx => Open { implicit ctx =>
OptionResult(postApi.urlData(id, ctx.me)) { OptionResult(postApi.urlData(id, ctx.me)) { case lila.forum.PostUrlData(categ, topic, page, number) =>
case lila.forum.PostUrlData(categ, topic, page, number) => Redirect(routes.ForumTopic.show(categ, topic, page).url + "#" + number)
Redirect(routes.ForumTopic.show(categ, topic, page).url + "#" + number)
} }
} }
} }

View File

@ -52,17 +52,16 @@ final class ForumTopic(env: Env) extends LilaController(env) with ForumControlle
def show(categSlug: String, slug: String, page: Int) = def show(categSlug: String, slug: String, page: Int) =
Open { implicit ctx => Open { implicit ctx =>
NotForKids { NotForKids {
OptionFuOk(topicApi.show(categSlug, slug, page, ctx.me)) { OptionFuOk(topicApi.show(categSlug, slug, page, ctx.me)) { case (categ, topic, posts) =>
case (categ, topic, posts) => for {
for { unsub <- ctx.userId ?? env.timeline.status(s"forum:${topic.id}")
unsub <- ctx.userId ?? env.timeline.status(s"forum:${topic.id}") canWrite <- isGrantedWrite(categSlug)
canWrite <- isGrantedWrite(categSlug) form <- ctx.me.ifTrue(
form <- ctx.me.ifTrue( !posts.hasNextPage && canWrite && topic.open && !topic.isOld
!posts.hasNextPage && canWrite && topic.open && !topic.isOld ) ?? { me => forms.postWithCaptcha(me) map some }
) ?? { me => forms.postWithCaptcha(me) map some } canModCateg <- isGrantedMod(categ.slug)
canModCateg <- isGrantedMod(categ.slug) _ <- env.user.lightUserApi preloadMany posts.currentPageResults.flatMap(_.userId)
_ <- env.user.lightUserApi preloadMany posts.currentPageResults.flatMap(_.userId) } yield html.forum.topic.show(categ, topic, posts, form, unsub, canModCateg = canModCateg)
} yield html.forum.topic.show(categ, topic, posts, form, unsub, canModCateg = canModCateg)
} }
} }
} }
@ -70,30 +69,27 @@ final class ForumTopic(env: Env) extends LilaController(env) with ForumControlle
def close(categSlug: String, slug: String) = def close(categSlug: String, slug: String) =
Auth { implicit ctx => me => Auth { implicit ctx => me =>
CategGrantMod(categSlug) { CategGrantMod(categSlug) {
OptionFuRedirect(topicApi.show(categSlug, slug, 1, ctx.me)) { OptionFuRedirect(topicApi.show(categSlug, slug, 1, ctx.me)) { case (categ, topic, pag) =>
case (categ, topic, pag) => topicApi.toggleClose(categ, topic, me) inject
topicApi.toggleClose(categ, topic, me) inject routes.ForumTopic.show(categSlug, slug, pag.nbPages)
routes.ForumTopic.show(categSlug, slug, pag.nbPages)
} }
} }
} }
def hide(categSlug: String, slug: String) = def hide(categSlug: String, slug: String) =
Secure(_.ModerateForum) { implicit ctx => me => Secure(_.ModerateForum) { implicit ctx => me =>
OptionFuRedirect(topicApi.show(categSlug, slug, 1, ctx.me)) { OptionFuRedirect(topicApi.show(categSlug, slug, 1, ctx.me)) { case (categ, topic, pag) =>
case (categ, topic, pag) => topicApi.toggleHide(categ, topic, me) inject
topicApi.toggleHide(categ, topic, me) inject routes.ForumTopic.show(categSlug, slug, pag.nbPages)
routes.ForumTopic.show(categSlug, slug, pag.nbPages)
} }
} }
def sticky(categSlug: String, slug: String) = def sticky(categSlug: String, slug: String) =
Auth { implicit ctx => me => Auth { implicit ctx => me =>
CategGrantMod(categSlug) { CategGrantMod(categSlug) {
OptionFuRedirect(topicApi.show(categSlug, slug, 1, ctx.me)) { OptionFuRedirect(topicApi.show(categSlug, slug, 1, ctx.me)) { case (categ, topic, pag) =>
case (categ, topic, pag) => topicApi.toggleSticky(categ, topic, me) inject
topicApi.toggleSticky(categ, topic, me) inject routes.ForumTopic.show(categSlug, slug, pag.nbPages)
routes.ForumTopic.show(categSlug, slug, pag.nbPages)
} }
} }
} }

View File

@ -90,12 +90,11 @@ final class Importer(env: Env) extends LilaController(env) {
): Fu[Option[lila.game.Game]] = ): Fu[Option[lila.game.Game]] =
env.importer.importer(data, me.map(_.id)) flatMap { game => env.importer.importer(data, me.map(_.id)) flatMap { game =>
me.map(_.id).??(env.game.cached.clearNbImportedByCache) inject game.some me.map(_.id).??(env.game.cached.clearNbImportedByCache) inject game.some
} recover { } recover { case e: Exception =>
case e: Exception => lila
lila .log("importer")
.log("importer") .warn(s"Imported game validates but can't be replayed:\n${data.pgn}", e)
.warn(s"Imported game validates but can't be replayed:\n${data.pgn}", e) none
none
} }
def masterGame(id: String, orientation: String) = def masterGame(id: String, orientation: String) =

View File

@ -38,11 +38,10 @@ final class Learn(env: Env) extends LilaController(env) {
.bindFromRequest() .bindFromRequest()
.fold( .fold(
_ => BadRequest.fuccess, _ => BadRequest.fuccess,
{ { case (stage, level, s) =>
case (stage, level, s) => val score = lila.learn.StageProgress.Score(s)
val score = lila.learn.StageProgress.Score(s) env.learn.api.setScore(me, stage, level, score) >>
env.learn.api.setScore(me, stage, level, score) >> env.activity.write.learn(me.id, stage) inject Ok(Json.obj("ok" -> true))
env.activity.write.learn(me.id, stage) inject Ok(Json.obj("ok" -> true))
} }
) )
} }

View File

@ -483,19 +483,17 @@ abstract private[controllers] class LilaController(val env: Env)
.dmap(_.withHeaders("Vary" -> "Accept")) .dmap(_.withHeaders("Vary" -> "Accept"))
protected def reqToCtx(req: RequestHeader): Fu[HeaderContext] = protected def reqToCtx(req: RequestHeader): Fu[HeaderContext] =
restoreUser(req) flatMap { restoreUser(req) flatMap { case (d, impersonatedBy) =>
case (d, impersonatedBy) => val lang = getAndSaveLang(req, d.map(_.user))
val lang = getAndSaveLang(req, d.map(_.user)) val ctx = UserContext(req, d.map(_.user), impersonatedBy, lang)
val ctx = UserContext(req, d.map(_.user), impersonatedBy, lang) pageDataBuilder(ctx, d.exists(_.hasFingerPrint)) dmap { Context(ctx, _) }
pageDataBuilder(ctx, d.exists(_.hasFingerPrint)) dmap { Context(ctx, _) }
} }
protected def reqToCtx[A](req: Request[A]): Fu[BodyContext[A]] = protected def reqToCtx[A](req: Request[A]): Fu[BodyContext[A]] =
restoreUser(req) flatMap { restoreUser(req) flatMap { case (d, impersonatedBy) =>
case (d, impersonatedBy) => val lang = getAndSaveLang(req, d.map(_.user))
val lang = getAndSaveLang(req, d.map(_.user)) val ctx = UserContext(req, d.map(_.user), impersonatedBy, lang)
val ctx = UserContext(req, d.map(_.user), impersonatedBy, lang) pageDataBuilder(ctx, d.exists(_.hasFingerPrint)) dmap { Context(ctx, _) }
pageDataBuilder(ctx, d.exists(_.hasFingerPrint)) dmap { Context(ctx, _) }
} }
private def getAndSaveLang(req: RequestHeader, user: Option[UserModel]): Lang = { private def getAndSaveLang(req: RequestHeader, user: Option[UserModel]): Lang = {
@ -510,33 +508,33 @@ abstract private[controllers] class LilaController(val env: Env)
ctx.me.fold(fuccess(PageData.anon(ctx.req, nonce, blindMode(ctx)))) { me => ctx.me.fold(fuccess(PageData.anon(ctx.req, nonce, blindMode(ctx)))) { me =>
env.pref.api.getPref(me, ctx.req) zip env.pref.api.getPref(me, ctx.req) zip
(if (isGranted(_.Teacher, me)) fuccess(true) else env.clas.api.student.isStudent(me.id)) zip { (if (isGranted(_.Teacher, me)) fuccess(true) else env.clas.api.student.isStudent(me.id)) zip {
if (isPage) { if (isPage) {
env.user.lightUserApi preloadUser me env.user.lightUserApi preloadUser me
env.team.api.nbRequests(me.id) zip env.team.api.nbRequests(me.id) zip
env.challenge.api.countInFor.get(me.id) zip env.challenge.api.countInFor.get(me.id) zip
env.notifyM.api.unreadCount(Notifies(me.id)).dmap(_.value) zip env.notifyM.api.unreadCount(Notifies(me.id)).dmap(_.value) zip
env.mod.inquiryApi.forMod(me) env.mod.inquiryApi.forMod(me)
} else } else
fuccess { fuccess {
(((0, 0), 0), none) (((0, 0), 0), none)
} }
} map { } map {
case ( case (
(pref, hasClas), (pref, hasClas),
teamNbRequests ~ nbChallenges ~ nbNotifications ~ inquiry teamNbRequests ~ nbChallenges ~ nbNotifications ~ inquiry
) => ) =>
PageData( PageData(
teamNbRequests, teamNbRequests,
nbChallenges, nbChallenges,
nbNotifications, nbNotifications,
pref, pref,
blindMode = blindMode(ctx), blindMode = blindMode(ctx),
hasFingerprint = hasFingerPrint, hasFingerprint = hasFingerPrint,
hasClas = hasClas, hasClas = hasClas,
inquiry = inquiry, inquiry = inquiry,
nonce = nonce nonce = nonce
) )
} }
} }
} }

View File

@ -33,14 +33,13 @@ final class Main(
.bindFromRequest() .bindFromRequest()
.fold( .fold(
_ => BadRequest, _ => BadRequest,
{ { case (enable, redirect) =>
case (enable, redirect) => Redirect(redirect) withCookies env.lilaCookie.cookie(
Redirect(redirect) withCookies env.lilaCookie.cookie( env.api.config.accessibility.blindCookieName,
env.api.config.accessibility.blindCookieName, if (enable == "0") "" else env.api.config.accessibility.hash,
if (enable == "0") "" else env.api.config.accessibility.hash, maxAge = env.api.config.accessibility.blindCookieMaxAge.toSeconds.toInt.some,
maxAge = env.api.config.accessibility.blindCookieMaxAge.toSeconds.toInt.some, httpOnly = true.some
httpOnly = true.some )
)
} }
) )
} }
@ -50,8 +49,8 @@ final class Main(
def captchaCheck(id: String) = def captchaCheck(id: String) =
Open { implicit ctx => Open { implicit ctx =>
env.hub.captcher.actor ? ValidCaptcha(id, ~get("solution")) map { env.hub.captcher.actor ? ValidCaptcha(id, ~get("solution")) map { case valid: Boolean =>
case valid: Boolean => Ok(if (valid) 1 else 0) Ok(if (valid) 1 else 0)
} }
} }
@ -74,8 +73,8 @@ final class Main(
def mobile = def mobile =
Open { implicit ctx => Open { implicit ctx =>
pageHit pageHit
OptionOk(prismicC getBookmark "mobile-apk") { OptionOk(prismicC getBookmark "mobile-apk") { case (doc, resolver) =>
case (doc, resolver) => html.mobile(doc, resolver) html.mobile(doc, resolver)
} }
} }

View File

@ -36,8 +36,8 @@ final class Mod(
} yield (inquiry, sus).some } yield (inquiry, sus).some
} }
}(ctx => }(ctx =>
me => { me => { case (inquiry, suspect) =>
case (inquiry, suspect) => reportC.onInquiryClose(inquiry, me, suspect.some)(ctx) reportC.onInquiryClose(inquiry, me, suspect.some)(ctx)
} }
) )
@ -50,16 +50,15 @@ final class Mod(
} yield (inquiry, sus).some } yield (inquiry, sus).some
} }
}(ctx => }(ctx =>
me => { me => { case (inquiry, suspect) =>
case (inquiry, suspect) => reportC.onInquiryClose(inquiry, me, suspect.some)(ctx) reportC.onInquiryClose(inquiry, me, suspect.some)(ctx)
} }
) )
def publicChat = def publicChat =
Secure(_.ChatTimeout) { implicit ctx => _ => Secure(_.ChatTimeout) { implicit ctx => _ =>
env.mod.publicChat.all map { env.mod.publicChat.all map { case (tournamentsAndChats, simulsAndChats) =>
case (tournamentsAndChats, simulsAndChats) => Ok(html.mod.publicChat(tournamentsAndChats, simulsAndChats))
Ok(html.mod.publicChat(tournamentsAndChats, simulsAndChats))
} }
} }
@ -72,8 +71,8 @@ final class Mod(
} yield (inquiry, suspect).some } yield (inquiry, suspect).some
} }
}(ctx => }(ctx =>
me => { me => { case (inquiry, suspect) =>
case (inquiry, suspect) => reportC.onInquiryClose(inquiry, me, suspect.some)(ctx) reportC.onInquiryClose(inquiry, me, suspect.some)(ctx)
} }
) )
@ -86,8 +85,8 @@ final class Mod(
} yield (inquiry, suspect).some } yield (inquiry, suspect).some
} }
}(ctx => }(ctx =>
me => { me => { case (inquiry, suspect) =>
case (inquiry, suspect) => reportC.onInquiryClose(inquiry, me, suspect.some)(ctx) reportC.onInquiryClose(inquiry, me, suspect.some)(ctx)
} }
) )
@ -104,8 +103,8 @@ final class Mod(
} }
} }
}(ctx => }(ctx =>
me => { me => { case (inquiry, suspect) =>
case (inquiry, suspect) => reportC.onInquiryClose(inquiry, me, suspect.some)(ctx) reportC.onInquiryClose(inquiry, me, suspect.some)(ctx)
} }
) )
@ -245,25 +244,29 @@ final class Mod(
env.report.api.inquiries env.report.api.inquiries
.ofModId(me.id) .ofModId(me.id)
.mon(_.mod.comm.segment("inquiries")) map { .mon(_.mod.comm.segment("inquiries")) map {
case chats ~ convos ~ publicLines ~ notes ~ history ~ inquiry => case chats ~ convos ~ publicLines ~ notes ~ history ~ inquiry =>
if (priv) { if (priv) {
if (!inquiry.??(_.isRecentCommOf(Suspect(user)))) if (!inquiry.??(_.isRecentCommOf(Suspect(user))))
env.slack.api.commlog(mod = me, user = user, inquiry.map(_.oldestAtom.by.value)) env.slack.api.commlog(mod = me, user = user, inquiry.map(_.oldestAtom.by.value))
if (isGranted(_.MonitoredMod)) if (isGranted(_.MonitoredMod))
env.slack.api.monitorMod(me.id, "eyes", s"checked out @${user.username}'s private comms") env.slack.api.monitorMod(
} me.id,
html.mod.communication( "eyes",
user, s"checked out @${user.username}'s private comms"
(povs zip chats) collect { )
case (p, Some(c)) if c.nonEmpty => p -> c }
} take 15, html.mod.communication(
convos, user,
publicLines, (povs zip chats) collect {
notes.filter(_.from != "irwin"), case (p, Some(c)) if c.nonEmpty => p -> c
history, } take 15,
priv convos,
) publicLines,
} notes.filter(_.from != "irwin"),
history,
priv
)
}
} }
} }
} }
@ -305,9 +308,9 @@ final class Mod(
def gamify = def gamify =
Secure(_.SeeReport) { implicit ctx => _ => Secure(_.SeeReport) { implicit ctx => _ =>
env.mod.gamify.leaderboards zip env.mod.gamify.leaderboards zip
env.mod.gamify.history(orCompute = true) map { env.mod.gamify.history(orCompute = true) map { case (leaderboards, history) =>
case (leaderboards, history) => Ok(html.mod.gamify.index(leaderboards, history)) Ok(html.mod.gamify.index(leaderboards, history))
} }
} }
def gamifyPeriod(periodStr: String) = def gamifyPeriod(periodStr: String) =
Secure(_.SeeReport) { implicit ctx => _ => Secure(_.SeeReport) { implicit ctx => _ =>
@ -430,8 +433,8 @@ final class Mod(
modApi.setEmail(me.id, user.id, setEmail) modApi.setEmail(me.id, user.id, setEmail)
} >> } >>
env.user.repo.email(user.id) map { email => env.user.repo.email(user.id) map { email =>
Ok(html.mod.emailConfirm("", user.some, email)).some Ok(html.mod.emailConfirm("", user.some, email)).some
} }
case _ => fuccess(none) case _ => fuccess(none)
} }
email.?? { em => email.?? { em =>

View File

@ -13,10 +13,9 @@ final class Msg(
def home = def home =
Auth { implicit ctx => me => Auth { implicit ctx => me =>
negotiate( negotiate(
html = html = inboxJson(me) map { json =>
inboxJson(me) map { json => Ok(views.html.msg.home(json))
Ok(views.html.msg.home(json)) },
},
api = v => api = v =>
{ {
if (v >= 5) inboxJson(me) if (v >= 5) inboxJson(me)
@ -38,10 +37,9 @@ final class Msg(
case Some(c) => case Some(c) =>
def newJson = inboxJson(me).map { _ + ("convo" -> env.msg.json.convo(c)) } def newJson = inboxJson(me).map { _ + ("convo" -> env.msg.json.convo(c)) }
negotiate( negotiate(
html = html = newJson map { json =>
newJson map { json => Ok(views.html.msg.home(json))
Ok(views.html.msg.home(json)) },
},
api = v => api = v =>
{ {
if (v >= 5) newJson if (v >= 5) newJson

View File

@ -18,8 +18,8 @@ final class Page(
private def helpBookmark(name: String) = private def helpBookmark(name: String) =
Open { implicit ctx => Open { implicit ctx =>
pageHit pageHit
OptionOk(prismicC getBookmark name) { OptionOk(prismicC getBookmark name) { case (doc, resolver) =>
case (doc, resolver) => views.html.site.help.page(name, doc, resolver) views.html.site.help.page(name, doc, resolver)
} }
} }
@ -28,16 +28,16 @@ final class Page(
private def bookmark(name: String) = private def bookmark(name: String) =
Open { implicit ctx => Open { implicit ctx =>
pageHit pageHit
OptionOk(prismicC getBookmark name) { OptionOk(prismicC getBookmark name) { case (doc, resolver) =>
case (doc, resolver) => views.html.site.page(doc, resolver) views.html.site.page(doc, resolver)
} }
} }
def source = def source =
Open { implicit ctx => Open { implicit ctx =>
pageHit pageHit
OptionOk(prismicC getBookmark "source") { OptionOk(prismicC getBookmark "source") { case (doc, resolver) =>
case (doc, resolver) => views.html.site.help.source(doc, resolver) views.html.site.help.source(doc, resolver)
} }
} }
@ -45,8 +45,8 @@ final class Page(
Open { implicit ctx => Open { implicit ctx =>
import play.api.libs.json._ import play.api.libs.json._
negotiate( negotiate(
html = OptionOk(prismicC getBookmark "variant") { html = OptionOk(prismicC getBookmark "variant") { case (doc, resolver) =>
case (doc, resolver) => views.html.site.variant.home(doc, resolver) views.html.site.variant.home(doc, resolver)
}, },
api = _ => api = _ =>
Ok(JsArray(chess.variant.Variant.all.map { v => Ok(JsArray(chess.variant.Variant.all.map { v =>
@ -64,8 +64,8 @@ final class Page(
(for { (for {
variant <- chess.variant.Variant.byKey get key variant <- chess.variant.Variant.byKey get key
perfType <- lila.rating.PerfType byVariant variant perfType <- lila.rating.PerfType byVariant variant
} yield OptionOk(prismicC getVariant variant) { } yield OptionOk(prismicC getVariant variant) { case (doc, resolver) =>
case (doc, resolver) => views.html.site.variant.show(doc, resolver, variant, perfType) views.html.site.variant.show(doc, resolver, variant, perfType)
}) | notFound }) | notFound
} }
} }

View File

@ -135,10 +135,9 @@ final class Plan(env: Env)(implicit system: akka.actor.ActorSystem) extends Lila
} }
def badStripeSession[A: Writes](err: A) = BadRequest(jsonError(err)) def badStripeSession[A: Writes](err: A) = BadRequest(jsonError(err))
def badStripeApiCall: PartialFunction[Throwable, Result] = { def badStripeApiCall: PartialFunction[Throwable, Result] = { case e: StripeException =>
case e: StripeException => logger.error("Plan.stripeCheckout", e)
logger.error("Plan.stripeCheckout", e) badStripeSession("Stripe API call failed")
badStripeSession("Stripe API call failed")
} }
private def createStripeSession(checkout: Checkout, customerId: CustomerId) = private def createStripeSession(checkout: Checkout, customerId: CustomerId) =

View File

@ -40,9 +40,9 @@ final class PlayApi(
env.user.repo.setBot(me) >> env.user.repo.setBot(me) >>
env.pref.api.setBot(me) >>- env.pref.api.setBot(me) >>-
env.user.lightUserApi.invalidate(me.id) pipe env.user.lightUserApi.invalidate(me.id) pipe
toResult recover { toResult recover { case lila.base.LilaInvalid(msg) =>
case lila.base.LilaInvalid(msg) => BadRequest(jsonError(msg)) BadRequest(jsonError(msg))
} }
} }
case _ => impl.command(me, cmd)(WithPovAsBot) case _ => impl.command(me, cmd)(WithPovAsBot)
} }
@ -111,8 +111,8 @@ final class PlayApi(
private def toResult(f: Funit): Fu[Result] = catchClientError(f inject jsonOkResult) private def toResult(f: Funit): Fu[Result] = catchClientError(f inject jsonOkResult)
private def catchClientError(f: Fu[Result]): Fu[Result] = private def catchClientError(f: Fu[Result]): Fu[Result] =
f recover { f recover { case e: lila.round.BenignError =>
case e: lila.round.BenignError => BadRequest(jsonError(e.getMessage)) BadRequest(jsonError(e.getMessage))
} }
private def WithPovAsBot(anyId: String, me: lila.user.User)(f: Pov => Fu[Result]) = private def WithPovAsBot(anyId: String, me: lila.user.User)(f: Pov => Fu[Result]) =

View File

@ -64,35 +64,33 @@ final class Practice(
} }
private def showUserPractice(us: lila.practice.UserStudy)(implicit ctx: Context) = private def showUserPractice(us: lila.practice.UserStudy)(implicit ctx: Context) =
analysisJson(us) map { analysisJson(us) map { case (analysisJson, studyJson) =>
case (analysisJson, studyJson) => NoCache(
NoCache( EnableSharedArrayBuffer(
EnableSharedArrayBuffer( Ok(
Ok( html.practice.show(
html.practice.show( us,
us, lila.practice.JsonView.JsData(
lila.practice.JsonView.JsData( study = studyJson,
study = studyJson, analysis = analysisJson,
analysis = analysisJson, practice = lila.practice.JsonView(us)
practice = lila.practice.JsonView(us)
)
) )
) )
) )
) )
)
} }
def chapter(studyId: String, chapterId: String) = def chapter(studyId: String, chapterId: String) =
Open { implicit ctx => Open { implicit ctx =>
OptionFuResult(api.getStudyWithChapter(ctx.me, studyId, chapterId)) { us => OptionFuResult(api.getStudyWithChapter(ctx.me, studyId, chapterId)) { us =>
analysisJson(us) map { analysisJson(us) map { case (analysisJson, studyJson) =>
case (analysisJson, studyJson) => Ok(
Ok( Json.obj(
Json.obj( "study" -> studyJson,
"study" -> studyJson, "analysis" -> analysisJson
"analysis" -> analysisJson )
) ) as JSON
) as JSON
} }
} map NoCache } map NoCache
} }

View File

@ -55,13 +55,12 @@ final class Pref(env: Env) extends LilaController(env) {
Ok.withCookies(env.lilaCookie.session("zoom2", (getInt("v") | 185).toString)).fuccess Ok.withCookies(env.lilaCookie.session("zoom2", (getInt("v") | 185).toString)).fuccess
} else { } else {
implicit val req = ctx.body implicit val req = ctx.body
(setters get name) ?? { (setters get name) ?? { case (form, fn) =>
case (form, fn) => FormResult(form) { v =>
FormResult(form) { v => fn(v, ctx) map { cookie =>
fn(v, ctx) map { cookie => Ok(()).withCookies(cookie)
Ok(()).withCookies(cookie)
}
} }
}
} }
} }
} }

View File

@ -33,10 +33,9 @@ final class Prismic(
api.bookmarks.get(name) ?? getDocument map2 { (doc: io.prismic.Document) => api.bookmarks.get(name) ?? getDocument map2 { (doc: io.prismic.Document) =>
doc -> makeLinkResolver(api) doc -> makeLinkResolver(api)
} }
} recover { } recover { case e: Exception =>
case e: Exception => logger.error(s"bookmark:$name", e)
logger.error(s"bookmark:$name", e) none
none
} }
def getVariant(variant: chess.variant.Variant) = def getVariant(variant: chess.variant.Variant) =

View File

@ -188,11 +188,10 @@ final class Puzzle(
vote => vote =>
env.puzzle.api.vote.find(id, me) flatMap { v => env.puzzle.api.vote.find(id, me) flatMap { v =>
env.puzzle.api.vote.update(id, me, v, vote == 1) env.puzzle.api.vote.update(id, me, v, vote == 1)
} map { } map { case (p, a) =>
case (p, a) => if (vote == 1) lila.mon.puzzle.vote.up.increment()
if (vote == 1) lila.mon.puzzle.vote.up.increment() else lila.mon.puzzle.vote.down.increment()
else lila.mon.puzzle.vote.down.increment() Ok(Json.arr(a.value, p.vote.sum))
Ok(Json.arr(a.value, p.vote.sum))
} }
) map (_ as JSON) ) map (_ as JSON)
} }

View File

@ -21,8 +21,7 @@ final class Relation(
private def renderActions(userId: String, mini: Boolean)(implicit ctx: Context) = private def renderActions(userId: String, mini: Boolean)(implicit ctx: Context) =
(ctx.userId ?? { api.fetchRelation(_, userId) }) zip (ctx.userId ?? { api.fetchRelation(_, userId) }) zip
(ctx.isAuth ?? { env.pref.api followable userId }) zip (ctx.isAuth ?? { env.pref.api followable userId }) zip
(ctx.userId ?? { api.fetchBlocks(userId, _) }) flatMap { (ctx.userId ?? { api.fetchBlocks(userId, _) }) flatMap { case relation ~ followable ~ blocked =>
case relation ~ followable ~ blocked =>
negotiate( negotiate(
html = fuccess(Ok { html = fuccess(Ok {
if (mini) if (mini)
@ -41,7 +40,7 @@ final class Relation(
) )
) )
) )
} }
def follow(userId: String) = def follow(userId: String) =
Auth { implicit ctx => me => Auth { implicit ctx => me =>

View File

@ -119,18 +119,17 @@ final class Relay(
OpenOrScoped(_.Study.Read)( OpenOrScoped(_.Study.Read)(
open = implicit ctx => { open = implicit ctx => {
pageHit pageHit
WithRelay(slug, id) { WithRelay(slug, id) { relay =>
relay => val sc =
val sc = if (relay.sync.ongoing)
if (relay.sync.ongoing) env.study.chapterRepo relaysAndTagsByStudyId relay.studyId flatMap { env.study.chapterRepo relaysAndTagsByStudyId relay.studyId flatMap { chapters =>
chapters => chapters.find(_.looksAlive) orElse chapters.headOption match {
chapters.find(_.looksAlive) orElse chapters.headOption match { case Some(chapter) => env.study.api.byIdWithChapter(relay.studyId, chapter.id)
case Some(chapter) => env.study.api.byIdWithChapter(relay.studyId, chapter.id) case None => env.study.api byIdWithChapter relay.studyId
case None => env.study.api byIdWithChapter relay.studyId }
}
} }
else env.study.api byIdWithChapter relay.studyId else env.study.api byIdWithChapter relay.studyId
sc flatMap { _ ?? { doShow(relay, _) } } sc flatMap { _ ?? { doShow(relay, _) } }
} }
}, },
scoped = _ => scoped = _ =>

View File

@ -35,22 +35,20 @@ final class Report(
private def renderList(room: String)(implicit ctx: Context) = private def renderList(room: String)(implicit ctx: Context) =
api.openAndRecentWithFilter(12, Room(room)) zip api.openAndRecentWithFilter(12, Room(room)) zip
getCounts flatMap { getCounts flatMap { case (reports, counts ~ streamers ~ appeals) =>
case (reports, counts ~ streamers ~ appeals) =>
(env.user.lightUserApi preloadMany reports.flatMap(_.report.userIds)) inject (env.user.lightUserApi preloadMany reports.flatMap(_.report.userIds)) inject
Ok(html.report.list(reports, room, counts, streamers, appeals)) Ok(html.report.list(reports, room, counts, streamers, appeals))
} }
def inquiry(id: String) = def inquiry(id: String) =
Secure(_.SeeReport) { _ => me => Secure(_.SeeReport) { _ => me =>
api.inquiries.toggle(AsMod(me), id) map { api.inquiries.toggle(AsMod(me), id) map { case (prev, next) =>
case (prev, next) => next.fold(
next.fold( Redirect {
Redirect { if (prev.exists(_.isAppeal)) routes.Appeal.queue()
if (prev.exists(_.isAppeal)) routes.Appeal.queue() else routes.Report.list()
else routes.Report.list() }
} )(onInquiryStart)
)(onInquiryStart)
} }
} }
@ -95,12 +93,11 @@ final class Report(
} }
else if (force) userC.modZoneOrRedirect(prev.user) else if (force) userC.modZoneOrRedirect(prev.user)
else else
api.inquiries.toggle(AsMod(me), prev.id) map { api.inquiries.toggle(AsMod(me), prev.id) map { case (prev, next) =>
case (prev, next) => next.fold(
next.fold( if (prev.exists(_.isAppeal)) Redirect(routes.Appeal.queue())
if (prev.exists(_.isAppeal)) Redirect(routes.Appeal.queue()) else redirectToList
else redirectToList )(onInquiryStart)
)(onInquiryStart)
} }
} }
} }
@ -133,8 +130,8 @@ final class Report(
def form = def form =
Auth { implicit ctx => _ => Auth { implicit ctx => _ =>
get("username") ?? env.user.repo.named flatMap { user => get("username") ?? env.user.repo.named flatMap { user =>
env.report.forms.createWithCaptcha map { env.report.forms.createWithCaptcha map { case (form, captcha) =>
case (form, captcha) => Ok(html.report.form(form, user, captcha)) Ok(html.report.form(form, user, captcha))
} }
} }
} }

View File

@ -33,16 +33,15 @@ final class Round(
else else
PreventTheft(pov) { PreventTheft(pov) {
pov.game.playableByAi ?? env.fishnet.player(pov.game) pov.game.playableByAi ?? env.fishnet.player(pov.game)
env.tournament.api.gameView.player(pov) flatMap { env.tournament.api.gameView.player(pov) flatMap { tour =>
tour => gameC.preloadUsers(pov.game) zip
gameC.preloadUsers(pov.game) zip (pov.game.simulId ?? env.simul.repo.find) zip
(pov.game.simulId ?? env.simul.repo.find) zip getPlayerChat(pov.game, tour.map(_.tour)) zip
getPlayerChat(pov.game, tour.map(_.tour)) zip (ctx.noBlind ?? env.game.crosstableApi
(ctx.noBlind ?? env.game.crosstableApi .withMatchup(pov.game)) zip
.withMatchup(pov.game)) zip (pov.game.isSwitchable ?? otherPovs(pov.game)) zip
(pov.game.isSwitchable ?? otherPovs(pov.game)) zip env.bookmark.api.exists(pov.game, ctx.me) zip
env.bookmark.api.exists(pov.game, ctx.me) zip env.api.roundApi.player(pov, tour, lila.api.Mobile.Api.currentVersion) map {
env.api.roundApi.player(pov, tour, lila.api.Mobile.Api.currentVersion) map {
case _ ~ simul ~ chatOption ~ crosstable ~ playing ~ bookmarked ~ data => case _ ~ simul ~ chatOption ~ crosstable ~ playing ~ bookmarked ~ data =>
simul foreach env.simul.api.onPlayerConnection(pov.game, ctx.me) simul foreach env.simul.api.onPlayerConnection(pov.game, ctx.me)
Ok( Ok(
@ -67,12 +66,11 @@ final class Round(
pov.game.playableByAi ?? env.fishnet.player(pov.game) pov.game.playableByAi ?? env.fishnet.player(pov.game)
gameC.preloadUsers(pov.game) zip gameC.preloadUsers(pov.game) zip
env.api.roundApi.player(pov, tour, apiVersion) zip env.api.roundApi.player(pov, tour, apiVersion) zip
getPlayerChat(pov.game, none) map { getPlayerChat(pov.game, none) map { case _ ~ data ~ chat =>
case _ ~ data ~ chat =>
Ok { Ok {
data.add("chat", chat.flatMap(_.game).map(c => lila.chat.JsonView(c.chat))) data.add("chat", chat.flatMap(_.game).map(c => lila.chat.JsonView(c.chat)))
} }
} }
} }
} }
) dmap NoCache ) dmap NoCache
@ -171,29 +169,29 @@ final class Round(
getWatcherChat(pov.game) zip getWatcherChat(pov.game) zip
(ctx.noBlind ?? env.game.crosstableApi.withMatchup(pov.game)) zip (ctx.noBlind ?? env.game.crosstableApi.withMatchup(pov.game)) zip
env.bookmark.api.exists(pov.game, ctx.me) flatMap { env.bookmark.api.exists(pov.game, ctx.me) flatMap {
case tour ~ simul ~ chat ~ crosstable ~ bookmarked => case tour ~ simul ~ chat ~ crosstable ~ bookmarked =>
env.api.roundApi.watcher( env.api.roundApi.watcher(
pov, pov,
tour, tour,
lila.api.Mobile.Api.currentVersion, lila.api.Mobile.Api.currentVersion,
tv = userTv.map { u => tv = userTv.map { u =>
lila.round.OnUserTv(u.id) lila.round.OnUserTv(u.id)
} }
) map { data => ) map { data =>
Ok( Ok(
html.round.watcher( html.round.watcher(
pov, pov,
data, data,
tour.map(_.tourAndTeamVs), tour.map(_.tourAndTeamVs),
simul, simul,
crosstable, crosstable,
userTv = userTv, userTv = userTv,
chatOption = chat, chatOption = chat,
bookmarked = bookmarked bookmarked = bookmarked
)
) )
) }
} }
}
else else
for { // web crawlers don't need the full thing for { // web crawlers don't need the full thing
initialFen <- env.game.gameRepo.initialFen(pov.gameId) initialFen <- env.game.gameRepo.initialFen(pov.gameId)
@ -243,7 +241,8 @@ final class Round(
) )
.some .some
(game.tournamentId, game.simulId, game.swissId) match { (game.tournamentId, game.simulId, game.swissId) match {
case (Some(tid), _, _) => { case (Some(tid), _, _) =>
{
ctx.isAuth && tour.fold(true)(tournamentC.canHaveChat(_, none)) ctx.isAuth && tour.fold(true)(tournamentC.canHaveChat(_, none))
} ?? env.chat.api.userChat.cached } ?? env.chat.api.userChat.cached
.findMine(Chat.Id(tid), ctx.me) .findMine(Chat.Id(tid), ctx.me)
@ -285,9 +284,9 @@ final class Round(
env.game.gameRepo.initialFen(pov.game) zip env.game.gameRepo.initialFen(pov.game) zip
env.game.crosstableApi.withMatchup(pov.game) zip env.game.crosstableApi.withMatchup(pov.game) zip
env.bookmark.api.exists(pov.game, ctx.me) map { env.bookmark.api.exists(pov.game, ctx.me) map {
case tour ~ simul ~ initialFen ~ crosstable ~ bookmarked => case tour ~ simul ~ initialFen ~ crosstable ~ bookmarked =>
Ok(html.game.bits.sides(pov, initialFen, tour, crosstable, simul, bookmarked = bookmarked)) Ok(html.game.bits.sides(pov, initialFen, tour, crosstable, simul, bookmarked = bookmarked))
} }
} }
} }

View File

@ -18,25 +18,22 @@ final class Simul(env: Env) extends LilaController(env) {
val home = Open { implicit ctx => val home = Open { implicit ctx =>
pageHit pageHit
fetchSimuls(ctx.me) flatMap { fetchSimuls(ctx.me) flatMap { case pending ~ created ~ started ~ finished =>
case pending ~ created ~ started ~ finished => Ok(html.simul.home(pending, created, started, finished)).fuccess
Ok(html.simul.home(pending, created, started, finished)).fuccess
} }
} }
val apiList = Action.async { val apiList = Action.async {
fetchSimuls(none) flatMap { fetchSimuls(none) flatMap { case pending ~ created ~ started ~ finished =>
case pending ~ created ~ started ~ finished => env.simul.jsonView.apiAll(pending, created, started, finished) map { json =>
env.simul.jsonView.apiAll(pending, created, started, finished) map { json => Ok(json) as JSON
Ok(json) as JSON }
}
} }
} }
val homeReload = Open { implicit ctx => val homeReload = Open { implicit ctx =>
fetchSimuls(ctx.me) map { fetchSimuls(ctx.me) map { case pending ~ created ~ started ~ finished =>
case pending ~ created ~ started ~ finished => Ok(html.simul.homeInner(pending, created, started, finished))
Ok(html.simul.homeInner(pending, created, started, finished))
} }
} }
@ -83,10 +80,10 @@ final class Simul(env: Env) extends LilaController(env) {
ctx.me.fold(true) { // anon can see public chats ctx.me.fold(true) { // anon can see public chats
env.chat.panic.allowed env.chat.panic.allowed
} && simul.team.fold(true) { teamId => } && simul.team.fold(true) { teamId =>
ctx.userId exists { ctx.userId exists {
env.team.api.syncBelongsTo(teamId, _) env.team.api.syncBelongsTo(teamId, _)
}
} }
}
def hostPing(simulId: String) = def hostPing(simulId: String) =
Open { implicit ctx => Open { implicit ctx =>

View File

@ -136,8 +136,8 @@ final class Streamer(
AsStreamer { s => AsStreamer { s =>
ctx.body.body.file("picture") match { ctx.body.body.file("picture") match {
case Some(pic) => case Some(pic) =>
api.uploadPicture(s.streamer, pic) recover { api.uploadPicture(s.streamer, pic) recover { case e: lila.base.LilaException =>
case e: lila.base.LilaException => BadRequest(html.streamer.picture(s, e.message.some)) BadRequest(html.streamer.picture(s, e.message.some))
} inject Redirect(routes.Streamer.edit()) } inject Redirect(routes.Streamer.edit())
case None => fuccess(Redirect(routes.Streamer.edit())) case None => fuccess(Redirect(routes.Streamer.edit()))
} }

View File

@ -79,10 +79,9 @@ final class Study(
Auth { implicit ctx => me => Auth { implicit ctx => me =>
env.study.pager.mine(me, Order(order), page) flatMap { pag => env.study.pager.mine(me, Order(order), page) flatMap { pag =>
negotiate( negotiate(
html = html = env.study.topicApi.userTopics(me.id) map { topics =>
env.study.topicApi.userTopics(me.id) map { topics => Ok(html.study.list.mine(pag, Order(order), me, topics))
Ok(html.study.list.mine(pag, Order(order), me, topics)) },
},
api = _ => apiStudies(pag) api = _ => apiStudies(pag)
) )
} }
@ -112,10 +111,9 @@ final class Study(
Auth { implicit ctx => me => Auth { implicit ctx => me =>
env.study.pager.mineMember(me, Order(order), page) flatMap { pag => env.study.pager.mineMember(me, Order(order), page) flatMap { pag =>
negotiate( negotiate(
html = html = env.study.topicApi.userTopics(me.id) map { topics =>
env.study.topicApi.userTopics(me.id) map { topics => Ok(html.study.list.mineMember(pag, Order(order), me, topics))
Ok(html.study.list.mineMember(pag, Order(order), me, topics)) },
},
api = _ => apiStudies(pag) api = _ => apiStudies(pag)
) )
} }
@ -137,10 +135,9 @@ final class Study(
case None => notFound case None => notFound
case Some(topic) => case Some(topic) =>
env.study.pager.byTopic(topic, ctx.me, Order(order), page) zip env.study.pager.byTopic(topic, ctx.me, Order(order), page) zip
ctx.me.??(u => env.study.topicApi.userTopics(u.id) dmap some) map { ctx.me.??(u => env.study.topicApi.userTopics(u.id) dmap some) map { case (pag, topics) =>
case (pag, topics) =>
Ok(html.study.topic.show(topic, pag, Order(order), topics)) Ok(html.study.topic.show(topic, pag, Order(order), topics))
} }
} }
} }
@ -341,40 +338,39 @@ final class Study(
def embed(id: String, chapterId: String) = def embed(id: String, chapterId: String) =
Action.async { implicit req => Action.async { implicit req =>
env.study.api.byIdWithChapter(id, chapterId).map(_.filterNot(_.study.isPrivate)) flatMap { env.study.api.byIdWithChapter(id, chapterId).map(_.filterNot(_.study.isPrivate)) flatMap {
_.fold(embedNotFound) { _.fold(embedNotFound) { case WithChapter(study, chapter) =>
case WithChapter(study, chapter) => for {
for { chapters <- env.study.chapterRepo.idNames(study.id)
chapters <- env.study.chapterRepo.idNames(study.id) studyJson <- env.study.jsonView(
studyJson <- env.study.jsonView( study.copy(
study.copy( members = lila.study.StudyMembers(Map.empty) // don't need no members
members = lila.study.StudyMembers(Map.empty) // don't need no members ),
), List(chapter.metadata),
List(chapter.metadata), chapter,
chapter, none
none )
) setup = chapter.setup
setup = chapter.setup initialFen = chapter.root.fen.some
initialFen = chapter.root.fen.some pov = userAnalysisC.makePov(initialFen, setup.variant)
pov = userAnalysisC.makePov(initialFen, setup.variant) baseData = env.round.jsonView.userAnalysisJson(
baseData = env.round.jsonView.userAnalysisJson( pov,
pov, lila.pref.Pref.default,
lila.pref.Pref.default, initialFen,
initialFen, setup.orientation,
setup.orientation, owner = false,
owner = false, me = none
me = none )
) analysis = baseData ++ Json.obj(
analysis = baseData ++ Json.obj( "treeParts" -> partitionTreeJsonWriter.writes {
"treeParts" -> partitionTreeJsonWriter.writes { lila.study.TreeBuilder.makeRoot(chapter.root, setup.variant)
lila.study.TreeBuilder.makeRoot(chapter.root, setup.variant) }
} )
) data = lila.study.JsonView.JsData(study = studyJson, analysis = analysis)
data = lila.study.JsonView.JsData(study = studyJson, analysis = analysis) result <- negotiate(
result <- negotiate( html = Ok(html.study.embed(study, chapter, chapters, data)).fuccess,
html = Ok(html.study.embed(study, chapter, chapters, data)).fuccess, api = _ => Ok(Json.obj("study" -> data.study, "analysis" -> data.analysis)).fuccess
api = _ => Ok(Json.obj("study" -> data.study, "analysis" -> data.analysis)).fuccess )
) } yield result
} yield result
} }
} map NoCache } map NoCache
} }
@ -446,17 +442,16 @@ final class Study(
def chapterPgn(id: String, chapterId: String) = def chapterPgn(id: String, chapterId: String) =
Open { implicit ctx => Open { implicit ctx =>
env.study.api.byIdWithChapter(id, chapterId) flatMap { env.study.api.byIdWithChapter(id, chapterId) flatMap {
_.fold(notFound) { _.fold(notFound) { case WithChapter(study, chapter) =>
case WithChapter(study, chapter) => CanViewResult(study) {
CanViewResult(study) { lila.mon.export.pgn.studyChapter.increment()
lila.mon.export.pgn.studyChapter.increment() Ok(env.study.pgnDump.ofChapter(study, requestPgnFlags(ctx.req))(chapter).toString)
Ok(env.study.pgnDump.ofChapter(study, requestPgnFlags(ctx.req))(chapter).toString) .withHeaders(
.withHeaders( CONTENT_DISPOSITION -> s"attachment; filename=${env.study.pgnDump.filename(study, chapter)}.pgn"
CONTENT_DISPOSITION -> s"attachment; filename=${env.study.pgnDump.filename(study, chapter)}.pgn" )
) .as(pgnContentType)
.as(pgnContentType) .fuccess
.fuccess }
}
} }
} }
} }
@ -471,17 +466,16 @@ final class Study(
def chapterGif(id: String, chapterId: String) = def chapterGif(id: String, chapterId: String) =
Open { implicit ctx => Open { implicit ctx =>
env.study.api.byIdWithChapter(id, chapterId) flatMap { env.study.api.byIdWithChapter(id, chapterId) flatMap {
_.fold(notFound) { _.fold(notFound) { case WithChapter(study, chapter) =>
case WithChapter(study, chapter) => CanViewResult(study) {
CanViewResult(study) { env.study.gifExport.ofChapter(chapter) map { stream =>
env.study.gifExport.ofChapter(chapter) map { stream => Ok.chunked(stream)
Ok.chunked(stream) .withHeaders(
.withHeaders( noProxyBufferHeader,
noProxyBufferHeader, CONTENT_DISPOSITION -> s"attachment; filename=${env.study.pgnDump.filename(study, chapter)}.gif"
CONTENT_DISPOSITION -> s"attachment; filename=${env.study.pgnDump.filename(study, chapter)}.gif" ) as "image/gif"
) as "image/gif"
}
} }
}
} }
} }
} }
@ -512,11 +506,10 @@ final class Study(
def topics = def topics =
Open { implicit ctx => Open { implicit ctx =>
env.study.topicApi.popular(50) zip env.study.topicApi.popular(50) zip
ctx.me.??(u => env.study.topicApi.userTopics(u.id) dmap some) map { ctx.me.??(u => env.study.topicApi.userTopics(u.id) dmap some) map { case (popular, mine) =>
case (popular, mine) =>
val form = mine map lila.study.StudyForm.topicsForm val form = mine map lila.study.StudyForm.topicsForm
Ok(html.study.topic.index(popular, mine, form)) Ok(html.study.topic.index(popular, mine, form))
} }
} }
def setTopics = def setTopics =

View File

@ -331,18 +331,16 @@ final class Team(
OptionFuRedirectUrl(for { OptionFuRedirectUrl(for {
requestOption <- api request requestId requestOption <- api request requestId
teamOption <- requestOption.??(req => env.team.teamRepo.byLeader(req.team, me.id)) teamOption <- requestOption.??(req => env.team.teamRepo.byLeader(req.team, me.id))
} yield (teamOption, requestOption).mapN((_, _))) { } yield (teamOption, requestOption).mapN((_, _))) { case (team, request) =>
case (team, request) => implicit val req = ctx.body
implicit val req = ctx.body forms.processRequest
forms.processRequest .bindFromRequest()
.bindFromRequest() .fold(
.fold( _ => fuccess(routes.Team.show(team.id).toString),
_ => fuccess(routes.Team.show(team.id).toString), { case (decision, url) =>
{ api.processRequest(team, request, decision == "accept") inject url
case (decision, url) => }
api.processRequest(team, request, decision == "accept") inject url )
}
)
} }
} }

View File

@ -410,10 +410,9 @@ final class Tournament(
def categShields(k: String) = def categShields(k: String) =
Open { implicit ctx => Open { implicit ctx =>
OptionFuOk(env.tournament.shieldApi.byCategKey(k)) { OptionFuOk(env.tournament.shieldApi.byCategKey(k)) { case (categ, awards) =>
case (categ, awards) => env.user.lightUserApi preloadMany awards.map(_.owner.value) inject
env.user.lightUserApi preloadMany awards.map(_.owner.value) inject html.tournament.shields.byCateg(categ, awards)
html.tournament.shields.byCateg(categ, awards)
} }
} }

View File

@ -39,25 +39,23 @@ final class Tv(
} }
private def lichessTv(channel: lila.tv.Tv.Channel)(implicit ctx: Context) = private def lichessTv(channel: lila.tv.Tv.Channel)(implicit ctx: Context) =
OptionFuResult(env.tv.tv getGameAndHistory channel) { OptionFuResult(env.tv.tv getGameAndHistory channel) { case (game, history) =>
case (game, history) => val flip = getBool("flip")
val flip = getBool("flip") val natural = Pov naturalOrientation game
val natural = Pov naturalOrientation game val pov = if (flip) !natural else natural
val pov = if (flip) !natural else natural val onTv = lila.round.OnLichessTv(channel.key, flip)
val onTv = lila.round.OnLichessTv(channel.key, flip) negotiate(
negotiate( html = env.tournament.api.gameView.watcher(pov.game) flatMap { tour =>
html = env.tournament.api.gameView.watcher(pov.game) flatMap { tour => env.api.roundApi.watcher(pov, tour, lila.api.Mobile.Api.currentVersion, tv = onTv.some) zip
env.api.roundApi.watcher(pov, tour, lila.api.Mobile.Api.currentVersion, tv = onTv.some) zip env.game.crosstableApi.withMatchup(game) zip
env.game.crosstableApi.withMatchup(game) zip env.tv.tv.getChampions map { case data ~ cross ~ champions =>
env.tv.tv.getChampions map { NoCache {
case data ~ cross ~ champions => Ok(html.tv.index(channel, champions, pov, data, cross, history))
NoCache { }
Ok(html.tv.index(channel, champions, pov, data, cross, history))
}
} }
}, },
api = apiVersion => env.api.roundApi.watcher(pov, none, apiVersion, tv = onTv.some) map { Ok(_) } api = apiVersion => env.api.roundApi.watcher(pov, none, apiVersion, tv = onTv.some) map { Ok(_) }
) )
} }
def games = gamesChannel(lila.tv.Tv.Channel.Best.key) def games = gamesChannel(lila.tv.Tv.Channel.Best.key)
@ -65,11 +63,10 @@ final class Tv(
def gamesChannel(chanKey: String) = def gamesChannel(chanKey: String) =
Open { implicit ctx => Open { implicit ctx =>
(lila.tv.Tv.Channel.byKey get chanKey) ?? { channel => (lila.tv.Tv.Channel.byKey get chanKey) ?? { channel =>
env.tv.tv.getChampions zip env.tv.tv.getGames(channel, 15) map { env.tv.tv.getChampions zip env.tv.tv.getGames(channel, 15) map { case (champs, games) =>
case (champs, games) => NoCache {
NoCache { Ok(html.tv.games(channel, games map Pov.naturalOrientation, champs))
Ok(html.tv.games(channel, games map Pov.naturalOrientation, champs)) }
}
} }
} }
} }
@ -82,8 +79,8 @@ final class Tv(
import play.api.libs.EventSource import play.api.libs.EventSource
env.round.tvBroadcast ? TvBroadcast.Connect mapTo env.round.tvBroadcast ? TvBroadcast.Connect mapTo
manifest[TvBroadcast.SourceType] map { source => manifest[TvBroadcast.SourceType] map { source =>
Ok.chunked(source via EventSource.flow).as(ContentTypes.EVENT_STREAM) pipe noProxyBuffer Ok.chunked(source via EventSource.flow).as(ContentTypes.EVENT_STREAM) pipe noProxyBuffer
} }
} }
def frame = def frame =

View File

@ -48,9 +48,9 @@ final class User(
val userId = UserModel normalize username val userId = UserModel normalize username
env.game.cached.lastPlayedPlayingId(userId) orElse env.game.cached.lastPlayedPlayingId(userId) orElse
env.game.gameRepo.quickLastPlayedId(userId) flatMap { env.game.gameRepo.quickLastPlayedId(userId) flatMap {
case None => NotFound("No ongoing game").fuccess case None => NotFound("No ongoing game").fuccess
case Some(gameId) => gameC.exportGame(gameId, req) case Some(gameId) => gameC.exportGame(gameId, req)
} }
} }
private def apiGames(u: UserModel, filter: String, page: Int)(implicit ctx: BodyContext[_]) = { private def apiGames(u: UserModel, filter: String, page: Int)(implicit ctx: BodyContext[_]) = {
@ -146,26 +146,26 @@ final class User(
ctx.userId.?? { env.game.crosstableApi.fetchOrEmpty(user.id, _) dmap some } zip ctx.userId.?? { env.game.crosstableApi.fetchOrEmpty(user.id, _) dmap some } zip
ctx.isAuth.?? { env.pref.api.followable(user.id) } zip ctx.isAuth.?? { env.pref.api.followable(user.id) } zip
ctx.userId.?? { relationApi.fetchRelation(_, user.id) } flatMap { ctx.userId.?? { relationApi.fetchRelation(_, user.id) } flatMap {
case blocked ~ crosstable ~ followable ~ relation => case blocked ~ crosstable ~ followable ~ relation =>
val ping = env.socket.isOnline(user.id) ?? UserLagCache.getLagRating(user.id) val ping = env.socket.isOnline(user.id) ?? UserLagCache.getLagRating(user.id)
negotiate( negotiate(
html = !ctx.is(user) ?? currentlyPlaying(user) map { pov => html = !ctx.is(user) ?? currentlyPlaying(user) map { pov =>
Ok(html.user.mini(user, pov, blocked, followable, relation, ping, crosstable)) Ok(html.user.mini(user, pov, blocked, followable, relation, ping, crosstable))
.withHeaders(CACHE_CONTROL -> "max-age=5") .withHeaders(CACHE_CONTROL -> "max-age=5")
}, },
api = _ => { api = _ => {
import lila.game.JsonView.crosstableWrites import lila.game.JsonView.crosstableWrites
fuccess( fuccess(
Ok( Ok(
Json.obj( Json.obj(
"crosstable" -> crosstable, "crosstable" -> crosstable,
"perfs" -> lila.user.JsonView.perfs(user, user.best8Perfs) "perfs" -> lila.user.JsonView.perfs(user, user.best8Perfs)
)
) )
) )
) }
} )
) }
}
else fuccess(Ok(html.user.bits.miniClosed(user))) else fuccess(Ok(html.user.bits.miniClosed(user)))
} }
} }
@ -364,9 +364,9 @@ final class User(
.logTimeIfGt(s"$username noteApi.forMod", 2 seconds)) zip .logTimeIfGt(s"$username noteApi.forMod", 2 seconds)) zip
env.playban.api.bans(familyUserIds).logTimeIfGt(s"$username playban.bans", 2 seconds) zip env.playban.api.bans(familyUserIds).logTimeIfGt(s"$username playban.bans", 2 seconds) zip
lila.security.UserSpy.withMeSortedWithEmails(env.user.repo, user, spy) map { lila.security.UserSpy.withMeSortedWithEmails(env.user.repo, user, spy) map {
case notes ~ bans ~ othersWithEmail => case notes ~ bans ~ othersWithEmail =>
html.user.mod.otherUsers(user, spy, othersWithEmail, notes, bans, nbOthers) html.user.mod.otherUsers(user, spy, othersWithEmail, notes, bans, nbOthers)
} }
} }
val identification = spyFu map { spy => val identification = spyFu map { spy =>
(isGranted(_.Doxing) || (user.lameOrAlt && !user.hasTitle)) ?? (isGranted(_.Doxing) || (user.lameOrAlt && !user.hasTitle)) ??
@ -461,11 +461,10 @@ final class User(
relateds <- relateds <-
ops ops
.zip(followables) .zip(followables)
.map { .map { case ((u, nb), followable) =>
case ((u, nb), followable) => relationApi.fetchRelation(me.id, u.id) map {
relationApi.fetchRelation(me.id, u.id) map { lila.relation.Related(u, nb.some, followable, _)
lila.relation.Related(u, nb.some, followable, _) }
}
} }
.sequenceFu .sequenceFu
} yield html.user.opponents(me, relateds) } yield html.user.opponents(me, relateds)
@ -516,7 +515,8 @@ final class User(
env.user.repo nameExists term map { r => env.user.repo nameExists term map { r =>
Ok(JsBoolean(r)) Ok(JsBoolean(r))
} }
case Some(term) => { case Some(term) =>
{
(get("tour"), get("swiss")) match { (get("tour"), get("swiss")) match {
case (Some(tourId), _) => env.tournament.playerRepo.searchPlayers(tourId, term, 10) case (Some(tourId), _) => env.tournament.playerRepo.searchPlayers(tourId, term, 10)
case (_, Some(swissId)) => env.swiss.api.searchPlayers(lila.swiss.Swiss.Id(swissId), term, 10) case (_, Some(swissId)) => env.swiss.api.searchPlayers(lila.swiss.Swiss.Id(swissId), term, 10)

View File

@ -113,8 +113,7 @@ final class UserAnalysis(
env.game.gameRepo initialFen pov.gameId flatMap { initialFen => env.game.gameRepo initialFen pov.gameId flatMap { initialFen =>
gameC.preloadUsers(pov.game) zip gameC.preloadUsers(pov.game) zip
(env.analyse.analyser get pov.game) zip (env.analyse.analyser get pov.game) zip
env.game.crosstableApi(pov.game) flatMap { env.game.crosstableApi(pov.game) flatMap { case _ ~ analysis ~ crosstable =>
case _ ~ analysis ~ crosstable =>
import lila.game.JsonView.crosstableWrites import lila.game.JsonView.crosstableWrites
env.api.roundApi.review( env.api.roundApi.review(
pov, pov,
@ -126,7 +125,7 @@ final class UserAnalysis(
) map { data => ) map { data =>
Ok(data.add("crosstable", crosstable)) Ok(data.add("crosstable", crosstable))
} }
} }
} }
// XHR only // XHR only
@ -142,19 +141,18 @@ final class UserAnalysis(
.inMemory(data) .inMemory(data)
.fold( .fold(
err => BadRequest(jsonError(err)).fuccess, err => BadRequest(jsonError(err)).fuccess,
{ { case (game, fen) =>
case (game, fen) => val pov = Pov(game, chess.White)
val pov = Pov(game, chess.White) env.api.roundApi.userAnalysisJson(
env.api.roundApi.userAnalysisJson( pov,
pov, ctx.pref,
ctx.pref, initialFen = fen,
initialFen = fen, pov.color,
pov.color, owner = false,
owner = false, me = ctx.me
me = ctx.me ) map { data =>
) map { data => Ok(data)
Ok(data) }
}
} }
) )
) )
@ -174,11 +172,11 @@ final class UserAnalysis(
forecasts => forecasts =>
env.round.forecastApi.save(pov, forecasts) >> env.round.forecastApi.save(pov, forecasts) >>
env.round.forecastApi.loadForDisplay(pov) map { env.round.forecastApi.loadForDisplay(pov) map {
case None => Ok(Json.obj("none" -> true)) case None => Ok(Json.obj("none" -> true))
case Some(fc) => Ok(Json toJson fc) as JSON case Some(fc) => Ok(Json toJson fc) as JSON
} recover { } recover { case Forecast.OutOfSync =>
case Forecast.OutOfSync => Ok(Json.obj("reload" -> true)) Ok(Json.obj("reload" -> true))
} }
) )
} }
} }

View File

@ -33,10 +33,9 @@ final class Video(env: Env) extends LilaController(env) {
} }
case None => case None =>
api.video.byTags(ctx.me, control.filter.tags, getInt("page") | 1) zip api.video.byTags(ctx.me, control.filter.tags, getInt("page") | 1) zip
api.video.count.apply map { api.video.count.apply map { case (videos, count) =>
case (videos, count) =>
Ok(html.video.index(videos, count, control)) Ok(html.video.index(videos, count, control))
} }
} }
} }
} }
@ -50,10 +49,9 @@ final class Video(env: Env) extends LilaController(env) {
api.video.similar(ctx.me, video, 9) zip api.video.similar(ctx.me, video, 9) zip
ctx.userId.?? { userId => ctx.userId.?? { userId =>
api.view.add(View.make(videoId = video.id, userId = userId)) api.view.add(View.make(videoId = video.id, userId = userId))
} map { } map { case (similar, _) =>
case (similar, _) =>
Ok(html.video.show(video, similar, control)) Ok(html.video.show(video, similar, control))
} }
} }
} }
} }

View File

@ -34,10 +34,9 @@ final class ErrorHandler(
) )
}) })
else InternalServerError("Sorry, something went wrong.") else InternalServerError("Sorry, something went wrong.")
} recover { } recover { case util.control.NonFatal(e) =>
case util.control.NonFatal(e) => lila.log("http").error(s"""Error handler exception on "${exception.getMessage}\"""", e)
lila.log("http").error(s"""Error handler exception on "${exception.getMessage}\"""", e) InternalServerError("Sorry, something went very wrong.")
InternalServerError("Sorry, something went very wrong.")
} }
override def onClientError(req: RequestHeader, statusCode: Int, msg: String): Fu[Result] = override def onClientError(req: RequestHeader, statusCode: Int, msg: String): Fu[Result] =

View File

@ -55,38 +55,37 @@ final class Preload(
.mon(_.lobby segment "streams")) zip .mon(_.lobby segment "streams")) zip
(ctx.userId ?? playbanApi.currentBan).mon(_.lobby segment "playban") zip (ctx.userId ?? playbanApi.currentBan).mon(_.lobby segment "playban") zip
(ctx.blind ?? ctx.me ?? roundProxy.urgentGames) flatMap { (ctx.blind ?? ctx.me ?? roundProxy.urgentGames) flatMap {
case ( case (
data,
povs
) ~ posts ~ tours ~ events ~ simuls ~ feat ~ entries ~ lead ~ tWinners ~ puzzle ~ streams ~ playban ~ blindGames =>
(ctx.me ?? currentGameMyTurn(povs, lightUserApi.sync))
.mon(_.lobby segment "currentGame") zip
lightUserApi
.preloadMany {
tWinners.map(_.userId) ::: posts.flatMap(_.userId) ::: entries.flatMap(_.userIds).toList
}
.mon(_.lobby segment "lightUsers") map {
case (currentGame, _) =>
Homepage(
data, data,
entries, povs
posts, ) ~ posts ~ tours ~ events ~ simuls ~ feat ~ entries ~ lead ~ tWinners ~ puzzle ~ streams ~ playban ~ blindGames =>
tours, (ctx.me ?? currentGameMyTurn(povs, lightUserApi.sync))
events, .mon(_.lobby segment "currentGame") zip
simuls, lightUserApi
feat, .preloadMany {
lead, tWinners.map(_.userId) ::: posts.flatMap(_.userId) ::: entries.flatMap(_.userIds).toList
tWinners, }
puzzle, .mon(_.lobby segment "lightUsers") map { case (currentGame, _) =>
streams.excludeUsers(events.flatMap(_.hostedBy)), Homepage(
lastPostCache.apply, data,
playban, entries,
currentGame, posts,
simulIsFeaturable, tours,
blindGames events,
) simuls,
} feat,
} lead,
tWinners,
puzzle,
streams.excludeUsers(events.flatMap(_.hostedBy)),
lastPostCache.apply,
playban,
currentGame,
simulIsFeaturable,
blindGames
)
}
}
def currentGameMyTurn(user: User): Fu[Option[CurrentGame]] = def currentGameMyTurn(user: User): Fu[Option[CurrentGame]] =
gameRepo.playingRealtimeNoAi(user).flatMap { gameRepo.playingRealtimeNoAi(user).flatMap {

View File

@ -76,10 +76,9 @@ object UserInfo {
} zip } zip
ctx.userId.?? { myId => ctx.userId.?? { myId =>
relationApi.fetchBlocks(u.id, myId).mon(_.user segment "blocks") relationApi.fetchBlocks(u.id, myId).mon(_.user segment "blocks")
} dmap { } dmap { case relation ~ notes ~ followable ~ blocked =>
case relation ~ notes ~ followable ~ blocked =>
Social(relation, notes, followable, blocked) Social(relation, notes, followable, blocked)
} }
} }
case class NbGames( case class NbGames(
@ -103,14 +102,14 @@ object UserInfo {
gameCached.nbPlaying(u.id).mon(_.user segment "nbPlaying") zip gameCached.nbPlaying(u.id).mon(_.user segment "nbPlaying") zip
gameCached.nbImportedBy(u.id).mon(_.user segment "nbImported") zip gameCached.nbImportedBy(u.id).mon(_.user segment "nbImported") zip
bookmarkApi.countByUser(u).mon(_.user segment "nbBookmarks") dmap { bookmarkApi.countByUser(u).mon(_.user segment "nbBookmarks") dmap {
case crosstable ~ playing ~ imported ~ bookmark => case crosstable ~ playing ~ imported ~ bookmark =>
NbGames( NbGames(
crosstable, crosstable,
playing = playing, playing = playing,
imported = imported, imported = imported,
bookmark = bookmark bookmark = bookmark
) )
} }
} }
final class UserInfoApi( final class UserInfoApi(
@ -146,31 +145,31 @@ object UserInfo {
playbanApi.completionRate(user.id).mon(_.user segment "completion") zip playbanApi.completionRate(user.id).mon(_.user segment "completion") zip
(nbs.playing > 0) ?? isHostingSimul(user.id).mon(_.user segment "simul") zip (nbs.playing > 0) ?? isHostingSimul(user.id).mon(_.user segment "simul") zip
userCached.rankingsOf(user.id) map { userCached.rankingsOf(user.id) map {
case ratingChart ~ nbFollowers ~ nbBlockers ~ nbPosts ~ nbStudies ~ trophies ~ shields ~ revols ~ teamIds ~ isCoach ~ isStreamer ~ insightVisible ~ completionRate ~ hasSimul ~ ranks => case ratingChart ~ nbFollowers ~ nbBlockers ~ nbPosts ~ nbStudies ~ trophies ~ shields ~ revols ~ teamIds ~ isCoach ~ isStreamer ~ insightVisible ~ completionRate ~ hasSimul ~ ranks =>
new UserInfo( new UserInfo(
user = user, user = user,
ranks = ranks, ranks = ranks,
nbs = nbs, nbs = nbs,
hasSimul = hasSimul, hasSimul = hasSimul,
ratingChart = ratingChart, ratingChart = ratingChart,
nbFollowers = nbFollowers, nbFollowers = nbFollowers,
nbBlockers = nbBlockers, nbBlockers = nbBlockers,
nbPosts = nbPosts, nbPosts = nbPosts,
nbStudies = nbStudies, nbStudies = nbStudies,
trophies = trophies ::: trophyApi.roleBasedTrophies( trophies = trophies ::: trophyApi.roleBasedTrophies(
user, user,
Granter(_.PublicMod)(user), Granter(_.PublicMod)(user),
Granter(_.Developer)(user), Granter(_.Developer)(user),
Granter(_.Verified)(user) Granter(_.Verified)(user)
), ),
shields = shields, shields = shields,
revolutions = revols, revolutions = revols,
teamIds = teamIds, teamIds = teamIds,
isStreamer = isStreamer, isStreamer = isStreamer,
isCoach = isCoach, isCoach = isCoach,
insightVisible = insightVisible, insightVisible = insightVisible,
completionRate = completionRate completionRate = completionRate
) )
} }
} }
} }

View File

@ -29,10 +29,9 @@ trait ChessgroundHelper {
val pieces = val pieces =
if (ctx.pref.isBlindfold) "" if (ctx.pref.isBlindfold) ""
else else
board.pieces.map { board.pieces.map { case (pos, piece) =>
case (pos, piece) => val klass = s"${piece.color.name} ${piece.role.name}"
val klass = s"${piece.color.name} ${piece.role.name}" s"""<piece class="$klass" style="top:${top(pos)}%;left:${left(pos)}%"></piece>"""
s"""<piece class="$klass" style="top:${top(pos)}%;left:${left(pos)}%"></piece>"""
} mkString "" } mkString ""
s"$highlights$pieces" s"$highlights$pieces"
} }
@ -44,8 +43,8 @@ trait ChessgroundHelper {
chessground( chessground(
board = pov.game.board, board = pov.game.board,
orient = pov.color, orient = pov.color,
lastMove = pov.game.history.lastMove.map(_.origDest) ?? { lastMove = pov.game.history.lastMove.map(_.origDest) ?? { case (orig, dest) =>
case (orig, dest) => List(orig, dest) List(orig, dest)
} }
) )

View File

@ -140,12 +140,11 @@ trait FormHelper { self: I18nHelper =>
cls := "form-control" cls := "form-control"
)(disabled option (st.disabled := true))(validationModifiers(field))( )(disabled option (st.disabled := true))(validationModifiers(field))(
default map { option(value := "")(_) }, default map { option(value := "")(_) },
options.toSeq map { options.toSeq map { case (value, name) =>
case (value, name) => option(
option( st.value := value.toString,
st.value := value.toString, field.value.has(value.toString) option selected
field.value.has(value.toString) option selected )(name)
)(name)
} }
), ),
disabled option hidden(field) disabled option hidden(field)

View File

@ -179,13 +179,14 @@ trait GameHelper { self: I18nHelper with UserHelper with AiHelper with StringHel
case Some(_) => trans.blackLeftTheGame.txt() case Some(_) => trans.blackLeftTheGame.txt()
case None => trans.draw.txt() case None => trans.draw.txt()
} }
case S.Draw => trans.draw.txt() case S.Draw => trans.draw.txt()
case S.Outoftime => (game.turnColor, game.loser) match { case S.Outoftime =>
case (White, Some(_)) => trans.whiteTimeOut.txt() (game.turnColor, game.loser) match {
case (White, None) => trans.whiteTimeOut.txt() + " • " + trans.draw.txt() case (White, Some(_)) => trans.whiteTimeOut.txt()
case (Black, Some(_)) => trans.blackTimeOut.txt() case (White, None) => trans.whiteTimeOut.txt() + " • " + trans.draw.txt()
case (Black, None) => trans.blackTimeOut.txt() + " • " + trans.draw.txt() case (Black, Some(_)) => trans.blackTimeOut.txt()
} case (Black, None) => trans.blackTimeOut.txt() + " • " + trans.draw.txt()
}
case S.NoStart => case S.NoStart =>
val color = game.loser.fold(Color.white)(_.color).name.capitalize val color = game.loser.fold(Color.white)(_.color).name.capitalize
s"$color didn't move" s"$color didn't move"

View File

@ -57,8 +57,8 @@ trait TournamentHelper { self: I18nHelper with DateHelper with UserHelper =>
def apply(name: String): Frag = def apply(name: String): Frag =
raw { raw {
replacements.foldLeft(name) { replacements.foldLeft(name) { case (n, (from, to)) =>
case (n, (from, to)) => n.replace(from, to) n.replace(from, to)
} }
} }
} }

View File

@ -55,12 +55,12 @@ trait UserHelper { self: I18nHelper with StringHelper with NumberHelper =>
PerfType(perfKey) map { showPerfRating(u, _) } PerfType(perfKey) map { showPerfRating(u, _) }
def showBestPerf(u: User)(implicit lang: Lang): Option[Frag] = def showBestPerf(u: User)(implicit lang: Lang): Option[Frag] =
u.perfs.bestPerf map { u.perfs.bestPerf map { case (pt, perf) =>
case (pt, perf) => showPerfRating(pt, perf) showPerfRating(pt, perf)
} }
def showBestPerfs(u: User, nb: Int)(implicit lang: Lang): List[Frag] = def showBestPerfs(u: User, nb: Int)(implicit lang: Lang): List[Frag] =
u.perfs.bestPerfs(nb) map { u.perfs.bestPerfs(nb) map { case (pt, perf) =>
case (pt, perf) => showPerfRating(pt, perf) showPerfRating(pt, perf)
} }
def showRatingDiff(diff: Int): Frag = def showRatingDiff(diff: Int): Frag =
@ -219,8 +219,8 @@ trait UserHelper { self: I18nHelper with StringHelper with NumberHelper =>
withPerfRating match { withPerfRating match {
case Some(perfType) => renderRating(user.perfs(perfType)) case Some(perfType) => renderRating(user.perfs(perfType))
case _ if withBestRating => case _ if withBestRating =>
user.perfs.bestPerf ?? { user.perfs.bestPerf ?? { case (_, perf) =>
case (_, perf) => renderRating(perf) renderRating(perf)
} }
case _ => "" case _ => ""
} }
@ -266,8 +266,8 @@ trait UserHelper { self: I18nHelper with StringHelper with NumberHelper =>
val name = user.titleUsername val name = user.titleUsername
val nbGames = user.count.game val nbGames = user.count.game
val createdAt = org.joda.time.format.DateTimeFormat forStyle "M-" print user.createdAt val createdAt = org.joda.time.format.DateTimeFormat forStyle "M-" print user.createdAt
val currentRating = user.perfs.bestPerf ?? { val currentRating = user.perfs.bestPerf ?? { case (pt, perf) =>
case (pt, perf) => s" Current ${pt.trans} rating: ${perf.intRating}." s" Current ${pt.trans} rating: ${perf.intRating}."
} }
s"$name played $nbGames games since $createdAt.$currentRating" s"$name played $nbGames games since $createdAt.$currentRating"
} }

View File

@ -22,11 +22,11 @@ object activity {
a.puzzles map renderPuzzles, a.puzzles map renderPuzzles,
a.games map renderGames, a.games map renderGames,
a.posts map renderPosts, a.posts map renderPosts,
a.corresMoves map { a.corresMoves map { case (nb, povs) =>
case (nb, povs) => renderCorresMoves(nb, povs) renderCorresMoves(nb, povs)
}, },
a.corresEnds map { a.corresEnds map { case (score, povs) =>
case (score, povs) => renderCorresEnds(score, povs) renderCorresEnds(score, povs)
}, },
a.follows map renderFollows, a.follows map renderFollows,
a.simuls map renderSimuls(u), a.simuls map renderSimuls(u),
@ -85,34 +85,32 @@ object activity {
) )
private def renderGames(games: Games)(implicit ctx: Context) = private def renderGames(games: Games)(implicit ctx: Context) =
games.value.toSeq.sortBy(-_._2.size).map { games.value.toSeq.sortBy(-_._2.size).map { case (pt, score) =>
case (pt, score) => entryTag(
entryTag( iconTag(pt.iconChar),
iconTag(pt.iconChar), scoreFrag(score),
scoreFrag(score), div(
div( trans.activity.playedNbGames.plural(score.size, score.size, pt.trans),
trans.activity.playedNbGames.plural(score.size, score.size, pt.trans), score.rp.filterNot(_.isEmpty).map(ratingProgFrag)
score.rp.filterNot(_.isEmpty).map(ratingProgFrag)
)
) )
)
} }
private def renderPosts(posts: Map[lila.forum.Topic, List[lila.forum.Post]])(implicit ctx: Context) = private def renderPosts(posts: Map[lila.forum.Topic, List[lila.forum.Post]])(implicit ctx: Context) =
ctx.noKid option entryTag( ctx.noKid option entryTag(
iconTag("d"), iconTag("d"),
div( div(
posts.toSeq.map { posts.toSeq.map { case (topic, posts) =>
case (topic, posts) => val url = routes.ForumTopic.show(topic.categId, topic.slug)
val url = routes.ForumTopic.show(topic.categId, topic.slug) frag(
frag( trans.activity.postedNbMessages
trans.activity.postedNbMessages .plural(posts.size, posts.size, a(href := url)(shorten(topic.name, 70))),
.plural(posts.size, posts.size, a(href := url)(shorten(topic.name, 70))), subTag(
subTag( posts.map { post =>
posts.map { post => div(cls := "line")(a(href := routes.ForumPost.redirect(post.id))(shorten(post.text, 120)))
div(cls := "line")(a(href := routes.ForumPost.redirect(post.id))(shorten(post.text, 120))) }
}
)
) )
)
} }
) )
) )
@ -167,18 +165,17 @@ object activity {
entryTag( entryTag(
iconTag("h"), iconTag("h"),
div( div(
List(all.in.map(_ -> true), all.out.map(_ -> false)).flatten map { List(all.in.map(_ -> true), all.out.map(_ -> false)).flatten map { case (f, in) =>
case (f, in) => frag(
frag( if (in) trans.activity.gainedNbFollowers.pluralSame(f.actualNb)
if (in) trans.activity.gainedNbFollowers.pluralSame(f.actualNb) else trans.activity.followedNbPlayers.pluralSame(f.actualNb),
else trans.activity.followedNbPlayers.pluralSame(f.actualNb), subTag(
subTag( fragList(f.ids.map(id => userIdLink(id.some))),
fragList(f.ids.map(id => userIdLink(id.some))), f.nb.map { nb =>
f.nb.map { nb => frag(" and ", nb - maxSubEntries, " more")
frag(" and ", nb - maxSubEntries, " more") }
}
)
) )
)
} }
) )
) )
@ -187,24 +184,23 @@ object activity {
entryTag( entryTag(
iconTag("f"), iconTag("f"),
div( div(
simuls.groupBy(_.isHost(u.some)).toSeq.map { simuls.groupBy(_.isHost(u.some)).toSeq.map { case (isHost, simuls) =>
case (isHost, simuls) => frag(
frag( if (isHost) trans.activity.hostedNbSimuls.pluralSame(simuls.size)
if (isHost) trans.activity.hostedNbSimuls.pluralSame(simuls.size) else trans.activity.joinedNbSimuls.pluralSame(simuls.size),
else trans.activity.joinedNbSimuls.pluralSame(simuls.size), subTag(
subTag( simuls.map { s =>
simuls.map { s => div(
div( a(href := routes.Simul.show(s.id))(
a(href := routes.Simul.show(s.id))( s.name,
s.name, " simul by ",
" simul by ", userIdLink(s.hostId.some)
userIdLink(s.hostId.some) ),
), scoreFrag(Score(s.wins, s.losses, s.draws, none))
scoreFrag(Score(s.wins, s.losses, s.draws, none)) )
) }
}
)
) )
)
} }
) )
) )

View File

@ -236,12 +236,11 @@ object appeal {
div( div(
select(cls := "appeal-presets")( select(cls := "appeal-presets")(
option(st.value := "")("Presets"), option(st.value := "")("Presets"),
ps.value.map { ps.value.map { case ModPreset(name, text) =>
case ModPreset(name, text) => option(
option( st.value := text,
st.value := text, st.title := text
st.title := text )(name)
)(name)
} }
), ),
isGranted(_.Presets) option a(href := routes.Mod.presets("appeal"))("Edit presets") isGranted(_.Presets) option a(href := routes.Mod.presets("appeal"))("Edit presets")

View File

@ -48,8 +48,8 @@ object signup {
"You must agree to the Lichess policies listed below:" "You must agree to the Lichess policies listed below:"
) )
), ),
agreements.map { agreements.map { case (field, i18n) =>
case (field, i18n) => form3.checkbox(form(field), i18n()) form3.checkbox(form(field), i18n())
} }
) )

View File

@ -46,9 +46,9 @@ object layout {
s"font/lichess.woff2" s"font/lichess.woff2"
)}" as="font" type="font/woff2" crossorigin>""" + )}" as="font" type="font/woff2" crossorigin>""" +
!ctx.pref.pieceNotationIsLetter ?? !ctx.pref.pieceNotationIsLetter ??
s"""<link rel="preload" href="${assetUrl( s"""<link rel="preload" href="${assetUrl(
s"font/lichess.chess.woff2" s"font/lichess.chess.woff2"
)}" as="font" type="font/woff2" crossorigin>""" )}" as="font" type="font/woff2" crossorigin>"""
} }
private val manifests = raw( private val manifests = raw(
"""<link rel="manifest" href="/manifest.json"><meta name="twitter:site" content="@lichess">""" """<link rel="manifest" href="/manifest.json"><meta name="twitter:site" content="@lichess">"""

View File

@ -124,20 +124,19 @@ object student {
s" ($nbStudents/${lila.clas.Clas.maxStudents})" s" ($nbStudents/${lila.clas.Clas.maxStudents})"
), ),
nbStudents > (lila.clas.Clas.maxStudents / 2) option maxStudentsWarning(clas), nbStudents > (lila.clas.Clas.maxStudents / 2) option maxStudentsWarning(clas),
created map { created map { case Student.WithPassword(student, password) =>
case Student.WithPassword(student, password) => flashMessage(cls := "student-add__created")(
flashMessage(cls := "student-add__created")( strong(
strong( trans.clas.lichessProfileXCreatedForY(
trans.clas.lichessProfileXCreatedForY( userIdLink(student.userId.some, withOnline = false),
userIdLink(student.userId.some, withOnline = false), student.realName
student.realName ),
), p(trans.clas.makeSureToCopy()),
p(trans.clas.makeSureToCopy()), pre(
pre( trans.clas.studentCredentials(student.realName, usernameOrId(student.userId), password.value)
trans.clas.studentCredentials(student.realName, usernameOrId(student.userId), password.value)
)
) )
) )
)
}, },
standardFlash(), standardFlash(),
(nbStudents <= lila.clas.Clas.maxStudents) option frag( (nbStudents <= lila.clas.Clas.maxStudents) option frag(
@ -222,13 +221,12 @@ object student {
) )
), ),
tbody( tbody(
created map { created map { case Student.WithPassword(student, password) =>
case Student.WithPassword(student, password) => tr(
tr( td(student.realName),
td(student.realName), td(usernameOrId(student.userId)),
td(usernameOrId(student.userId)), td(password.value)
td(password.value) )
)
} }
) )
) )

View File

@ -76,26 +76,25 @@ object studentDashboard {
) )
), ),
tbody( tbody(
students.sortBy(-_.user.seenAt.??(_.getMillis)).map { students.sortBy(-_.user.seenAt.??(_.getMillis)).map { case Student.WithUser(student, user) =>
case Student.WithUser(student, user) => tr(
tr( td(
td( userLink(
userLink( user,
user, name = span(
name = span( strong(user.username),
strong(user.username), em(student.realName)
em(student.realName) ).some,
).some, withTitle = false
withTitle = false )
) ),
), td(dataSort := user.perfs.bestRating, cls := "rating")(cls := "rating")(user.best3Perfs.map {
td(dataSort := user.perfs.bestRating, cls := "rating")(cls := "rating")(user.best3Perfs.map { showPerfRating(user, _)
showPerfRating(user, _) }),
}), td(user.count.game.localize),
td(user.count.game.localize), td(user.perfs.puzzle.nb.localize),
td(user.perfs.puzzle.nb.localize), challengeTd(user)
challengeTd(user) )
)
} }
) )
) )

View File

@ -150,21 +150,20 @@ object teacherDashboard {
) )
), ),
tbody( tbody(
students.sortBy(_.user.username).map { students.sortBy(_.user.username).map { case s @ Student.WithUser(_, user) =>
case s @ Student.WithUser(_, user) => val prog = progress(user)
val prog = progress(user) tr(
tr( studentTd(c, s),
studentTd(c, s), td(dataSort := user.perfs(progress.perfType).intRating, cls := "rating")(
td(dataSort := user.perfs(progress.perfType).intRating, cls := "rating")( user.perfs(progress.perfType).showRatingProvisional
user.perfs(progress.perfType).showRatingProvisional ),
), td(dataSort := prog.ratingProgress)(
td(dataSort := prog.ratingProgress)( ratingProgress(prog.ratingProgress) | trans.clas.na.txt()
ratingProgress(prog.ratingProgress) | trans.clas.na.txt() ),
), td(prog.nb),
td(prog.nb), if (progress.isPuzzle) td(dataSort := prog.winRate)(prog.winRate, "%")
if (progress.isPuzzle) td(dataSort := prog.winRate)(prog.winRate, "%") else td(dataSort := prog.millis)(showPeriod(prog.period))
else td(dataSort := prog.millis)(showPeriod(prog.period)) )
)
} }
) )
) )
@ -193,24 +192,23 @@ object teacherDashboard {
) )
), ),
tbody( tbody(
students.sortBy(_.user.username).map { students.sortBy(_.user.username).map { case s @ Student.WithUser(_, user) =>
case s @ Student.WithUser(_, user) => val coord = coordScores.getOrElse(user.id, chess.Color.Map(0, 0))
val coord = coordScores.getOrElse(user.id, chess.Color.Map(0, 0)) tr(
tr( studentTd(c, s),
studentTd(c, s), td(dataSort := basicCompletion.getOrElse(user.id, 0))(
td(dataSort := basicCompletion.getOrElse(user.id, 0))( basicCompletion.getOrElse(user.id, 0).toString,
basicCompletion.getOrElse(user.id, 0).toString, "%"
"%" ),
), td(dataSort := practiceCompletion.getOrElse(user.id, 0))(
td(dataSort := practiceCompletion.getOrElse(user.id, 0))( practiceCompletion.getOrElse(user.id, 0).toString,
practiceCompletion.getOrElse(user.id, 0).toString, "%"
"%" ),
), td(dataSort := coord.white, cls := "coords")(
td(dataSort := coord.white, cls := "coords")( i(cls := "color-icon is white")(coord.white),
i(cls := "color-icon is white")(coord.white), i(cls := "color-icon is black")(coord.black)
i(cls := "color-icon is black")(coord.black)
)
) )
)
} }
) )
) )
@ -270,21 +268,20 @@ object teacherDashboard {
) )
), ),
tbody( tbody(
students.sortBy(_.user.username).map { students.sortBy(_.user.username).map { case s @ Student.WithUser(student, user) =>
case s @ Student.WithUser(student, user) => tr(
tr( studentTd(c, s),
studentTd(c, s), td(dataSort := user.perfs.bestRating, cls := "rating")(user.best3Perfs.map {
td(dataSort := user.perfs.bestRating, cls := "rating")(user.best3Perfs.map { showPerfRating(user, _)
showPerfRating(user, _) }),
}), td(user.count.game.localize),
td(user.count.game.localize), td(user.perfs.puzzle.nb),
td(user.perfs.puzzle.nb), td(dataSort := user.seenAt.map(_.getMillis.toString))(user.seenAt.map(momentFromNowOnce)),
td(dataSort := user.seenAt.map(_.getMillis.toString))(user.seenAt.map(momentFromNowOnce)), td(
td( dataSort := (if (student.managed) 1 else 0),
dataSort := (if (student.managed) 1 else 0), student.managed option iconTag("5")(title := trans.clas.managed.txt())
student.managed option iconTag("5")(title := trans.clas.managed.txt())
)
) )
)
} }
) )
) )

View File

@ -50,12 +50,11 @@ object index {
"coach-lang", "coach-lang",
lang.fold("All languages")(LangList.name), lang.fold("All languages")(LangList.name),
langSelections langSelections
.map { .map { case (code, name) =>
case (code, name) => a(
a( href := routes.Coach.search(code, order.key),
href := routes.Coach.search(code, order.key), cls := (code == lang.fold("all")(_.code)).option("current")
cls := (code == lang.fold("all")(_.code)).option("current") )(name)
)(name)
} }
), ),
views.html.base.bits.mselect( views.html.base.bits.mselect(

View File

@ -95,14 +95,13 @@ object coordinate {
List( List(
(trans.coordinates.averageScoreAsWhiteX, score.white), (trans.coordinates.averageScoreAsWhiteX, score.white),
(trans.coordinates.averageScoreAsBlackX, score.black) (trans.coordinates.averageScoreAsBlackX, score.black)
).map { ).map { case (averageScoreX, s) =>
case (averageScoreX, s) => div(cls := "chart_container")(
div(cls := "chart_container")( s.nonEmpty option frag(
s.nonEmpty option frag( p(averageScoreX(raw(s"""<strong>${"%.2f".format(s.sum.toDouble / s.size)}</strong>"""))),
p(averageScoreX(raw(s"""<strong>${"%.2f".format(s.sum.toDouble / s.size)}</strong>"""))), div(cls := "user_chart", attr("data-points") := safeJsonValue(Json toJson s))
div(cls := "user_chart", attr("data-points") := safeJsonValue(Json toJson s))
)
) )
)
} }
) )
} }

View File

@ -135,15 +135,14 @@ object categ {
td(cls := "right")(categ.nbTopics.localize), td(cls := "right")(categ.nbTopics.localize),
td(cls := "right")(categ.nbPosts.localize), td(cls := "right")(categ.nbPosts.localize),
td( td(
categ.lastPost.map { categ.lastPost.map { case (topic, post, page) =>
case (topic, post, page) => frag(
frag( a(href := s"${routes.ForumTopic.show(categ.slug, topic.slug, page)}#${post.number}")(
a(href := s"${routes.ForumTopic.show(categ.slug, topic.slug, page)}#${post.number}")( momentFromNow(post.createdAt)
momentFromNow(post.createdAt) ),
), br,
br, trans.by(authorName(post))
trans.by(authorName(post)) )
)
} }
) )
) )

View File

@ -165,28 +165,27 @@ object topic {
) )
) )
), ),
formWithCaptcha.map { formWithCaptcha.map { case (form, captcha) =>
case (form, captcha) => postForm(
postForm( cls := "form3 reply",
cls := "form3 reply", action := s"${routes.ForumPost.create(categ.slug, topic.slug, posts.currentPage)}#reply",
action := s"${routes.ForumPost.create(categ.slug, topic.slug, posts.currentPage)}#reply", novalidate
novalidate )(
)( form3.group(form("text"), trans.message()) { f =>
form3.group(form("text"), trans.message()) { f => form3.textarea(f, klass = "post-text-area")(rows := 10, bits.dataTopic := topic.id)
form3.textarea(f, klass = "post-text-area")(rows := 10, bits.dataTopic := topic.id) },
}, views.html.base.captcha(form, captcha),
views.html.base.captcha(form, captcha), form3.actions(
form3.actions( a(href := routes.ForumCateg.show(categ.slug))(trans.cancel()),
a(href := routes.ForumCateg.show(categ.slug))(trans.cancel()), isGranted(_.PublicMod) option
isGranted(_.PublicMod) option form3.submit(
form3.submit( frag("Reply as a mod"),
frag("Reply as a mod"), nameValue = (form("modIcon").name, "true").some,
nameValue = (form("modIcon").name, "true").some, icon = "".some
icon = "".some ),
), form3.submit(trans.reply())
form3.submit(trans.reply())
)
) )
)
}, },
pager pager
) )

View File

@ -21,21 +21,20 @@ object crosstable {
} }
div(cls := "crosstable")( div(cls := "crosstable")(
ct.fillSize > 0 option raw { s"""<fill style="flex:${ct.fillSize * 0.75} 1 auto"></fill>""" }, ct.fillSize > 0 option raw { s"""<fill style="flex:${ct.fillSize * 0.75} 1 auto"></fill>""" },
ct.results.zipWithIndex.map { ct.results.zipWithIndex.map { case (r, i) =>
case (r, i) => tag("povs")(
tag("povs")( cls := List(
cls := List( "sep" -> matchupSepAt.has(i),
"sep" -> matchupSepAt.has(i), "current" -> currentId.has(r.gameId)
"current" -> currentId.has(r.gameId) )
) )(ct.users.toList.map { u =>
)(ct.users.toList.map { u => val (linkClass, text) = r.winnerId match {
val (linkClass, text) = r.winnerId match { case Some(w) if w == u.id => "glpt win" -> "1"
case Some(w) if w == u.id => "glpt win" -> "1" case None => "glpt" -> "½"
case None => "glpt" -> "½" case _ => "glpt loss" -> "0"
case _ => "glpt loss" -> "0" }
} a(href := s"""${routes.Round.watcher(r.gameId, "white")}?pov=${u.id}""", cls := linkClass)(text)
a(href := s"""${routes.Round.watcher(r.gameId, "white")}?pov=${u.id}""", cls := linkClass)(text) })
})
}, },
matchup map { m => matchup map { m =>
div(cls := "crosstable__matchup", title := trans.currentMatchScore.txt())(ct.users.toList.map { u => div(cls := "crosstable__matchup", title := trans.currentMatchScore.txt())(ct.users.toList.map { u =>

View File

@ -143,14 +143,13 @@ object widgets {
aiRating(level) aiRating(level)
) )
} getOrElse { } getOrElse {
(player.nameSplit.fold[Frag](anonSpan) { (player.nameSplit.fold[Frag](anonSpan) { case (name, rating) =>
case (name, rating) => frag(
frag( span(name),
span(name), rating.map { r =>
rating.map { r => frag(br, r)
frag(br, r) }
} )
)
}) })
} }
} }

View File

@ -95,9 +95,9 @@ object home {
), ),
currentGame.map(bits.currentGameInfo) orElse currentGame.map(bits.currentGameInfo) orElse
playban.map(bits.playbanInfo) getOrElse { playban.map(bits.playbanInfo) getOrElse {
if (ctx.blind) blindLobby(blindGames) if (ctx.blind) blindLobby(blindGames)
else bits.lobbyApp else bits.lobbyApp
}, },
div(cls := "lobby__side")( div(cls := "lobby__side")(
ctx.blind option h2("Highlights"), ctx.blind option h2("Highlights"),
ctx.noKid option st.section(cls := "lobby__streams")( ctx.noKid option st.section(cls := "lobby__streams")(

View File

@ -103,36 +103,35 @@ object communication {
priv option frag( priv option frag(
h2("Recent private chats"), h2("Recent private chats"),
div(cls := "player_chats")( div(cls := "player_chats")(
players.map { players.map { case (pov, chat) =>
case (pov, chat) => div(cls := "game")(
div(cls := "game")( a(
a( href := routes.Round.player(pov.fullId),
href := routes.Round.player(pov.fullId), cls := List(
cls := List( "title" -> true,
"title" -> true, "friend_title" -> pov.game.fromFriend
"friend_title" -> pov.game.fromFriend
),
title := pov.game.fromFriend.option("Friend game")
)(
usernameOrAnon(pov.opponent.userId),
" ",
momentFromNowOnce(pov.game.movedAt)
), ),
div(cls := "chat")( title := pov.game.fromFriend.option("Friend game")
chat.lines.map { line => )(
div( usernameOrAnon(pov.opponent.userId),
cls := List( " ",
"line" -> true, momentFromNowOnce(pov.game.movedAt)
"author" -> (line.author.toLowerCase == u.id) ),
) div(cls := "chat")(
)( chat.lines.map { line =>
userIdLink(line.author.toLowerCase.some, withOnline = false, withTitle = false), div(
nbsp, cls := List(
richText(line.text) "line" -> true,
"author" -> (line.author.toLowerCase == u.id)
) )
} )(
) userIdLink(line.author.toLowerCase.some, withOnline = false, withTitle = false),
nbsp,
richText(line.text)
)
}
) )
)
} }
), ),
div(cls := "threads")( div(cls := "threads")(

View File

@ -87,15 +87,14 @@ object gamify {
) )
), ),
tbody( tbody(
leaderboards(period).zipWithIndex.map { leaderboards(period).zipWithIndex.map { case (m, i) =>
case (m, i) => tr(
tr( th(i + 1),
th(i + 1), th(userIdLink(m.modId.some, withOnline = false)),
th(userIdLink(m.modId.some, withOnline = false)), td(m.action.localize),
td(m.action.localize), td(m.report.localize),
td(m.report.localize), td(cls := "score")(m.score.localize)
td(cls := "score")(m.score.localize) )
)
} }
) )
) )

View File

@ -27,34 +27,33 @@ object permissions {
div(cls := "permission-list")( div(cls := "permission-list")(
lila.security.Permission.categorized lila.security.Permission.categorized
.filter { case (_, ps) => ps.exists(canGrant(me, _)) } .filter { case (_, ps) => ps.exists(canGrant(me, _)) }
.map { .map { case (categ, perms) =>
case (categ, perms) => st.section(
st.section( h2(categ),
h2(categ), perms
perms .filter(canGrant(me, _))
.filter(canGrant(me, _)) .map { perm =>
.map { perm => val id = s"permission-${perm.dbKey}"
val id = s"permission-${perm.dbKey}" div(
div( cls := isGranted(perm, u) option "granted",
cls := isGranted(perm, u) option "granted", title := isGranted(perm, u).?? {
title := isGranted(perm, u).?? { Permission.findGranterPackage(userPerms, perm).map { p =>
Permission.findGranterPackage(userPerms, perm).map { p => s"Granted by package: $p"
s"Granted by package: $p"
}
} }
)( }
span( )(
form3.cmnToggle( span(
id, form3.cmnToggle(
"permissions[]", id,
checked = u.roles.contains(perm.dbKey), "permissions[]",
value = perm.dbKey checked = u.roles.contains(perm.dbKey),
) value = perm.dbKey
), )
label(`for` := id)(perm.name) ),
) label(`for` := id)(perm.name)
} )
) }
)
} }
), ),
form3.actions( form3.actions(

View File

@ -24,10 +24,27 @@ object publicChat {
div(id := "communication", cls := "page-menu__content public_chat box box-pad")( div(id := "communication", cls := "page-menu__content public_chat box box-pad")(
h2("Tournament Chats"), h2("Tournament Chats"),
div(cls := "player_chats")( div(cls := "player_chats")(
tourChats.map { tourChats.map { case (tournament, chat) =>
case (tournament, chat) => div(cls := "game")(
a(cls := "title", href := routes.Tournament.show(tournament.id))(tournament.name),
div(cls := "chat")(
chat.lines.filter(_.isVisible).map { line =>
div(cls := "line")(
userIdLink(line.author.toLowerCase.some, withOnline = false, withTitle = false),
" ",
richText(line.text)
)
}
)
)
}
),
div(
h2("Simul Chats"),
div(cls := "player_chats")(
simulChats.map { case (simul, chat) =>
div(cls := "game")( div(cls := "game")(
a(cls := "title", href := routes.Tournament.show(tournament.id))(tournament.name), a(cls := "title", href := routes.Simul.show(simul.id))(simul.name),
div(cls := "chat")( div(cls := "chat")(
chat.lines.filter(_.isVisible).map { line => chat.lines.filter(_.isVisible).map { line =>
div(cls := "line")( div(cls := "line")(
@ -38,25 +55,6 @@ object publicChat {
} }
) )
) )
}
),
div(
h2("Simul Chats"),
div(cls := "player_chats")(
simulChats.map {
case (simul, chat) =>
div(cls := "game")(
a(cls := "title", href := routes.Simul.show(simul.id))(simul.name),
div(cls := "chat")(
chat.lines.filter(_.isVisible).map { line =>
div(cls := "line")(
userIdLink(line.author.toLowerCase.some, withOnline = false, withTitle = false),
" ",
richText(line.text)
)
}
)
)
} }
) )
) )

View File

@ -125,25 +125,24 @@ object search {
) )
), ),
tbody( tbody(
users.map { users.map { case lila.user.User.WithEmails(u, emails) =>
case lila.user.User.WithEmails(u, emails) => tr(
tr( td(
td( userLink(u, withBestRating = true, params = "?mod"),
userLink(u, withBestRating = true, params = "?mod"), (isGranted(_.Doxing) && isGranted(_.SetEmail)) option
(isGranted(_.Doxing) && isGranted(_.SetEmail)) option email(emails.list.map(_.value).mkString(", "))
email(emails.list.map(_.value).mkString(", ")) ),
), td(u.count.game.localize),
td(u.count.game.localize), td(
td( u.marks.alt option mark("ALT"),
u.marks.alt option mark("ALT"), u.marks.engine option mark("ENGINE"),
u.marks.engine option mark("ENGINE"), u.marks.boost option mark("BOOSTER"),
u.marks.boost option mark("BOOSTER"), u.marks.troll option mark("SHADOWBAN")
u.marks.troll option mark("SHADOWBAN") ),
), td(u.disabled option mark("CLOSED")),
td(u.disabled option mark("CLOSED")), td(momentFromNow(u.createdAt)),
td(momentFromNow(u.createdAt)), td(u.seenAt.map(momentFromNow(_)))
td(u.seenAt.map(momentFromNow(_))) )
)
} }
) )
) )

View File

@ -100,11 +100,10 @@ object bits {
} }
), ),
div(cls := "now-playing")( div(cls := "now-playing")(
playing.partition(_.isMyTurn) pipe { playing.partition(_.isMyTurn) pipe { case (myTurn, otherTurn) =>
case (myTurn, otherTurn) => (myTurn ++ otherTurn.take(6 - myTurn.size)) take 9 map {
(myTurn ++ otherTurn.take(6 - myTurn.size)) take 9 map { views.html.game.mini(_)
views.html.game.mini(_) }
}
} }
) )
) )

View File

@ -41,8 +41,8 @@ private object bits {
renderLabel(form("variant"), trans.variant()), renderLabel(form("variant"), trans.variant()),
renderSelect( renderSelect(
form("variant"), form("variant"),
variants.filter { variants.filter { case (id, _, _) =>
case (id, _, _) => ctx.noBlind || lila.game.Game.blindModeVariants.exists(_.id.toString == id) ctx.noBlind || lila.game.Game.blindModeVariants.exists(_.id.toString == id)
} }
) )
) )
@ -53,34 +53,32 @@ private object bits {
compare: (String, String) => Boolean = (a, b) => a == b compare: (String, String) => Boolean = (a, b) => a == b
) = ) =
select(id := s"$prefix${field.id}", name := field.name)( select(id := s"$prefix${field.id}", name := field.name)(
options.map { options.map { case (value, name, title) =>
case (value, name, title) => option(
option( st.value := value,
st.value := value, st.title := title,
st.title := title, field.value.exists(v => compare(v, value)) option selected
field.value.exists(v => compare(v, value)) option selected )(name)
)(name)
} }
) )
def renderRadios(field: Field, options: Seq[SelectChoice]) = def renderRadios(field: Field, options: Seq[SelectChoice]) =
st.group(cls := "radio")( st.group(cls := "radio")(
options.map { options.map { case (key, name, hint) =>
case (key, name, hint) => div(
div( input(
input( tpe := "radio",
tpe := "radio", id := s"$prefix${field.id}_$key",
id := s"$prefix${field.id}_$key", st.name := field.name,
st.name := field.name, value := key,
value := key, field.value.has(key) option checked
field.value.has(key) option checked ),
), label(
label( cls := "required",
cls := "required", title := hint,
title := hint, `for` := s"$prefix${field.id}_$key"
`for` := s"$prefix${field.id}_$key" )(name)
)(name) )
)
} }
) )

View File

@ -76,11 +76,10 @@ object filter {
options: Seq[(Any, String, Option[String])], options: Seq[(Any, String, Option[String])],
checks: Set[String] = Set.empty checks: Set[String] = Set.empty
): Frag = ): Frag =
options.zipWithIndex.map { options.zipWithIndex.map { case ((value, text, hint), index) =>
case ((value, text, hint), index) => div(cls := "checkable")(
div(cls := "checkable")( renderCheckbox(form, key, index, value.toString, raw(text), hint, checks)
renderCheckbox(form, key, index, value.toString, raw(text), hint, checks) )
)
} }
private def renderCheckbox( private def renderCheckbox(

View File

@ -68,9 +68,8 @@ object forms {
renderRadios(form("level"), lila.setup.AiConfig.levelChoices) renderRadios(form("level"), lila.setup.AiConfig.levelChoices)
), ),
div(cls := "ai_info")( div(cls := "ai_info")(
ratings.toList.map { ratings.toList.map { case (level, _) =>
case (level, _) => div(cls := s"${prefix}level_$level")(trans.aiNameLevelAiLevel("A.I.", level))
div(cls := s"${prefix}level_$level")(trans.aiNameLevelAiLevel("A.I.", level))
} }
) )
) )
@ -138,15 +137,14 @@ object forms {
if (ctx.blind) submitButton("Create the game") if (ctx.blind) submitButton("Create the game")
else else
div(cls := "color-submits")( div(cls := "color-submits")(
translatedSideChoices.map { translatedSideChoices.map { case (key, name, _) =>
case (key, name, _) => submitButton(
submitButton( (typ == "hook") option disabled,
(typ == "hook") option disabled, title := name,
title := name, cls := s"color-submits__button button button-metal $key",
cls := s"color-submits__button button button-metal $key", st.name := "color",
st.name := "color", value := key
value := key )(i)
)(i)
} }
) )
) )

View File

@ -158,30 +158,29 @@ object contact {
"trolling" -> trolling(), "trolling" -> trolling(),
"insults" -> insults(), "insults" -> insults(),
"some other reason" -> otherReason() "some other reason" -> otherReason()
).map { ).map { case (reason, name) =>
case (reason, name) => Leaf(
Leaf( reason,
reason, frag("Report a player for ", name),
frag("Report a player for ", name), frag(
frag( p(
p( a(href := routes.Report.form())(toReportAPlayer(name)),
a(href := routes.Report.form())(toReportAPlayer(name)), "."
"." ),
), p(
p( youCanAlsoReachReportPage(button(cls := "thin button button-empty", dataIcon := "!"))
youCanAlsoReachReportPage(button(cls := "thin button button-empty", dataIcon := "!")) ),
), p(
p( doNotMessageModerators(),
doNotMessageModerators(), br,
br, doNotReportInForum(),
doNotReportInForum(), br,
br, doNotSendReportEmails(),
doNotSendReportEmails(), br,
br, onlyReports()
onlyReports()
)
) )
) )
)
} }
), ),
Branch( Branch(

View File

@ -89,40 +89,39 @@ object edit extends Context.ToLang {
) )
) )
), ),
modData.map { modData.map { case (log, notes) =>
case (log, notes) => div(cls := "mod_log status")(
div(cls := "mod_log status")( strong(cls := "text", dataIcon := "!")(
strong(cls := "text", dataIcon := "!")( "Moderation history",
"Moderation history", log.isEmpty option ": nothing to show."
log.isEmpty option ": nothing to show." ),
), log.nonEmpty option ul(
log.nonEmpty option ul( log.map { e =>
log.map { e => li(
li( userIdLink(e.mod.some, withTitle = false),
userIdLink(e.mod.some, withTitle = false), " ",
" ", b(e.showAction),
b(e.showAction), " ",
" ", e.details,
e.details, " ",
" ", momentFromNow(e.date)
momentFromNow(e.date) )
) }
} ),
), br,
br, strong(cls := "text", dataIcon := "!")(
strong(cls := "text", dataIcon := "!")( "Moderator notes",
"Moderator notes", notes.isEmpty option ": nothing to show."
notes.isEmpty option ": nothing to show." ),
), notes.nonEmpty option ul(
notes.nonEmpty option ul( notes.map { note =>
notes.map { note => li(
li( p(cls := "meta")(userIdLink(note.from.some), " ", momentFromNow(note.date)),
p(cls := "meta")(userIdLink(note.from.some), " ", momentFromNow(note.date)), p(cls := "text")(richText(note.text))
p(cls := "text")(richText(note.text)) )
) }
}
)
) )
)
}, },
postForm( postForm(
cls := "form3", cls := "form3",

View File

@ -73,25 +73,24 @@ object timeline {
a(href := routes.Simul.show(simulId))(simulName) a(href := routes.Simul.show(simulId))(simulName)
) )
case GameEnd(playerId, opponent, win, perfKey) => case GameEnd(playerId, opponent, win, perfKey) =>
lila.rating.PerfType(perfKey) map { lila.rating.PerfType(perfKey) map { perf =>
perf => (win match {
(win match { case Some(true) => trans.victoryVsYInZ
case Some(true) => trans.victoryVsYInZ case Some(false) => trans.defeatVsYInZ
case Some(false) => trans.defeatVsYInZ case None => trans.drawVsYInZ
case None => trans.drawVsYInZ })(
})( a(
a( href := routes.Round.player(playerId),
href := routes.Round.player(playerId), dataIcon := perf.iconChar,
dataIcon := perf.iconChar, cls := "text glpt"
cls := "text glpt" )(win match {
)(win match { case Some(true) => trans.victory()
case Some(true) => trans.victory() case Some(false) => trans.defeat()
case Some(false) => trans.defeat() case None => trans.draw()
case None => trans.draw() }),
}), userIdLink(opponent, withOnline = false),
userIdLink(opponent, withOnline = false), perf.trans
perf.trans )
)
} }
case StudyCreate(userId, studyId, studyName) => case StudyCreate(userId, studyId, studyName) =>
trans.xCreatesStudyY( trans.xCreatesStudyY(

View File

@ -22,22 +22,21 @@ object shields {
div(cls := "page-menu__content box box-pad")( div(cls := "page-menu__content box box-pad")(
h1("Tournament shields"), h1("Tournament shields"),
div(cls := "tournament-shields")( div(cls := "tournament-shields")(
history.sorted.map { history.sorted.map { case (categ, awards) =>
case (categ, awards) => section(
section( h2(
h2( a(href := routes.Tournament.categShields(categ.key))(
a(href := routes.Tournament.categShields(categ.key))( span(cls := "shield-trophy")(categ.iconChar.toString),
span(cls := "shield-trophy")(categ.iconChar.toString), categ.name
categ.name )
) ),
), ol(awards.map { aw =>
ol(awards.map { aw => li(
li( userIdLink(aw.owner.value.some),
userIdLink(aw.owner.value.some), a(href := routes.Tournament.show(aw.tourId))(showDate(aw.date))
a(href := routes.Tournament.show(aw.tourId))(showDate(aw.date)) )
) })
}) )
)
} }
) )
) )

View File

@ -505,57 +505,56 @@ object mod {
) )
), ),
tbody( tbody(
othersWithEmail.others.map { othersWithEmail.others.map { case other @ UserSpy.OtherUser(o, _, _) =>
case other @ UserSpy.OtherUser(o, _, _) => val dox = isGranted(_.Doxing) || (o.lameOrAlt && !o.hasTitle)
val dox = isGranted(_.Doxing) || (o.lameOrAlt && !o.hasTitle) val userNotes =
val userNotes = notes.filter(n => n.to == o.id && (ctx.me.exists(n.isFrom) || isGranted(_.Doxing)))
notes.filter(n => n.to == o.id && (ctx.me.exists(n.isFrom) || isGranted(_.Doxing))) tr(
tr( dataTags := s"${other.ips.mkString(" ")} ${other.fps.mkString(" ")}",
dataTags := s"${other.ips.mkString(" ")} ${other.fps.mkString(" ")}", cls := (o == u) option "same"
cls := (o == u) option "same" )(
if (dox || o == u) td(dataSort := o.id)(userLink(o, withBestRating = true, params = "?mod"))
else td,
if (dox) td(othersWithEmail emailValueOf o)
else td,
td(
// show prints and ips separately
dataSort := other.score + (other.ips.nonEmpty ?? 1000000) + (other.fps.nonEmpty ?? 3000000)
)( )(
if (dox || o == u) td(dataSort := o.id)(userLink(o, withBestRating = true, params = "?mod")) List(other.ips.size -> "IP", other.fps.size -> "Print")
else td, .collect {
if (dox) td(othersWithEmail emailValueOf o) case (nb, name) if nb > 0 => s"$nb $name"
else td, }
td( .mkString(", ")
// show prints and ips separately ),
dataSort := other.score + (other.ips.nonEmpty ?? 1000000) + (other.fps.nonEmpty ?? 3000000) td(dataSort := o.count.game)(o.count.game.localize),
)( markTd(~bans.get(o.id), playban(cls := "text")(~bans.get(o.id))),
List(other.ips.size -> "IP", other.fps.size -> "Print") markTd(o.marks.alt ?? 1, alt),
.collect { markTd(o.marks.troll ?? 1, shadowban),
case (nb, name) if nb > 0 => s"$nb $name" markTd(o.marks.boost ?? 1, boosting),
} markTd(o.marks.engine ?? 1, engine),
.mkString(", ") markTd(o.disabled ?? 1, closed),
), markTd(o.marks.reportban ?? 1, reportban),
td(dataSort := o.count.game)(o.count.game.localize), userNotes.nonEmpty option {
markTd(~bans.get(o.id), playban(cls := "text")(~bans.get(o.id))), td(dataSort := userNotes.size)(
markTd(o.marks.alt ?? 1, alt), a(href := s"${routes.User.show(o.username)}?notes")(
markTd(o.marks.troll ?? 1, shadowban), notesText(
markTd(o.marks.boost ?? 1, boosting), title := s"Notes from ${userNotes.map(_.from).map(usernameOrId).mkString(", ")}",
markTd(o.marks.engine ?? 1, engine), cls := "is-green"
markTd(o.disabled ?? 1, closed), ),
markTd(o.marks.reportban ?? 1, reportban), userNotes.size
userNotes.nonEmpty option {
td(dataSort := userNotes.size)(
a(href := s"${routes.User.show(o.username)}?notes")(
notesText(
title := s"Notes from ${userNotes.map(_.from).map(usernameOrId).mkString(", ")}",
cls := "is-green"
),
userNotes.size
)
) )
} getOrElse td(dataSort := 0),
td(dataSort := o.createdAt.getMillis)(momentFromNowServer(o.createdAt)),
td(dataSort := o.seenAt.map(_.getMillis.toString))(o.seenAt.map(momentFromNowServer)),
isGranted(_.CloseAccount) option td(
o.enabled option button(
cls := "button button-empty button-thin button-red mark-alt",
href := routes.Mod.alt(o.id, !o.marks.alt)
)("ALT")
) )
} getOrElse td(dataSort := 0),
td(dataSort := o.createdAt.getMillis)(momentFromNowServer(o.createdAt)),
td(dataSort := o.seenAt.map(_.getMillis.toString))(o.seenAt.map(momentFromNowServer)),
isGranted(_.CloseAccount) option td(
o.enabled option button(
cls := "button button-empty button-thin button-red mark-alt",
href := routes.Mod.alt(o.id, !o.marks.alt)
)("ALT")
) )
)
} }
) )
), ),

View File

@ -29,14 +29,13 @@ object top {
h1(a(href := routes.User.list(), dataIcon := "I"), title), h1(a(href := routes.User.list(), dataIcon := "I"), title),
table(cls := "slist slist-pad")( table(cls := "slist slist-pad")(
tbody( tbody(
users.zipWithIndex.map { users.zipWithIndex.map { case (u, i) =>
case (u, i) => tr(
tr( td(i + 1),
td(i + 1), td(lightUserLink(u.user)),
td(lightUserLink(u.user)), td(u.rating),
td(u.rating), td(ratingProgress(u.progress))
td(ratingProgress(u.progress)) )
)
} }
) )
) )

View File

@ -38,15 +38,14 @@ object chart {
) )
), ),
tbody( tbody(
data.perfResults.map { data.perfResults.map { case (pt, res) =>
case (pt, res) => tr(
tr( th(iconTag(pt.iconChar, pt.trans)),
th(iconTag(pt.iconChar, pt.trans)), td(res.nb.localize),
td(res.nb.localize), td(res.points.median.map(_.toInt)),
td(res.points.median.map(_.toInt)), td(res.points.sum.localize),
td(res.points.sum.localize), td(res.rankPercentMedian, "%")
td(res.rankPercentMedian, "%") )
)
}, },
tr( tr(
th("Total"), th("Total"),

View File

@ -53,11 +53,11 @@ final class ActivityReadApi(
.mon(_.user segment "activity.posts") dmap some .mon(_.user segment "activity.posts") dmap some
} }
practice = (for { practice = (for {
p <- a.practice p <- a.practice
struct <- practiceStructure struct <- practiceStructure
} yield p.value flatMap { } yield p.value flatMap { case (studyId, nb) =>
case (studyId, nb) => struct study studyId map (_ -> nb) struct study studyId map (_ -> nb)
} toMap) } toMap)
postView = posts.map { p => postView = posts.map { p =>
p.groupBy(_.topic) p.groupBy(_.topic)
.view .view

View File

@ -22,12 +22,11 @@ private object BSONHandlers {
implicit lazy val activityIdHandler = { implicit lazy val activityIdHandler = {
val sep = ':' val sep = ':'
tryHandler[Id]( tryHandler[Id](
{ { case BSONString(v) =>
case BSONString(v) => v split sep match {
v split sep match { case Array(userId, dayStr) => Success(Id(userId, Day(Integer.parseInt(dayStr))))
case Array(userId, dayStr) => Success(Id(userId, Day(Integer.parseInt(dayStr)))) case _ => handlerBadValue(s"Invalid activity id $v")
case _ => handlerBadValue(s"Invalid activity id $v") }
}
}, },
id => BSONString(s"${id.userId}$sep${id.day.value}") id => BSONString(s"${id.userId}$sep${id.day.value}")
) )
@ -35,12 +34,11 @@ private object BSONHandlers {
implicit private lazy val ratingHandler = BSONIntegerHandler.as[Rating](Rating.apply, _.value) implicit private lazy val ratingHandler = BSONIntegerHandler.as[Rating](Rating.apply, _.value)
implicit private lazy val ratingProgHandler = tryHandler[RatingProg]( implicit private lazy val ratingProgHandler = tryHandler[RatingProg](
{ { case v: BSONArray =>
case v: BSONArray => for {
for { before <- v.getAsTry[Rating](0)
before <- v.getAsTry[Rating](0) after <- v.getAsTry[Rating](1)
after <- v.getAsTry[Rating](1) } yield RatingProg(before, after)
} yield RatingProg(before, after)
}, },
o => BSONArray(o.before, o.after) o => BSONArray(o.before, o.after)
) )

View File

@ -32,8 +32,8 @@ final class JsonView(
implicit val scoreWrites = Json.writes[Score] implicit val scoreWrites = Json.writes[Score]
implicit val gamesWrites = OWrites[Games] { games => implicit val gamesWrites = OWrites[Games] { games =>
JsObject { JsObject {
games.value.toList.sortBy(-_._2.size).map { games.value.toList.sortBy(-_._2.size).map { case (pt, score) =>
case (pt, score) => pt.key -> scoreWrites.writes(score) pt.key -> scoreWrites.writes(score)
} }
} }
} }
@ -104,43 +104,41 @@ final class JsonView(
.add("tournaments", a.tours) .add("tournaments", a.tours)
.add( .add(
"practice", "practice",
a.practice.map(_.toList.sortBy(-_._2) map { a.practice.map(_.toList.sortBy(-_._2) map { case (study, nb) =>
case (study, nb) => Json.obj(
Json.obj( "url" -> s"/practice/-/${study.slug}/${study.id}",
"url" -> s"/practice/-/${study.slug}/${study.id}", "name" -> study.name,
"name" -> study.name, "nbPositions" -> nb
"nbPositions" -> nb )
)
}) })
) )
.add("simuls", a.simuls.map(_ map simulWrites(user).writes)) .add("simuls", a.simuls.map(_ map simulWrites(user).writes))
.add( .add(
"correspondenceMoves", "correspondenceMoves",
a.corresMoves.map { a.corresMoves.map { case (nb, povs) =>
case (nb, povs) => Json.obj("nb" -> nb, "games" -> povs) Json.obj("nb" -> nb, "games" -> povs)
} }
) )
.add( .add(
"correspondenceEnds", "correspondenceEnds",
a.corresEnds.map { a.corresEnds.map { case (score, povs) =>
case (score, povs) => Json.obj("score" -> score, "games" -> povs) Json.obj("score" -> score, "games" -> povs)
} }
) )
.add("follows" -> a.follows) .add("follows" -> a.follows)
.add("studies" -> a.studies) .add("studies" -> a.studies)
.add("teams" -> a.teams) .add("teams" -> a.teams)
.add("posts" -> a.posts.map(_ map { .add("posts" -> a.posts.map(_ map { case (topic, posts) =>
case (topic, posts) => Json.obj(
Json.obj( "topicUrl" -> s"/forum/${topic.categId}/${topic.slug}",
"topicUrl" -> s"/forum/${topic.categId}/${topic.slug}", "topicName" -> topic.name,
"topicName" -> topic.name, "posts" -> posts.map { p =>
"posts" -> posts.map { p => Json.obj(
Json.obj( "url" -> s"/forum/redirect/post/${p.id}",
"url" -> s"/forum/redirect/post/${p.id}", "text" -> p.text.take(500)
"text" -> p.text.take(500) )
) }
} )
)
})) }))
.add("patron" -> a.patron) .add("patron" -> a.patron)
.add("stream" -> a.stream) .add("stream" -> a.stream)

View File

@ -23,12 +23,12 @@ sealed trait Advice {
case MateAdvice(seq, _, _, _) => seq.desc case MateAdvice(seq, _, _, _) => seq.desc
case CpAdvice(judgment, _, _) => judgment.toString case CpAdvice(judgment, _, _) => judgment.toString
}) + "." + { }) + "." + {
withBestMove ?? { withBestMove ?? {
info.variation.headOption ?? { move => info.variation.headOption ?? { move =>
s" $move was best." s" $move was best."
}
} }
} }
}
def evalComment: Option[String] = { def evalComment: Option[String] = {
List(prev.evalComment, info.evalComment).flatten mkString " → " List(prev.evalComment, info.evalComment).flatten mkString " → "

View File

@ -25,17 +25,17 @@ final class Analyser(
gameRepo.setAnalysed(game.id) gameRepo.setAnalysed(game.id)
analysisRepo.save(analysis) >> analysisRepo.save(analysis) >>
sendAnalysisProgress(analysis, complete = true) >>- { sendAnalysisProgress(analysis, complete = true) >>- {
Bus.publish(actorApi.AnalysisReady(game, analysis), "analysisReady") Bus.publish(actorApi.AnalysisReady(game, analysis), "analysisReady")
Bus.publish(InsertGame(game), "gameSearchInsert") Bus.publish(InsertGame(game), "gameSearchInsert")
requesterApi save analysis requesterApi save analysis
} }
} }
} }
case Some(_) => case Some(_) =>
analysisRepo.save(analysis) >> analysisRepo.save(analysis) >>
sendAnalysisProgress(analysis, complete = true) >>- { sendAnalysisProgress(analysis, complete = true) >>- {
requesterApi save analysis requesterApi save analysis
} }
} }
def progress(analysis: Analysis): Funit = sendAnalysisProgress(analysis, complete = false) def progress(analysis: Analysis): Funit = sendAnalysisProgress(analysis, complete = false)
@ -44,20 +44,19 @@ final class Analyser(
analysis.studyId match { analysis.studyId match {
case None => case None =>
gameRepo gameWithInitialFen analysis.id map { gameRepo gameWithInitialFen analysis.id map {
_ ?? { _ ?? { case (game, initialFen) =>
case (game, initialFen) => Bus.publish(
Bus.publish( TellIfExists(
TellIfExists( analysis.id,
analysis.id, actorApi.AnalysisProgress(
actorApi.AnalysisProgress( analysis = analysis,
analysis = analysis, game = game,
game = game, variant = game.variant,
variant = game.variant, initialFen = initialFen | FEN(game.variant.initialFen)
initialFen = initialFen | FEN(game.variant.initialFen) )
) ),
), "roundSocket"
"roundSocket" )
)
} }
} }
case Some(_) => case Some(_) =>

View File

@ -21,11 +21,10 @@ case class Analysis(
def providedByLichess = by exists (_ startsWith "lichess-") def providedByLichess = by exists (_ startsWith "lichess-")
lazy val infoAdvices: InfoAdvices = { lazy val infoAdvices: InfoAdvices = {
(Info.start(startPly) :: infos) sliding 2 collect { (Info.start(startPly) :: infos) sliding 2 collect { case List(prev, info) =>
case List(prev, info) => info -> {
info -> { info.hasVariation ?? Advice(prev, info)
info.hasVariation ?? Advice(prev, info) }
}
} }
}.toList }.toList

View File

@ -21,8 +21,8 @@ final class AnalysisRepo(coll: Coll)(implicit ec: scala.concurrent.ExecutionCont
def associateToGames(games: List[Game]): Fu[List[Analysis.Analyzed]] = def associateToGames(games: List[Game]): Fu[List[Analysis.Analyzed]] =
byIds(games.map(_.id)) map { as => byIds(games.map(_.id)) map { as =>
games zip as collect { games zip as collect { case (game, Some(analysis)) =>
case (game, Some(analysis)) => Analysis.Analyzed(game, analysis) Analysis.Analyzed(game, analysis)
} }
} }

View File

@ -33,21 +33,20 @@ final class Annotator(netDomain: lila.common.config.NetDomain) {
} }
private def annotateTurns(p: Pgn, advices: List[Advice]): Pgn = private def annotateTurns(p: Pgn, advices: List[Advice]): Pgn =
advices.foldLeft(p) { advices.foldLeft(p) { case (pgn, advice) =>
case (pgn, advice) => pgn.updateTurn(
pgn.updateTurn( advice.turn,
advice.turn, turn =>
turn => turn.update(
turn.update( advice.color,
advice.color, move =>
move => move.copy(
move.copy( glyphs = Glyphs.fromList(advice.judgment.glyph :: Nil),
glyphs = Glyphs.fromList(advice.judgment.glyph :: Nil), comments = advice.makeComment(withEval = true, withBestMove = true) :: move.comments,
comments = advice.makeComment(withEval = true, withBestMove = true) :: move.comments, variations = makeVariation(turn, advice) :: Nil
variations = makeVariation(turn, advice) :: Nil )
) )
) )
)
} }
private def makeVariation(turn: Turn, advice: Advice): List[Turn] = private def makeVariation(turn: Turn, advice: Advice): List[Turn] =

View File

@ -80,8 +80,8 @@ object Info {
} }
def decodeList(str: String, fromPly: Int): Option[List[Info]] = { def decodeList(str: String, fromPly: Int): Option[List[Info]] = {
str.split(listSeparator).toList.zipWithIndex map { str.split(listSeparator).toList.zipWithIndex map { case (infoStr, index) =>
case (infoStr, index) => decode(index + 1 + fromPly, infoStr) decode(index + 1 + fromPly, infoStr)
} }
}.sequence }.sequence

View File

@ -8,29 +8,28 @@ import lila.tree.Eval.JsonHandlers._
object JsonView { object JsonView {
def moves(analysis: Analysis, withGlyph: Boolean = true) = def moves(analysis: Analysis, withGlyph: Boolean = true) =
JsArray(analysis.infoAdvices map { JsArray(analysis.infoAdvices map { case (info, adviceOption) =>
case (info, adviceOption) => Json
Json .obj()
.obj() .add("eval" -> info.cp)
.add("eval" -> info.cp) .add("mate" -> info.mate)
.add("mate" -> info.mate) .add("best" -> info.best.map(_.uci))
.add("best" -> info.best.map(_.uci)) .add("variation" -> info.variation.nonEmpty.option(info.variation mkString " "))
.add("variation" -> info.variation.nonEmpty.option(info.variation mkString " ")) .add("judgment" -> adviceOption.map { a =>
.add("judgment" -> adviceOption.map { a => Json
Json .obj(
.obj( "name" -> a.judgment.name,
"name" -> a.judgment.name, "comment" -> a.makeComment(withEval = false, withBestMove = true)
"comment" -> a.makeComment(withEval = false, withBestMove = true) )
) .add(
.add( "glyph" -> withGlyph.option(
"glyph" -> withGlyph.option( Json.obj(
Json.obj( "name" -> a.judgment.glyph.name,
"name" -> a.judgment.glyph.name, "symbol" -> a.judgment.glyph.symbol
"symbol" -> a.judgment.glyph.symbol
)
) )
) )
}) )
})
}) })
import Accuracy.povToPovLike import Accuracy.povToPovLike
@ -40,8 +39,8 @@ object JsonView {
.find(_._1 == pov.color) .find(_._1 == pov.color)
.map(_._2) .map(_._2)
.map(s => .map(s =>
JsObject(s map { JsObject(s map { case (nag, nb) =>
case (nag, nb) => nag.toString.toLowerCase -> JsNumber(nb) nag.toString.toLowerCase -> JsNumber(nb)
}).add("acpl" -> lila.analyse.Accuracy.mean(pov, analysis)) }).add("acpl" -> lila.analyse.Accuracy.mean(pov, analysis))
) )

View File

@ -65,8 +65,8 @@ final private[api] class Cli(
private def run(args: List[String]): Fu[String] = { private def run(args: List[String]): Fu[String] = {
(processors lift args) | fufail("Unknown command: " + args.mkString(" ")) (processors lift args) | fufail("Unknown command: " + args.mkString(" "))
} recover { } recover { case e: Exception =>
case e: Exception => "ERROR " + e "ERROR " + e
} }
private def processors = private def processors =

View File

@ -90,8 +90,8 @@ final class Env(
private lazy val linkCheck = wire[LinkCheck] private lazy val linkCheck = wire[LinkCheck]
Bus.subscribeFun("chatLinkCheck") { Bus.subscribeFun("chatLinkCheck") { case GetLinkCheck(line, source, promise) =>
case GetLinkCheck(line, source, promise) => promise completeWith linkCheck(line, source) promise completeWith linkCheck(line, source)
} }
system.scheduler.scheduleWithFixedDelay(1 minute, 1 minute) { () => system.scheduler.scheduleWithFixedDelay(1 minute, 1 minute) { () =>

View File

@ -183,9 +183,8 @@ final private[api] class GameApi(
else fuccess(List.fill(games.size)(none[Analysis])) else fuccess(List.fill(games.size)(none[Analysis]))
allAnalysis flatMap { analysisOptions => allAnalysis flatMap { analysisOptions =>
(games map gameRepo.initialFen).sequenceFu map { initialFens => (games map gameRepo.initialFen).sequenceFu map { initialFens =>
games zip analysisOptions zip initialFens map { games zip analysisOptions zip initialFens map { case ((g, analysisOption), initialFen) =>
case ((g, analysisOption), initialFen) => gameToJson(g, analysisOption, initialFen, checkToken(withFlags))
gameToJson(g, analysisOption, initialFen, checkToken(withFlags))
} }
} }
} }

View File

@ -68,18 +68,17 @@ final class GameApiV2(
private val fileR = """[\s,]""".r private val fileR = """[\s,]""".r
def filename(game: Game, format: Format): Fu[String] = def filename(game: Game, format: Format): Fu[String] =
gameLightUsers(game) map { gameLightUsers(game) map { case List(wu, bu) =>
case List(wu, bu) => fileR.replaceAllIn(
fileR.replaceAllIn( "lichess_pgn_%s_%s_vs_%s.%s.%s".format(
"lichess_pgn_%s_%s_vs_%s.%s.%s".format( Tag.UTCDate.format.print(game.createdAt),
Tag.UTCDate.format.print(game.createdAt), pgnDump.dumper.player(game.whitePlayer, wu),
pgnDump.dumper.player(game.whitePlayer, wu), pgnDump.dumper.player(game.blackPlayer, bu),
pgnDump.dumper.player(game.blackPlayer, bu), game.id,
game.id, format.toString.toLowerCase
format.toString.toLowerCase ),
), "_"
"_" )
)
} }
def filename(tour: Tournament, format: Format): String = def filename(tour: Tournament, format: Format): String =
fileR.replaceAllIn( fileR.replaceAllIn(
@ -154,44 +153,42 @@ final class GameApiV2(
playerRepo.teamsOfPlayers(config.tournamentId, pairings.flatMap(_.users).distinct).dmap(_.toMap) playerRepo.teamsOfPlayers(config.tournamentId, pairings.flatMap(_.users).distinct).dmap(_.toMap)
} flatMap { playerTeams => } flatMap { playerTeams =>
gameRepo.gameOptionsFromSecondary(pairings.map(_.gameId)) map { gameRepo.gameOptionsFromSecondary(pairings.map(_.gameId)) map {
_.zip(pairings) collect { _.zip(pairings) collect { case (Some(game), pairing) =>
case (Some(game), pairing) => import cats.implicits._
import cats.implicits._ (
game,
pairing,
( (
game, playerTeams.get(pairing.user1),
pairing, playerTeams.get(
( pairing.user2
playerTeams.get(pairing.user1), )
playerTeams.get( ) mapN chess.Color.Map.apply[String]
pairing.user2 )
)
) mapN chess.Color.Map.apply[String]
)
} }
} }
} }
} }
.mapConcat(identity) .mapConcat(identity)
.mapAsync(4) { .mapAsync(4) { case (game, pairing, teams) =>
case (game, pairing, teams) => enrich(config.flags)(game) dmap { (_, pairing, teams) } enrich(config.flags)(game) dmap { (_, pairing, teams) }
} }
.mapAsync(4) { .mapAsync(4) { case ((game, fen, analysis), pairing, teams) =>
case ((game, fen, analysis), pairing, teams) => config.format match {
config.format match { case Format.PGN => pgnDump.formatter(config.flags)(game, fen, analysis, teams, none)
case Format.PGN => pgnDump.formatter(config.flags)(game, fen, analysis, teams, none) case Format.JSON =>
case Format.JSON => def addBerserk(color: chess.Color)(json: JsObject) =
def addBerserk(color: chess.Color)(json: JsObject) = if (pairing berserkOf color)
if (pairing berserkOf color) json deepMerge Json.obj(
json deepMerge Json.obj( "players" -> Json.obj(color.name -> Json.obj("berserk" -> true))
"players" -> Json.obj(color.name -> Json.obj("berserk" -> true)) )
) else json
else json toJson(game, fen, analysis, config.flags, teams) dmap
toJson(game, fen, analysis, config.flags, teams) dmap addBerserk(chess.White) dmap
addBerserk(chess.White) dmap addBerserk(chess.Black) dmap { json =>
addBerserk(chess.Black) dmap { json =>
s"${Json.stringify(json)}\n" s"${Json.stringify(json)}\n"
} }
} }
} }
} }
} }
@ -207,22 +204,21 @@ final class GameApiV2(
.mapAsync(1)(gameRepo.gamesFromSecondary) .mapAsync(1)(gameRepo.gamesFromSecondary)
.mapConcat(identity) .mapConcat(identity)
.mapAsync(4)(enrich(config.flags)) .mapAsync(4)(enrich(config.flags))
.mapAsync(4) { .mapAsync(4) { case (game, fen, analysis) =>
case (game, fen, analysis) => config.format match {
config.format match { case Format.PGN => pgnDump.formatter(config.flags)(game, fen, analysis, none, none)
case Format.PGN => pgnDump.formatter(config.flags)(game, fen, analysis, none, none) case Format.JSON =>
case Format.JSON => toJson(game, fen, analysis, config.flags, None) dmap { json =>
toJson(game, fen, analysis, config.flags, None) dmap { json => s"${Json.stringify(json)}\n"
s"${Json.stringify(json)}\n" }
} }
}
} }
private def preparationFlow(config: Config, realPlayers: Option[RealPlayers]) = private def preparationFlow(config: Config, realPlayers: Option[RealPlayers]) =
Flow[Game] Flow[Game]
.mapAsync(4)(enrich(config.flags)) .mapAsync(4)(enrich(config.flags))
.mapAsync(4) { .mapAsync(4) { case (game, fen, analysis) =>
case (game, fen, analysis) => formatterFor(config)(game, fen, analysis, None, realPlayers) formatterFor(config)(game, fen, analysis, None, realPlayers)
} }
private def enrich(flags: WithFlags)(game: Game) = private def enrich(flags: WithFlags)(game: Game) =
@ -280,19 +276,18 @@ final class GameApiV2(
"createdAt" -> g.createdAt, "createdAt" -> g.createdAt,
"lastMoveAt" -> g.movedAt, "lastMoveAt" -> g.movedAt,
"status" -> g.status.name, "status" -> g.status.name,
"players" -> JsObject(g.players zip lightUsers map { "players" -> JsObject(g.players zip lightUsers map { case (p, user) =>
case (p, user) => p.color.name -> Json
p.color.name -> Json .obj()
.obj() .add("user", user)
.add("user", user) .add("rating", p.rating)
.add("rating", p.rating) .add("ratingDiff", p.ratingDiff)
.add("ratingDiff", p.ratingDiff) .add("name", p.name)
.add("name", p.name) .add("provisional" -> p.provisional)
.add("provisional" -> p.provisional) .add("aiLevel" -> p.aiLevel)
.add("aiLevel" -> p.aiLevel) .add("analysis" -> analysisOption.flatMap(analysisJson.player(g pov p.color)))
.add("analysis" -> analysisOption.flatMap(analysisJson.player(g pov p.color))) .add("team" -> teams.map(_(p.color)))
.add("team" -> teams.map(_(p.color))) // .add("moveCentis" -> withFlags.moveTimes ?? g.moveTimes(p.color).map(_.map(_.centis)))
// .add("moveCentis" -> withFlags.moveTimes ?? g.moveTimes(p.color).map(_.map(_.centis)))
}) })
) )
.add("initialFen" -> initialFen.map(_.value)) .add("initialFen" -> initialFen.map(_.value))

View File

@ -13,8 +13,7 @@ final class LobbyApi(
def apply(implicit ctx: Context): Fu[(JsObject, List[Pov])] = def apply(implicit ctx: Context): Fu[(JsObject, List[Pov])] =
ctx.me.fold(seekApi.forAnon)(seekApi.forUser).mon(_.lobby segment "seeks") zip ctx.me.fold(seekApi.forAnon)(seekApi.forUser).mon(_.lobby segment "seeks") zip
(ctx.me ?? gameProxyRepo.urgentGames).mon(_.lobby segment "urgentGames") flatMap { (ctx.me ?? gameProxyRepo.urgentGames).mon(_.lobby segment "urgentGames") flatMap { case (seeks, povs) =>
case (seeks, povs) =>
val displayedPovs = povs take 9 val displayedPovs = povs take 9
lightUserApi.preloadMany(displayedPovs.flatMap(_.opponent.userId)) inject { lightUserApi.preloadMany(displayedPovs.flatMap(_.opponent.userId)) inject {
implicit val lang = ctx.lang implicit val lang = ctx.lang
@ -27,7 +26,7 @@ final class LobbyApi(
"nbNowPlaying" -> povs.size "nbNowPlaying" -> povs.size
) -> displayedPovs ) -> displayedPovs
} }
} }
def nowPlaying(pov: Pov) = def nowPlaying(pov: Pov) =
Json Json

View File

@ -81,9 +81,8 @@ final class PersonalDataExport(
def privateMessages(msgs: Seq[(User.ID, String, DateTime)]) = def privateMessages(msgs: Seq[(User.ID, String, DateTime)]) =
List( List(
textTitle(s"${msgs.size} Direct messages"), textTitle(s"${msgs.size} Direct messages"),
msgs.map { msgs.map { case (to, text, date) =>
case (to, text, date) => s"$to ${textDate(date)}\n$text"
s"$to ${textDate(date)}\n$text"
} mkString bigSep } mkString bigSep
) )

View File

@ -42,23 +42,22 @@ final class PgnDump(
} }
private def addEvals(p: Pgn, analysis: Analysis): Pgn = private def addEvals(p: Pgn, analysis: Analysis): Pgn =
analysis.infos.foldLeft(p) { analysis.infos.foldLeft(p) { case (pgn, info) =>
case (pgn, info) => pgn.updateTurn(
pgn.updateTurn( info.turn,
info.turn, turn =>
turn => turn.update(
turn.update( info.color,
info.color, move => {
move => { val comment = info.cp
val comment = info.cp .map(_.pawns.toString)
.map(_.pawns.toString) .orElse(info.mate.map(m => s"#${m.value}"))
.orElse(info.mate.map(m => s"#${m.value}")) move.copy(
move.copy( comments = comment.map(c => s"[%eval $c]").toList ::: move.comments
comments = comment.map(c => s"[%eval $c]").toList ::: move.comments )
) }
} )
) )
)
} }
def formatter(flags: WithFlags) = def formatter(flags: WithFlags) =

View File

@ -51,17 +51,17 @@ final private[api] class RoundApi(
(ctx.me.ifTrue(ctx.isMobileApi) ?? (me => noteApi.get(pov.gameId, me.id))) zip (ctx.me.ifTrue(ctx.isMobileApi) ?? (me => noteApi.get(pov.gameId, me.id))) zip
forecastApi.loadForDisplay(pov) zip forecastApi.loadForDisplay(pov) zip
bookmarkApi.exists(pov.game, ctx.me) map { bookmarkApi.exists(pov.game, ctx.me) map {
case json ~ simul ~ swiss ~ note ~ forecast ~ bookmarked => case json ~ simul ~ swiss ~ note ~ forecast ~ bookmarked =>
( (
withTournament(pov, tour) _ compose withTournament(pov, tour) _ compose
withSwiss(swiss) compose withSwiss(swiss) compose
withSimul(simul) compose withSimul(simul) compose
withSteps(pov, initialFen) compose withSteps(pov, initialFen) compose
withNote(note) compose withNote(note) compose
withBookmark(bookmarked) compose withBookmark(bookmarked) compose
withForecastCount(forecast.map(_.steps.size)) withForecastCount(forecast.map(_.steps.size))
)(json) )(json)
} }
} }
.mon(_.round.api.player) .mon(_.round.api.player)
@ -88,8 +88,7 @@ final private[api] class RoundApi(
(pov.game.simulId ?? simulApi.find) zip (pov.game.simulId ?? simulApi.find) zip
swissApi.gameView(pov) zip swissApi.gameView(pov) zip
(ctx.me.ifTrue(ctx.isMobileApi) ?? (me => noteApi.get(pov.gameId, me.id))) zip (ctx.me.ifTrue(ctx.isMobileApi) ?? (me => noteApi.get(pov.gameId, me.id))) zip
bookmarkApi.exists(pov.game, ctx.me) map { bookmarkApi.exists(pov.game, ctx.me) map { case json ~ simul ~ swiss ~ note ~ bookmarked =>
case json ~ simul ~ swiss ~ note ~ bookmarked =>
( (
withTournament(pov, tour) _ compose withTournament(pov, tour) _ compose
withSwiss(swiss) compose withSwiss(swiss) compose
@ -98,7 +97,7 @@ final private[api] class RoundApi(
withBookmark(bookmarked) compose withBookmark(bookmarked) compose
withSteps(pov, initialFen) withSteps(pov, initialFen)
)(json) )(json)
} }
} }
.mon(_.round.api.watcher) .mon(_.round.api.watcher)
@ -127,8 +126,7 @@ final private[api] class RoundApi(
(pov.game.simulId ?? simulApi.find) zip (pov.game.simulId ?? simulApi.find) zip
swissApi.gameView(pov) zip swissApi.gameView(pov) zip
ctx.userId.ifTrue(ctx.isMobileApi).?? { noteApi.get(pov.gameId, _) } zip ctx.userId.ifTrue(ctx.isMobileApi).?? { noteApi.get(pov.gameId, _) } zip
bookmarkApi.exists(pov.game, ctx.me) map { bookmarkApi.exists(pov.game, ctx.me) map { case json ~ tour ~ simul ~ swiss ~ note ~ bookmarked =>
case json ~ tour ~ simul ~ swiss ~ note ~ bookmarked =>
( (
withTournament(pov, tour) _ compose withTournament(pov, tour) _ compose
withSwiss(swiss) compose withSwiss(swiss) compose
@ -138,7 +136,7 @@ final private[api] class RoundApi(
withTree(pov, analysis, initialFen, withFlags) compose withTree(pov, analysis, initialFen, withFlags) compose
withAnalysis(pov.game, analysis) withAnalysis(pov.game, analysis)
)(json) )(json)
} }
} }
.mon(_.round.api.watcher) .mon(_.round.api.watcher)

View File

@ -58,43 +58,43 @@ final private[api] class UserApi(
.map(_.map { cr => .map(_.map { cr =>
math.round(cr * 100) math.round(cr * 100)
}) map { }) map {
case gameOption ~ nbGamesWithMe ~ following ~ followers ~ followable ~ relation ~ case gameOption ~ nbGamesWithMe ~ following ~ followers ~ followable ~ relation ~
isFollowed ~ nbBookmarks ~ nbPlaying ~ nbImported ~ completionRate => isFollowed ~ nbBookmarks ~ nbPlaying ~ nbImported ~ completionRate =>
jsonView(u) ++ { jsonView(u) ++ {
Json Json
.obj( .obj(
"url" -> makeUrl(s"@/${u.username}"), // for app BC "url" -> makeUrl(s"@/${u.username}"), // for app BC
"playing" -> gameOption.map(g => makeUrl(s"${g.gameId}/${g.color.name}")), "playing" -> gameOption.map(g => makeUrl(s"${g.gameId}/${g.color.name}")),
"nbFollowing" -> following, "nbFollowing" -> following,
"nbFollowers" -> followers, "nbFollowers" -> followers,
"completionRate" -> completionRate, "completionRate" -> completionRate,
"count" -> Json.obj( "count" -> Json.obj(
"all" -> u.count.game, "all" -> u.count.game,
"rated" -> u.count.rated, "rated" -> u.count.rated,
"ai" -> u.count.ai, "ai" -> u.count.ai,
"draw" -> u.count.draw, "draw" -> u.count.draw,
"drawH" -> u.count.drawH, "drawH" -> u.count.drawH,
"loss" -> u.count.loss, "loss" -> u.count.loss,
"lossH" -> u.count.lossH, "lossH" -> u.count.lossH,
"win" -> u.count.win, "win" -> u.count.win,
"winH" -> u.count.winH, "winH" -> u.count.winH,
"bookmark" -> nbBookmarks, "bookmark" -> nbBookmarks,
"playing" -> nbPlaying, "playing" -> nbPlaying,
"import" -> nbImported, "import" -> nbImported,
"me" -> nbGamesWithMe "me" -> nbGamesWithMe
)
) )
) .add("streaming", liveStreamApi.isStreaming(u.id)) ++
.add("streaming", liveStreamApi.isStreaming(u.id)) ++ as.isDefined.??(
as.isDefined.??( Json.obj(
Json.obj( "followable" -> followable,
"followable" -> followable, "following" -> relation.has(true),
"following" -> relation.has(true), "blocking" -> relation.has(false),
"blocking" -> relation.has(false), "followsYou" -> isFollowed
"followsYou" -> isFollowed )
) )
) }.noNull
}.noNull }
}
} }
private def addPlayingStreaming(js: JsObject, id: User.ID) = private def addPlayingStreaming(js: JsObject, id: User.ID) =

View File

@ -33,7 +33,8 @@ final class ChallengeApi(
def create(c: Challenge): Fu[Boolean] = def create(c: Challenge): Fu[Boolean] =
isLimitedByMaxPlaying(c) flatMap { isLimitedByMaxPlaying(c) flatMap {
case true => fuFalse case true => fuFalse
case false => { case false =>
{
repo like c flatMap { _ ?? repo.cancel } repo like c flatMap { _ ?? repo.cancel }
} >> (repo insert c) >>- { } >> (repo insert c) >>- {
uncacheAndNotify(c) uncacheAndNotify(c)

View File

@ -53,22 +53,22 @@ final class ChallengeGranter(
.fold[Fu[Option[ChallengeDenied.Reason]]](fuccess(YouAreAnon.some)) { from => .fold[Fu[Option[ChallengeDenied.Reason]]](fuccess(YouAreAnon.some)) { from =>
relationApi.fetchRelation(dest, from) zip relationApi.fetchRelation(dest, from) zip
prefApi.getPref(dest).map(_.challenge) map { prefApi.getPref(dest).map(_.challenge) map {
case (Some(Block), _) => YouAreBlocked.some case (Some(Block), _) => YouAreBlocked.some
case (_, Pref.Challenge.NEVER) => TheyDontAcceptChallenges.some case (_, Pref.Challenge.NEVER) => TheyDontAcceptChallenges.some
case (Some(Follow), _) => none // always accept from followed case (Some(Follow), _) => none // always accept from followed
case (_, _) if from.marks.engine && !dest.marks.engine => YouAreBlocked.some case (_, _) if from.marks.engine && !dest.marks.engine => YouAreBlocked.some
case (_, Pref.Challenge.FRIEND) => FriendsOnly.some case (_, Pref.Challenge.FRIEND) => FriendsOnly.some
case (_, Pref.Challenge.RATING) => case (_, Pref.Challenge.RATING) =>
perfType ?? { pt => perfType ?? { pt =>
if (from.perfs(pt).provisional || dest.perfs(pt).provisional) if (from.perfs(pt).provisional || dest.perfs(pt).provisional)
RatingIsProvisional(pt).some RatingIsProvisional(pt).some
else { else {
val diff = math.abs(from.perfs(pt).intRating - dest.perfs(pt).intRating) val diff = math.abs(from.perfs(pt).intRating - dest.perfs(pt).intRating)
(diff > ratingThreshold) option RatingOutsideRange(pt) (diff > ratingThreshold) option RatingOutsideRange(pt)
}
} }
} case (_, Pref.Challenge.ALWAYS) => none
case (_, Pref.Challenge.ALWAYS) => none }
}
} }
.map { .map {
case None if dest.isBot && perfType.has(PerfType.UltraBullet) => BotUltraBullet.some case None if dest.isBot && perfType.has(PerfType.UltraBullet) => BotUltraBullet.some

View File

@ -57,8 +57,8 @@ final private class ChallengeRepo(coll: Coll, maxPerUser: Max)(implicit
.void .void
private[challenge] def allWithUserId(userId: String): Fu[List[Challenge]] = private[challenge] def allWithUserId(userId: String): Fu[List[Challenge]] =
createdByChallengerId(userId) zip createdByDestId(userId) dmap { createdByChallengerId(userId) zip createdByDestId(userId) dmap { case (x, y) =>
case (x, y) => x ::: y x ::: y
} }
@nowarn("cat=unused") def like(c: Challenge) = @nowarn("cat=unused") def like(c: Challenge) =
@ -87,9 +87,8 @@ final private class ChallengeRepo(coll: Coll, maxPerUser: Max)(implicit
.hint(coll hint $doc("seenAt" -> 1)) // partial index .hint(coll hint $doc("seenAt" -> 1)) // partial index
.cursor[Challenge]() .cursor[Challenge]()
.list(max) .list(max)
.recoverWith { .recoverWith { case _: reactivemongo.core.errors.DatabaseException =>
case _: reactivemongo.core.errors.DatabaseException => coll.list[Challenge](selector, max)
coll.list[Challenge](selector, max)
} }
} }

View File

@ -22,8 +22,8 @@ final private class ChallengeSocket(
private lazy val send: String => Unit = remoteSocketApi.makeSender("chal-out").apply _ private lazy val send: String => Unit = remoteSocketApi.makeSender("chal-out").apply _
private lazy val challengeHandler: Handler = { private lazy val challengeHandler: Handler = { case Protocol.In.OwnerPings(ids) =>
case Protocol.In.OwnerPings(ids) => ids foreach api.ping ids foreach api.ping
} }
remoteSocketApi.subscribe("chal-in", Protocol.In.reader)( remoteSocketApi.subscribe("chal-in", Protocol.In.reader)(

View File

@ -65,16 +65,15 @@ private object Joiner {
) )
.withId(c.id) .withId(c.id)
.pipe { g => .pipe { g =>
state.fold(g) { state.fold(g) { case sit @ SituationPlus(Situation(board, _), _) =>
case sit @ SituationPlus(Situation(board, _), _) => g.copy(
g.copy( chess = g.chess.copy(
chess = g.chess.copy( situation = g.situation.copy(
situation = g.situation.copy( board = g.board.copy(history = board.history)
board = g.board.copy(history = board.history) ),
), turns = sit.turns
turns = sit.turns
)
) )
)
} }
} }
.start .start

Some files were not shown because too many files have changed in this diff Show More