Merge branch 'master' into asso
* master: (23 commits) display game completion rate on user profile hide link to favourite opponents make many DB accesses more type safe don't count forum topics tweak ranking API prevent heavy forum DB queries upgrade scalachess catch and log link adder exceptions playban penalties for clock sitters upgrade scalachess fix fishnet analysis nodes config remove useless user count apply game limit to challenges Update stockfish.js and use submodule improve seek limit play up to 200 concurrent games per user limit opening trainer to 10,000 openings use distinct for opening selection asset version analysis with 4 million nodes per move ...
This commit is contained in:
commit
b247f15597
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -28,3 +28,6 @@
|
|||
[submodule "public/vendor/shepherd"]
|
||||
path = public/vendor/shepherd
|
||||
url = https://github.com/HubSpot/shepherd
|
||||
[submodule "public/vendor/stockfish.js"]
|
||||
path = public/vendor/stockfish.js
|
||||
url = https://github.com/niklasf/stockfish.js
|
||||
|
|
|
@ -30,7 +30,6 @@ final class Env(
|
|||
lightUser = Env.user.lightUser)
|
||||
|
||||
lazy val userInfo = mashup.UserInfo(
|
||||
countUsers = () => Env.user.countEnabled,
|
||||
bookmarkApi = Env.bookmark.api,
|
||||
relationApi = Env.relation.api,
|
||||
trophyApi = Env.user.trophyApi,
|
||||
|
@ -43,7 +42,8 @@ final class Env(
|
|||
isHostingSimul = Env.simul.isHosting,
|
||||
isStreamer = Env.tv.isStreamer.apply,
|
||||
insightShare = Env.insight.share,
|
||||
getPlayTime = Env.game.playTime.apply) _
|
||||
getPlayTime = Env.game.playTime.apply,
|
||||
completionRate = Env.playban.api.completionRate) _
|
||||
|
||||
system.actorOf(Props(new actor.Renderer), name = RendererName)
|
||||
|
||||
|
@ -70,6 +70,7 @@ final class Env(
|
|||
Env.tv,
|
||||
Env.blog,
|
||||
Env.video,
|
||||
Env.playban, // required to load the actor
|
||||
Env.shutup, // required to load the actor
|
||||
Env.insight, // required to load the actor
|
||||
Env.worldMap, // required to load the actor
|
||||
|
|
|
@ -13,9 +13,11 @@ object ForumCateg extends LilaController with ForumController {
|
|||
|
||||
def show(slug: String, page: Int) = Open { implicit ctx =>
|
||||
NotForKids {
|
||||
CategGrantRead(slug) {
|
||||
OptionOk(categApi.show(slug, page, ctx.troll)) {
|
||||
case (categ, topics) => html.forum.categ.show(categ, topics)
|
||||
Reasonable(page, 50, errorPage = notFound) {
|
||||
CategGrantRead(slug) {
|
||||
OptionOk(categApi.show(slug, page, ctx.troll)) {
|
||||
case (categ, topics) => html.forum.categ.show(categ, topics)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -331,11 +331,10 @@ private[controllers] trait LilaController
|
|||
}
|
||||
|
||||
protected def XhrOnly(res: => Fu[Result])(implicit ctx: Context) =
|
||||
if (HTTPRequest isXhr ctx.req) res
|
||||
else notFound
|
||||
if (HTTPRequest isXhr ctx.req) res else notFound
|
||||
|
||||
protected def Reasonable(page: Int, max: Int = 40)(result: => Fu[Result]): Fu[Result] =
|
||||
(page < max).fold(result, BadRequest("resource too old").fuccess)
|
||||
protected def Reasonable(page: Int, max: Int = 40, errorPage: => Fu[Result] = BadRequest("resource too old").fuccess)(result: => Fu[Result]): Fu[Result] =
|
||||
if (page < max) result else errorPage
|
||||
|
||||
protected def NotForKids(f: => Fu[Result])(implicit ctx: Context) =
|
||||
if (ctx.kid) notFound else f
|
||||
|
|
|
@ -3,10 +3,10 @@ package controllers
|
|||
import scala.util.{ Try, Success, Failure }
|
||||
|
||||
import play.api.data._, Forms._
|
||||
import play.api.mvc._
|
||||
import play.twirl.api.Html
|
||||
import play.api.Play.current
|
||||
import play.api.i18n.Messages.Implicits._
|
||||
import play.api.mvc._
|
||||
import play.api.Play.current
|
||||
import play.twirl.api.Html
|
||||
|
||||
import lila.api.Context
|
||||
import lila.app._
|
||||
|
@ -25,9 +25,9 @@ object Opening extends LilaController {
|
|||
|
||||
private def renderShow(opening: OpeningModel)(implicit ctx: Context) =
|
||||
env userInfos ctx.me zip identify(opening) map {
|
||||
case (infos, identified) =>
|
||||
views.html.opening.show(opening, identified, infos, env.AnimationDuration)
|
||||
}
|
||||
case (infos, identified) =>
|
||||
views.html.opening.show(opening, identified, infos, env.AnimationDuration)
|
||||
}
|
||||
|
||||
private def makeData(
|
||||
opening: OpeningModel,
|
||||
|
@ -46,12 +46,16 @@ object Opening extends LilaController {
|
|||
animationDuration = env.AnimationDuration)) as JSON
|
||||
}
|
||||
|
||||
private val noMoreOpeningJson = jsonError("No more openings for you!")
|
||||
|
||||
def home = Open { implicit ctx =>
|
||||
if (HTTPRequest isXhr ctx.req) env.selector(ctx.me) zip (env userInfos ctx.me) flatMap {
|
||||
case (opening, infos) => makeData(opening, infos, true, none, none)
|
||||
case (Some(opening), infos) => makeData(opening, infos, true, none, none)
|
||||
case (None, _) => NotFound(noMoreOpeningJson).fuccess
|
||||
}
|
||||
else env.selector(ctx.me) flatMap { opening =>
|
||||
renderShow(opening) map { Ok(_) }
|
||||
else env.selector(ctx.me) flatMap {
|
||||
case Some(opening) => renderShow(opening) map { Ok(_) }
|
||||
case None => fuccess(Ok(html.opening.noMore()))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ import lila.api.{ Context, BodyContext }
|
|||
import lila.app._
|
||||
import lila.common.{ HTTPRequest, LilaCookie }
|
||||
import lila.game.{ GameRepo, Pov, AnonCookie }
|
||||
import lila.setup.Processor.HookResult
|
||||
import lila.setup.{ HookConfig, ValidFen }
|
||||
import lila.user.UserRepo
|
||||
import views._
|
||||
|
@ -89,9 +90,14 @@ object Setup extends LilaController with TheftPrevention {
|
|||
destUser = destUser,
|
||||
rematchOf = none)
|
||||
env.processor.saveFriendConfig(config) >>
|
||||
(Env.challenge.api create challenge) >> negotiate(
|
||||
html = fuccess(Redirect(routes.Round.watcher(challenge.id, "white"))),
|
||||
api = _ => Challenge showChallenge challenge)
|
||||
(Env.challenge.api create challenge) flatMap {
|
||||
case true => negotiate(
|
||||
html = fuccess(Redirect(routes.Round.watcher(challenge.id, "white"))),
|
||||
api = _ => Challenge showChallenge challenge)
|
||||
case false => negotiate(
|
||||
html = fuccess(Redirect(routes.Lobby.home)),
|
||||
api = _ => fuccess(BadRequest(jsonError("Challenge not created"))))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -108,10 +114,14 @@ object Setup extends LilaController with TheftPrevention {
|
|||
}
|
||||
}
|
||||
|
||||
private def hookResponse(hookId: String) =
|
||||
Ok(Json.obj(
|
||||
private def hookResponse(res: HookResult) = res match {
|
||||
case HookResult.Created(id) => Ok(Json.obj(
|
||||
"ok" -> true,
|
||||
"hook" -> Json.obj("id" -> hookId))) as JSON
|
||||
"hook" -> Json.obj("id" -> id))) as JSON
|
||||
case HookResult.Refused => BadRequest(jsonError("Game was not created"))
|
||||
}
|
||||
|
||||
private val hookRefused = BadRequest(jsonError("Game was not created"))
|
||||
|
||||
def hook(uid: String) = OpenBody { implicit ctx =>
|
||||
implicit val req = ctx.body
|
||||
|
@ -123,9 +133,7 @@ object Setup extends LilaController with TheftPrevention {
|
|||
api = _ => BadRequest(errorsAsJson(err)).fuccess),
|
||||
config => (ctx.userId ?? Env.relation.api.fetchBlocking) flatMap {
|
||||
blocking =>
|
||||
env.processor.hook(config, uid, HTTPRequest sid req, blocking) map hookResponse recover {
|
||||
case e: IllegalArgumentException => BadRequest(jsonError(e.getMessage)) as JSON
|
||||
}
|
||||
env.processor.hook(config, uid, HTTPRequest sid req, blocking) map hookResponse
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -140,9 +148,7 @@ object Setup extends LilaController with TheftPrevention {
|
|||
_.fold(config)(config.updateFrom)
|
||||
} flatMap { config =>
|
||||
(ctx.userId ?? Env.relation.api.fetchBlocking) flatMap { blocking =>
|
||||
env.processor.hook(config, uid, HTTPRequest sid ctx.req, blocking) map hookResponse recover {
|
||||
case e: IllegalArgumentException => BadRequest(jsonError(e.getMessage)) as JSON
|
||||
}
|
||||
env.processor.hook(config, uid, HTTPRequest sid ctx.req, blocking) map hookResponse
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,6 @@ import lila.user.{ User, Trophy, Trophies, TrophyApi }
|
|||
case class UserInfo(
|
||||
user: User,
|
||||
ranks: lila.rating.UserRankMap,
|
||||
nbUsers: Int,
|
||||
nbPlaying: Int,
|
||||
hasSimul: Boolean,
|
||||
crosstable: Option[Crosstable],
|
||||
|
@ -29,7 +28,8 @@ case class UserInfo(
|
|||
playTime: User.PlayTime,
|
||||
trophies: Trophies,
|
||||
isStreamer: Boolean,
|
||||
insightVisible: Boolean) {
|
||||
insightVisible: Boolean,
|
||||
completionRate: Option[Double]) {
|
||||
|
||||
def nbRated = user.count.rated
|
||||
|
||||
|
@ -37,6 +37,8 @@ case class UserInfo(
|
|||
|
||||
def percentRated: Int = math.round(nbRated / user.count.game.toFloat * 100)
|
||||
|
||||
def completionRatePercent = completionRate.map { cr => math.round(cr * 100) }
|
||||
|
||||
def allTrophies = List(
|
||||
isStreamer option Trophy(
|
||||
_id = "",
|
||||
|
@ -49,7 +51,6 @@ case class UserInfo(
|
|||
object UserInfo {
|
||||
|
||||
def apply(
|
||||
countUsers: () => Fu[Int],
|
||||
bookmarkApi: BookmarkApi,
|
||||
relationApi: RelationApi,
|
||||
trophyApi: TrophyApi,
|
||||
|
@ -62,9 +63,9 @@ object UserInfo {
|
|||
isHostingSimul: String => Fu[Boolean],
|
||||
isStreamer: String => Boolean,
|
||||
insightShare: lila.insight.Share,
|
||||
getPlayTime: User => Fu[User.PlayTime])(user: User, ctx: Context): Fu[UserInfo] =
|
||||
countUsers() zip
|
||||
getRanks(user.id) zip
|
||||
getPlayTime: User => Fu[User.PlayTime],
|
||||
completionRate: User.ID => Fu[Option[Double]])(user: User, ctx: Context): Fu[UserInfo] =
|
||||
getRanks(user.id) zip
|
||||
(gameCached nbPlaying user.id) zip
|
||||
gameCached.nbImportedBy(user.id) zip
|
||||
(ctx.me.filter(user!=) ?? { me => crosstableApi(me.id, user.id) }) zip
|
||||
|
@ -75,13 +76,13 @@ object UserInfo {
|
|||
studyRepo.countByOwner(user.id) zip
|
||||
trophyApi.findByUser(user) zip
|
||||
(user.count.rated >= 10).??(insightShare.grant(user, ctx.me)) zip
|
||||
getPlayTime(user) flatMap {
|
||||
case ((((((((((((nbUsers, ranks), nbPlaying), nbImported), crosstable), ratingChart), nbFollowers), nbBlockers), nbPosts), nbStudies), trophies), insightVisible), playTime) =>
|
||||
getPlayTime(user) zip
|
||||
completionRate(user.id) flatMap {
|
||||
case ((((((((((((ranks, nbPlaying), nbImported), crosstable), ratingChart), nbFollowers), nbBlockers), nbPosts), nbStudies), trophies), insightVisible), playTime), completionRate) =>
|
||||
(nbPlaying > 0) ?? isHostingSimul(user.id) map { hasSimul =>
|
||||
new UserInfo(
|
||||
user = user,
|
||||
ranks = ranks,
|
||||
nbUsers = nbUsers,
|
||||
nbPlaying = nbPlaying,
|
||||
hasSimul = hasSimul,
|
||||
crosstable = crosstable,
|
||||
|
@ -95,7 +96,8 @@ object UserInfo {
|
|||
playTime = playTime,
|
||||
trophies = trophies,
|
||||
isStreamer = isStreamer(user.id),
|
||||
insightVisible = insightVisible)
|
||||
insightVisible = insightVisible,
|
||||
completionRate = completionRate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ trait PaginatorHelper {
|
|||
|
||||
implicit def toRichPager[A](pager: Paginator[A]) = new {
|
||||
|
||||
def sliding(length: Int): List[Option[Int]] = {
|
||||
def sliding(length: Int, showPost: Boolean = true): List[Option[Int]] = {
|
||||
val fromPage = 1 max (pager.currentPage - length)
|
||||
val toPage = pager.nbPages min (pager.currentPage + length)
|
||||
val pre = fromPage match {
|
||||
|
@ -18,7 +18,8 @@ trait PaginatorHelper {
|
|||
val post = toPage match {
|
||||
case x if x == pager.nbPages => Nil
|
||||
case x if x == pager.nbPages - 1 => List(pager.nbPages.some)
|
||||
case x => List(none, pager.nbPages.some)
|
||||
case x if showPost => List(none, pager.nbPages.some)
|
||||
case _ => List(none)
|
||||
}
|
||||
pre ::: (fromPage to toPage).toList.map(some) ::: post
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import java.text.SimpleDateFormat
|
|||
import java.util.Date
|
||||
import java.util.regex.Matcher.quoteReplacement
|
||||
|
||||
import lila.user.{User, UserContext}
|
||||
import lila.user.{ User, UserContext }
|
||||
import org.apache.commons.lang3.StringEscapeUtils.escapeHtml4
|
||||
import play.twirl.api.Html
|
||||
|
||||
|
@ -50,10 +50,10 @@ trait StringHelper { self: NumberHelper =>
|
|||
private val urlRegex = """(?i)\b((https?://|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,6}/)((?:[^\s<>]+|\(([^\s<>]+|(\([^\s<>]+\)))*\))+(?:\(([^\s<>]+|(\([^\s<>]+\)))*\)|[^\s`!\[\]{};:'".,<>?«»“”‘’])))""".r
|
||||
|
||||
/**
|
||||
* Creates hyperlinks to user profiles mentioned using the '@' prefix. e.g. @ornicar
|
||||
* @param text The text to regex match
|
||||
* @return The text as a HTML hyperlink
|
||||
*/
|
||||
* Creates hyperlinks to user profiles mentioned using the '@' prefix. e.g. @ornicar
|
||||
* @param text The text to regex match
|
||||
* @return The text as a HTML hyperlink
|
||||
*/
|
||||
def addUserProfileLinks(text: String) = User.atUsernameRegex.replaceAllIn(text, m => {
|
||||
val user = m group 1
|
||||
val url = s"//$netDomain/@/$user"
|
||||
|
@ -61,27 +61,37 @@ trait StringHelper { self: NumberHelper =>
|
|||
s"""<a href="$url">@$user</a>"""
|
||||
})
|
||||
|
||||
def addLinks(text: String) = urlRegex.replaceAllIn(text, m => {
|
||||
if (m.group(2) == "http://" || m.group(2) == "https://") {
|
||||
if (s"${delocalize(m.group(3))}/" startsWith s"$netDomain/") {
|
||||
// internal
|
||||
val link = delocalize(m.group(3))
|
||||
s"""<a rel="nofollow" href="//$link">$link</a>"""
|
||||
} else {
|
||||
// external
|
||||
s"""<a rel="nofollow" href="${m.group(1)}" target="_blank">${m.group(1)}</a>"""
|
||||
def addLinks(text: String) = try {
|
||||
urlRegex.replaceAllIn(text, m => {
|
||||
if (m.group(2) == "http://" || m.group(2) == "https://") {
|
||||
if (s"${delocalize(m.group(3))}/" startsWith s"$netDomain/") {
|
||||
// internal
|
||||
val link = delocalize(m.group(3))
|
||||
s"""<a rel="nofollow" href="//$link">$link</a>"""
|
||||
}
|
||||
else {
|
||||
// external
|
||||
s"""<a rel="nofollow" href="${m.group(1)}" target="_blank">${m.group(1)}</a>"""
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (s"${delocalize(m.group(2))}/" startsWith s"$netDomain/") {
|
||||
// internal
|
||||
val link = delocalize(m.group(1))
|
||||
s"""<a rel="nofollow" href="//$link">$link</a>"""
|
||||
} else {
|
||||
// external
|
||||
s"""<a rel="nofollow" href="http://${m.group(1)}" target="_blank">${m.group(1)}</a>"""
|
||||
else {
|
||||
if (s"${delocalize(m.group(2))}/" startsWith s"$netDomain/") {
|
||||
// internal
|
||||
val link = delocalize(m.group(1))
|
||||
s"""<a rel="nofollow" href="//$link">$link</a>"""
|
||||
}
|
||||
else {
|
||||
// external
|
||||
s"""<a rel="nofollow" href="http://${m.group(1)}" target="_blank">${m.group(1)}</a>"""
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
catch {
|
||||
case e: IllegalArgumentException =>
|
||||
lila.log("templating").error(s"addLinks($text)", e)
|
||||
text
|
||||
}
|
||||
|
||||
private val delocalize = new lila.common.String.Delocalizer(netDomain)
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
} else {
|
||||
<span class="disabled" data-icon="I"></span>
|
||||
}
|
||||
@pager.sliding(3).map {
|
||||
@pager.sliding(3, showPost = false).map {
|
||||
case None => { ... }
|
||||
case Some(p) => {
|
||||
@if(p == pager.currentPage) {
|
||||
|
|
10
app/views/opening/noMore.scala.html
Normal file
10
app/views/opening/noMore.scala.html
Normal file
|
@ -0,0 +1,10 @@
|
|||
@()(implicit ctx: Context)
|
||||
|
||||
@site.message("You played all the openings!", back = false) {
|
||||
<a href="http://mr--jack.deviantart.com/art/Chess-Set-106198801">
|
||||
<img width="100%" src="@staticUrl("images/futurist-set.jpg")" alt="Futurist chess set drawing" />
|
||||
</a>
|
||||
<br /><br /><br />
|
||||
Sorry, we don't have any more openings for you.<br /><br />
|
||||
You played quite a lot of them, didn't you :)
|
||||
}
|
|
@ -69,8 +69,8 @@ description = describeUser(u)).some) {
|
|||
<a href="@routes.UserTournament.path(u.username, "recent")" class="intertab tournament_stats" data-toints="@u.toints">@splitNumber(u.toints + " " + trans.tournamentPoints())</a>
|
||||
<a href="@routes.Study.byOwnerDefault(u.username)" class="intertab">@splitNumber(info.nbStudies + " studies")</a>
|
||||
<a class="intertab" @NotForKids {
|
||||
href="@routes.ForumPost.search("user:" + u.username, 1)"
|
||||
}>@splitNumber(trans.nbForumPosts(info.nbPosts))</a>
|
||||
href="@routes.ForumPost.search("user:" + u.username, 1)"
|
||||
}>@splitNumber(trans.nbForumPosts(info.nbPosts))</a>
|
||||
@NotForKids {
|
||||
@if(ctx.isAuth && !ctx.is(u)) {
|
||||
<a class="intertab note_zone_toggle">@splitNumber(notes.size + " Notes")</a>
|
||||
|
@ -181,7 +181,10 @@ description = describeUser(u)).some) {
|
|||
@u.seenAt.map { seen =>
|
||||
<p class="thin">@trans.lastSeenActive(momentFromNow(seen))</p>
|
||||
}
|
||||
<a href="@routes.User.opponents(u.username)">@trans.favoriteOpponents()</a>
|
||||
@info.completionRatePercent.map { c =>
|
||||
<p class="thin">Game completion rate: @c%</p>
|
||||
}
|
||||
<!-- <a href="@routes.User.opponents(u.username)">@trans.favoriteOpponents()</a> -->
|
||||
<br />
|
||||
<br />
|
||||
<p>@trans.tpTimeSpentPlaying(showPeriod(info.playTime.totalPeriod))</p>
|
||||
|
|
|
@ -9,7 +9,7 @@ net {
|
|||
ip = "5.196.91.160"
|
||||
asset {
|
||||
domain = ${net.domain}
|
||||
version = 984
|
||||
version = 989
|
||||
}
|
||||
}
|
||||
forcedev = false
|
||||
|
@ -146,7 +146,7 @@ opening {
|
|||
step = 160
|
||||
max = 1000
|
||||
}
|
||||
modulo = 20000
|
||||
max_attempts = 10000
|
||||
}
|
||||
animation.duration = ${chessground.animation.duration}
|
||||
api.token = ${api.token}
|
||||
|
@ -346,6 +346,7 @@ lobby {
|
|||
max_per_page = 14
|
||||
max_per_user = 5
|
||||
}
|
||||
max_playing = ${setup.max_playing}
|
||||
}
|
||||
timeline {
|
||||
collection {
|
||||
|
@ -504,6 +505,7 @@ setup {
|
|||
user_config = config
|
||||
anon_config = config_anon
|
||||
}
|
||||
max_playing = 200
|
||||
}
|
||||
challenge {
|
||||
collection.challenge = challenge
|
||||
|
@ -514,6 +516,7 @@ challenge {
|
|||
}
|
||||
history.message.ttl = 40 seconds
|
||||
uid.timeout = 7 seconds
|
||||
max_playing = ${setup.max_playing}
|
||||
}
|
||||
study {
|
||||
collection.study = study
|
||||
|
|
|
@ -27,7 +27,7 @@ private[bookmark] object BookmarkRepo {
|
|||
coll.remove($doc("g" -> gameId)).void
|
||||
|
||||
def removeByGameIds(gameIds: List[String]): Funit =
|
||||
coll.remove($doc("g" -> $in(gameIds: _*))).void
|
||||
coll.remove($doc("g" $in gameIds)).void
|
||||
|
||||
private def add(gameId: String, userId: String, date: DateTime): Funit =
|
||||
coll.insert($doc(
|
||||
|
|
|
@ -33,6 +33,8 @@ case class Challenge(
|
|||
def challengerIsAnon = challenger.isLeft
|
||||
def destUserId = destUser.map(_.id)
|
||||
|
||||
def userIds = List(challengerUserId, destUserId).flatten
|
||||
|
||||
def daysPerTurn = timeControl match {
|
||||
case TimeControl.Correspondence(d) => d.some
|
||||
case _ => none
|
||||
|
|
|
@ -14,6 +14,8 @@ final class ChallengeApi(
|
|||
repo: ChallengeRepo,
|
||||
joiner: Joiner,
|
||||
jsonView: JsonView,
|
||||
gameCache: lila.game.Cached,
|
||||
maxPlaying: Int,
|
||||
socketHub: ActorRef,
|
||||
userRegister: ActorSelection,
|
||||
lilaBus: lila.common.Bus) {
|
||||
|
@ -23,10 +25,14 @@ final class ChallengeApi(
|
|||
def allFor(userId: User.ID): Fu[AllChallenges] =
|
||||
createdByDestId(userId) zip createdByChallengerId(userId) map (AllChallenges.apply _).tupled
|
||||
|
||||
def create(c: Challenge): Funit = {
|
||||
repo like c flatMap { _ ?? repo.cancel }
|
||||
} >> (repo insert c) >> uncacheAndNotify(c) >>- {
|
||||
lilaBus.publish(Event.Create(c), 'challenge)
|
||||
// returns boolean success
|
||||
def create(c: Challenge): Fu[Boolean] = isLimitedByMaxPlaying(c) flatMap {
|
||||
case true => fuccess(false)
|
||||
case false => {
|
||||
repo like c flatMap { _ ?? repo.cancel }
|
||||
} >> (repo insert c) >> uncacheAndNotify(c) >>- {
|
||||
lilaBus.publish(Event.Create(c), 'challenge)
|
||||
} inject true
|
||||
}
|
||||
|
||||
def byId = repo byId _
|
||||
|
@ -82,7 +88,13 @@ final class ChallengeApi(
|
|||
} yield success
|
||||
}
|
||||
|
||||
def removeByUserId = repo removeByUserId _
|
||||
def removeByUserId = repo removeByUserId _
|
||||
|
||||
private def isLimitedByMaxPlaying(c: Challenge) =
|
||||
if (c.hasClock) fuccess(false)
|
||||
else c.userIds.map { userId =>
|
||||
gameCache.nbPlaying(userId) map (maxPlaying <=)
|
||||
}.sequenceFu.map(_ exists identity)
|
||||
|
||||
private[challenge] def sweep: Funit =
|
||||
repo.realTimeUnseenSince(DateTime.now minusSeconds 10, max = 50).flatMap { cs =>
|
||||
|
|
|
@ -14,6 +14,7 @@ final class Env(
|
|||
config: Config,
|
||||
system: ActorSystem,
|
||||
onStart: String => Unit,
|
||||
gameCache: lila.game.Cached,
|
||||
lightUser: String => Option[lila.common.LightUser],
|
||||
hub: lila.hub.Env,
|
||||
db: lila.db.Env,
|
||||
|
@ -26,6 +27,7 @@ final class Env(
|
|||
val UidTimeout = config duration "uid.timeout"
|
||||
val SocketTimeout = config duration "socket.timeout"
|
||||
val SocketName = config getString "socket.name"
|
||||
val MaxPlaying = config getInt "max_playing"
|
||||
}
|
||||
import settings._
|
||||
|
||||
|
@ -51,6 +53,8 @@ final class Env(
|
|||
repo = repo,
|
||||
joiner = new Joiner(onStart = onStart),
|
||||
jsonView = jsonView,
|
||||
gameCache = gameCache,
|
||||
maxPlaying = MaxPlaying,
|
||||
socketHub = socketHub,
|
||||
userRegister = hub.actor.userRegister,
|
||||
lilaBus = system.lilaBus)
|
||||
|
@ -73,6 +77,7 @@ object Env {
|
|||
system = lila.common.PlayApp.system,
|
||||
onStart = lila.game.Env.current.onStart,
|
||||
hub = lila.hub.Env.current,
|
||||
gameCache = lila.game.Env.current.cached,
|
||||
lightUser = lila.user.Env.current.lightUser,
|
||||
db = lila.db.Env.current,
|
||||
scheduler = lila.common.PlayApp.scheduler)
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 8b58cf1a837a47b92f47a65aeea7742296642e57
|
||||
Subproject commit 7addd334e6052189d87d33374ef2c67fdb0d29b6
|
|
@ -286,7 +286,7 @@ trait dsl {
|
|||
}
|
||||
|
||||
/** Matches any of the values that exist in an array specified in the query.*/
|
||||
def $in[T](values: T*)(implicit writer: BSONWriter[T, _ <: BSONValue]): SimpleExpression[BSONDocument] = {
|
||||
def $in[T](values: Iterable[T])(implicit writer: BSONWriter[T, _ <: BSONValue]): SimpleExpression[BSONDocument] = {
|
||||
SimpleExpression(field, $doc("$in" -> values))
|
||||
}
|
||||
|
||||
|
@ -306,7 +306,7 @@ trait dsl {
|
|||
}
|
||||
|
||||
/** Matches values that do not exist in an array specified to the query. */
|
||||
def $nin[T](values: T*)(implicit writer: BSONWriter[T, _ <: BSONValue]): SimpleExpression[BSONDocument] = {
|
||||
def $nin[T](values: Iterable[T])(implicit writer: BSONWriter[T, _ <: BSONValue]): SimpleExpression[BSONDocument] = {
|
||||
SimpleExpression(field, $doc("$nin" -> values))
|
||||
}
|
||||
|
||||
|
|
|
@ -179,8 +179,8 @@ object JsonApi {
|
|||
implicit val WorkWrites = OWrites[Work] { work =>
|
||||
Json.obj(
|
||||
"work" -> (work match {
|
||||
case a: Analysis => Json.obj("type" -> "analysis", "id" -> work.id)
|
||||
case m: Move => Json.obj("type" -> "move", "id" -> work.id, "level" -> m.level)
|
||||
case a: Analysis => Json.obj("type" -> "analysis", "id" -> a.id, "nodes" -> a.nodes)
|
||||
case m: Move => Json.obj("type" -> "move", "id" -> m.id, "level" -> m.level)
|
||||
})
|
||||
) ++ Json.toJson(work.game).as[JsObject]
|
||||
}
|
||||
|
|
|
@ -14,8 +14,8 @@ object CategRepo {
|
|||
def withTeams(teams: Set[String]): Fu[List[Categ]] =
|
||||
coll.find($or(
|
||||
"team" $exists false,
|
||||
$doc("team" -> $doc("$in" -> teams))
|
||||
)).sort($sort asc "pos").cursor[Categ]().gather[List]()
|
||||
$doc("team" $in teams))
|
||||
).sort($sort asc "pos").cursor[Categ]().gather[List]()
|
||||
|
||||
def nextPosition: Fu[Int] =
|
||||
coll.primitiveOne[Int]($empty, $sort desc "pos", "pos") map (~_ + 1)
|
||||
|
|
|
@ -48,16 +48,16 @@ sealed abstract class PostRepo(troll: Boolean) {
|
|||
multi = true).void
|
||||
|
||||
def selectTopic(topicId: String) = $doc("topicId" -> topicId) ++ trollFilter
|
||||
def selectTopics(topicIds: List[String]) = $doc("topicId" $in (topicIds: _*)) ++ trollFilter
|
||||
def selectTopics(topicIds: List[String]) = $doc("topicId" $in topicIds) ++ trollFilter
|
||||
|
||||
def selectCateg(categId: String) = $doc("categId" -> categId) ++ trollFilter
|
||||
def selectCategs(categIds: List[String]) = $doc("categId" $in (categIds: _*)) ++ trollFilter
|
||||
def selectCategs(categIds: List[String]) = $doc("categId" $in categIds) ++ trollFilter
|
||||
|
||||
val selectNotHidden = $doc("hidden" -> false)
|
||||
|
||||
def selectLangs(langs: List[String]) =
|
||||
if (langs.isEmpty) $empty
|
||||
else $doc("lang" $in (langs: _*))
|
||||
else $doc("lang" $in langs)
|
||||
|
||||
def findDuplicate(post: Post): Fu[Option[Post]] = coll.uno[Post]($doc(
|
||||
"createdAt" $gt DateTime.now.minusHours(1),
|
||||
|
|
|
@ -78,8 +78,8 @@ private[forum] final class TopicApi(
|
|||
} >>- mentionNotifier.notifyMentionedUsers(post, topic) inject topic
|
||||
}
|
||||
|
||||
def paginator(categ: Categ, page: Int, troll: Boolean): Fu[Paginator[TopicView]] = Paginator(
|
||||
adapter = new Adapter[Topic](
|
||||
def paginator(categ: Categ, page: Int, troll: Boolean): Fu[Paginator[TopicView]] = {
|
||||
val adapter = new Adapter[Topic](
|
||||
collection = env.topicColl,
|
||||
selector = TopicRepo(troll) byCategQuery categ,
|
||||
projection = $empty,
|
||||
|
@ -88,9 +88,15 @@ private[forum] final class TopicApi(
|
|||
env.postColl.byId[Post](topic lastPostId troll) map { post =>
|
||||
TopicView(categ, topic, post, env.postApi lastPageOf topic, troll)
|
||||
}
|
||||
},
|
||||
currentPage = page,
|
||||
maxPerPage = maxPerPage)
|
||||
}
|
||||
val cachedAdapter =
|
||||
if (categ.isTeam) adapter
|
||||
else new CachedAdapter(adapter, nbResults = fuccess(1000))
|
||||
Paginator(
|
||||
adapter = cachedAdapter,
|
||||
currentPage = page,
|
||||
maxPerPage = maxPerPage)
|
||||
}
|
||||
|
||||
def delete(categ: Categ, topic: Topic): Funit =
|
||||
PostRepo.idsByTopicId(topic.id) flatMap { postIds =>
|
||||
|
|
|
@ -187,12 +187,11 @@ object GameRepo {
|
|||
coll.exists($id(id) ++ Query.analysed(true))
|
||||
|
||||
def filterAnalysed(ids: Seq[String]): Fu[Set[String]] =
|
||||
coll.distinct("_id", $doc(
|
||||
"_id" -> $doc("$in" -> ids),
|
||||
coll.distinct("_id", ($inIds(ids) ++ $doc(
|
||||
F.analysed -> true
|
||||
).some) map lila.db.BSON.asStringSet
|
||||
)).some) map lila.db.BSON.asStringSet
|
||||
|
||||
def exists(id: String) = coll.exists($doc("_id" -> id))
|
||||
def exists(id: String) = coll.exists($id(id))
|
||||
|
||||
def incBookmarks(id: ID, value: Int) =
|
||||
coll.update($id(id), $inc(F.bookmarks -> value)).void
|
||||
|
@ -357,10 +356,10 @@ object GameRepo {
|
|||
|
||||
def findMirror(game: Game): Fu[Option[Game]] = coll.uno[Game]($doc(
|
||||
F.id -> $doc("$ne" -> game.id),
|
||||
F.playerUids -> $doc("$in" -> game.userIds),
|
||||
F.playerUids $in game.userIds,
|
||||
F.status -> Status.Started.id,
|
||||
F.createdAt -> $doc("$gt" -> (DateTime.now minusMinutes 15)),
|
||||
F.updatedAt -> $doc("$gt" -> (DateTime.now minusMinutes 5)),
|
||||
F.createdAt $gt (DateTime.now minusMinutes 15),
|
||||
F.updatedAt $gt (DateTime.now minusMinutes 5),
|
||||
"$or" -> $arr(
|
||||
$doc(s"${F.whitePlayer}.ai" -> $doc("$exists" -> true)),
|
||||
$doc(s"${F.blackPlayer}.ai" -> $doc("$exists" -> true))
|
||||
|
|
|
@ -28,7 +28,7 @@ object Query {
|
|||
|
||||
val mate: Bdoc = status(Status.Mate)
|
||||
|
||||
val draw: Bdoc = F.status $in (Status.Draw.id, Status.Stalemate.id)
|
||||
val draw: Bdoc = F.status $in List(Status.Draw.id, Status.Stalemate.id)
|
||||
|
||||
def draw(u: String): Bdoc = user(u) ++ draw
|
||||
|
||||
|
@ -50,7 +50,7 @@ object Query {
|
|||
|
||||
def user(u: String): Bdoc = F.playerUids $eq u
|
||||
def user(u: User): Bdoc = F.playerUids $eq u.id
|
||||
def users(u: Seq[String]) = F.playerUids $in (u: _*)
|
||||
def users(u: Seq[String]) = F.playerUids $in u
|
||||
|
||||
val noAi: Bdoc = $doc(
|
||||
"p0.ai" $exists false,
|
||||
|
@ -65,7 +65,7 @@ object Query {
|
|||
def win(u: String) = user(u) ++ $doc(F.winnerId -> u)
|
||||
|
||||
def loss(u: String) = user(u) ++ $doc(
|
||||
F.status $in (Status.finishedWithWinner.map(_.id): _*),
|
||||
F.status $in Status.finishedWithWinner.map(_.id),
|
||||
F.winnerId -> $exists(true).++($ne(u))
|
||||
)
|
||||
|
||||
|
|
|
@ -145,7 +145,7 @@ object Dimension {
|
|||
def filtersOf[X](d: Dimension[X], selected: List[X]): Bdoc = d match {
|
||||
case Dimension.MovetimeRange => selected match {
|
||||
case Nil => $empty
|
||||
case xs => $doc(d.dbKey -> $doc("$in" -> xs.flatMap(_.tenths.list)))
|
||||
case xs => $doc(d.dbKey $in xs.flatMap(_.tenths.list))
|
||||
}
|
||||
case _ => selected map d.bson.write match {
|
||||
case Nil => $empty
|
||||
|
|
|
@ -13,6 +13,7 @@ final class Env(
|
|||
onStart: String => Unit,
|
||||
blocking: String => Fu[Set[String]],
|
||||
playban: String => Fu[Option[lila.playban.TempBan]],
|
||||
gameCache: lila.game.Cached,
|
||||
system: ActorSystem,
|
||||
scheduler: lila.common.Scheduler) {
|
||||
|
||||
|
@ -29,6 +30,7 @@ final class Env(
|
|||
val CollectionSeekArchive = config getString "collection.seek_archive"
|
||||
val SeekMaxPerPage = config getInt "seek.max_per_page"
|
||||
val SeekMaxPerUser = config getInt "seek.max_per_user"
|
||||
val MaxPlaying = config getInt "max_playing"
|
||||
}
|
||||
import settings._
|
||||
|
||||
|
@ -49,6 +51,8 @@ final class Env(
|
|||
new Lobby(
|
||||
socket = socket,
|
||||
seekApi = seekApi,
|
||||
gameCache = gameCache,
|
||||
maxPlaying = MaxPlaying,
|
||||
blocking = blocking,
|
||||
playban = playban,
|
||||
onStart = onStart)
|
||||
|
@ -80,6 +84,7 @@ object Env {
|
|||
onStart = lila.game.Env.current.onStart,
|
||||
blocking = lila.relation.Env.current.api.fetchBlocking,
|
||||
playban = lila.playban.Env.current.api.currentBan _,
|
||||
gameCache = lila.game.Env.current.cached,
|
||||
system = lila.common.PlayApp.system,
|
||||
scheduler = lila.common.PlayApp.scheduler)
|
||||
}
|
||||
|
|
|
@ -15,6 +15,8 @@ import org.joda.time.DateTime
|
|||
private[lobby] final class Lobby(
|
||||
socket: ActorRef,
|
||||
seekApi: SeekApi,
|
||||
gameCache: lila.game.Cached,
|
||||
maxPlaying: Int,
|
||||
blocking: String => Fu[Set[String]],
|
||||
playban: String => Fu[Option[lila.playban.TempBan]],
|
||||
onStart: String => Unit) extends Actor {
|
||||
|
@ -72,10 +74,14 @@ private[lobby] final class Lobby(
|
|||
}
|
||||
|
||||
case BiteSeek(seekId, user) => NoPlayban(user.some) {
|
||||
lila.mon.lobby.seek.join()
|
||||
seekApi find seekId foreach {
|
||||
_ foreach { seek =>
|
||||
Biter(seek, user) pipeTo self
|
||||
gameCache.nbPlaying(user.id) foreach { nbPlaying =>
|
||||
if (nbPlaying < maxPlaying) {
|
||||
lila.mon.lobby.seek.join()
|
||||
seekApi find seekId foreach {
|
||||
_ foreach { seek =>
|
||||
Biter(seek, user) pipeTo self
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ final class Env(
|
|||
api = api,
|
||||
toleranceStep = config getInt "selector.tolerance.step",
|
||||
toleranceMax = config getInt "selector.tolerance.max",
|
||||
modulo = config getInt "selector.modulo")
|
||||
maxAttempts = config getInt "selector.max_attempts")
|
||||
|
||||
lazy val finisher = new Finisher(
|
||||
api = api,
|
||||
|
|
|
@ -33,21 +33,14 @@ private[opening] final class OpeningApi(
|
|||
def add(a: Attempt) = attemptColl insert a void
|
||||
|
||||
def hasPlayed(user: User, opening: Opening): Fu[Boolean] =
|
||||
attemptColl.count($doc(
|
||||
attemptColl.exists($doc(
|
||||
Attempt.BSONFields.id -> Attempt.makeId(opening.id, user.id)
|
||||
).some) map (0!=)
|
||||
))
|
||||
|
||||
def playedIds(user: User, max: Int): Fu[BSONArray] = {
|
||||
val col = attemptColl
|
||||
import reactivemongo.api.collections.bson.BSONBatchCommands.AggregationFramework.{ Group, Limit, Match, Push }
|
||||
|
||||
val playedIdsGroup =
|
||||
Group($boolean(true))("ids" -> Push(Attempt.BSONFields.openingId))
|
||||
|
||||
col.aggregate(Match($doc(Attempt.BSONFields.userId -> user.id)),
|
||||
List(Limit(max), playedIdsGroup)).map(_.documents.headOption.flatMap(
|
||||
_.getAs[BSONArray]("ids")).getOrElse(BSONArray()))
|
||||
}
|
||||
def playedIds(user: User): Fu[BSONArray] =
|
||||
attemptColl.distinct(Attempt.BSONFields.openingId,
|
||||
$doc(Attempt.BSONFields.userId -> user.id).some
|
||||
) map BSONArray.apply
|
||||
}
|
||||
|
||||
object identify {
|
||||
|
|
|
@ -12,32 +12,32 @@ private[opening] final class Selector(
|
|||
api: OpeningApi,
|
||||
toleranceStep: Int,
|
||||
toleranceMax: Int,
|
||||
modulo: Int) {
|
||||
maxAttempts: Int) {
|
||||
|
||||
val anonSkipMax = 1500
|
||||
|
||||
def apply(me: Option[User]): Fu[Opening] = (me match {
|
||||
def apply(me: Option[User]): Fu[Option[Opening]] = (me match {
|
||||
case None =>
|
||||
openingColl.find($empty)
|
||||
.skip(Random nextInt anonSkipMax)
|
||||
.uno[Opening] flatten "Can't find a opening for anon player!"
|
||||
case Some(user) => api.attempt.playedIds(user, modulo) flatMap { ids =>
|
||||
.uno[Opening]
|
||||
case Some(user) if user.perfs.opening.nb >= maxAttempts => fuccess(none)
|
||||
case Some(user) => api.attempt.playedIds(user) flatMap { ids =>
|
||||
tryRange(user, toleranceStep, ids)
|
||||
} recoverWith {
|
||||
case e: Exception => apply(none)
|
||||
}
|
||||
}).mon(_.opening.selector.time) >>- lila.mon.opening.selector.count()
|
||||
|
||||
private def tryRange(user: User, tolerance: Int, ids: BSONArray): Fu[Opening] =
|
||||
private def tryRange(user: User, tolerance: Int, ids: BSONArray): Fu[Option[Opening]] =
|
||||
openingColl.uno[Opening]($doc(
|
||||
Opening.BSONFields.id -> $doc("$nin" -> ids),
|
||||
Opening.BSONFields.rating $gt
|
||||
(user.perfs.opening.intRating - tolerance) $lt
|
||||
(user.perfs.opening.intRating + tolerance)
|
||||
)) flatMap {
|
||||
case Some(opening) => fuccess(opening)
|
||||
case None => if ((tolerance + toleranceStep) <= toleranceMax)
|
||||
case None if (tolerance + toleranceStep) <= toleranceMax =>
|
||||
tryRange(user, tolerance + toleranceStep, ids)
|
||||
else fufail(s"Can't find a opening for user $user!")
|
||||
case res => fuccess(res)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package lila.playban
|
||||
|
||||
import akka.actor.{ ActorSelection, ActorSystem }
|
||||
import akka.actor._
|
||||
import com.typesafe.config.Config
|
||||
import scala.concurrent.duration._
|
||||
|
||||
|
@ -16,9 +16,7 @@ final class Env(
|
|||
}
|
||||
import settings._
|
||||
|
||||
lazy val api = new PlaybanApi(coll = coll, isRematch = isRematch)
|
||||
|
||||
private lazy val coll = db(CollectionPlayban)
|
||||
lazy val api = new PlaybanApi(coll = db(CollectionPlayban), isRematch = isRematch)
|
||||
}
|
||||
|
||||
object Env {
|
||||
|
|
|
@ -3,7 +3,6 @@ package lila.playban
|
|||
import org.joda.time.DateTime
|
||||
import reactivemongo.bson._
|
||||
import reactivemongo.bson.Macros
|
||||
import reactivemongo.core.commands._
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import chess.Color
|
||||
|
@ -43,38 +42,57 @@ final class PlaybanApi(
|
|||
game.player(quitterColor).userId ?? save(Outcome.RageQuit)
|
||||
}
|
||||
|
||||
def sittingOrGood(game: Game, sitterColor: Color): Funit = blameable(game) ?? {
|
||||
(for {
|
||||
userId <- game.player(sitterColor).userId
|
||||
lmt <- game.lastMoveTimeInSeconds
|
||||
seconds = nowSeconds - lmt
|
||||
clock <- game.clock
|
||||
// a tenth of the total time, at least 15s, at most 3 minutes
|
||||
limit = (clock.estimateTotalTime / 10) max 15 min (3 * 60)
|
||||
if seconds >= limit
|
||||
} yield save(Outcome.Sitting)(userId)) | goodFinish(game)
|
||||
}
|
||||
|
||||
def goodFinish(game: Game): Funit = blameable(game) ?? {
|
||||
game.userIds.map(save(Outcome.Good)).sequenceFu.void
|
||||
}
|
||||
|
||||
def currentBan(userId: String): Fu[Option[TempBan]] = coll.find(
|
||||
$doc("_id" -> userId, "b.0" -> $doc("$exists" -> true)),
|
||||
$doc("_id" -> userId, "b.0" $exists true),
|
||||
$doc("_id" -> false, "b" -> $doc("$slice" -> -1))
|
||||
).uno[Bdoc].map {
|
||||
_.flatMap(_.getAs[List[TempBan]]("b")).??(_.find(_.inEffect))
|
||||
}
|
||||
|
||||
def bans(userId: String): Fu[List[TempBan]] = coll.find(
|
||||
$doc("_id" -> userId, "b.0" -> $doc("$exists" -> true)),
|
||||
$doc("_id" -> false, "b" -> true)
|
||||
).uno[Bdoc].map {
|
||||
~_.flatMap(_.getAs[List[TempBan]]("b"))
|
||||
def completionRate(userId: String): Fu[Option[Double]] =
|
||||
coll.primitiveOne[List[Outcome]]($id(userId), "o").map(~_) map { outcomes =>
|
||||
outcomes.collect {
|
||||
case Outcome.RageQuit | Outcome.Sitting => false
|
||||
case Outcome.Good => true
|
||||
} match {
|
||||
case c if c.size >= 5 => Some(c.count(identity).toDouble / c.size)
|
||||
case _ => none
|
||||
}
|
||||
}
|
||||
|
||||
def bans(userId: String): Fu[List[TempBan]] =
|
||||
coll.primitiveOne[List[TempBan]]($doc("_id" -> userId, "b.0" $exists true), "b").map(~_)
|
||||
|
||||
def bans(userIds: List[String]): Fu[Map[String, Int]] = coll.find(
|
||||
$inIds(userIds),
|
||||
$doc("b" -> true)
|
||||
).cursor[Bdoc]().gather[List]().map {
|
||||
_.flatMap { obj =>
|
||||
obj.getAs[String]("_id") flatMap { id =>
|
||||
obj.getAs[BSONArray]("b") map { id -> _.stream.size }
|
||||
obj.getAs[Barr]("b") map { id -> _.stream.size }
|
||||
}
|
||||
}.toMap
|
||||
}
|
||||
|
||||
private def save(outcome: Outcome): String => Funit = userId => {
|
||||
coll.findAndUpdate(
|
||||
selector = $doc("_id" -> userId),
|
||||
selector = $id(userId),
|
||||
update = $doc("$push" -> $doc(
|
||||
"o" -> $doc(
|
||||
"$each" -> List(outcome),
|
||||
|
@ -89,15 +107,13 @@ final class PlaybanApi(
|
|||
|
||||
private def legiferate(record: UserRecord): Funit = record.newBan ?? { ban =>
|
||||
coll.update(
|
||||
$doc("_id" -> record.userId),
|
||||
$doc(
|
||||
"$unset" -> $doc("o" -> true),
|
||||
"$push" -> $doc(
|
||||
$id(record.userId),
|
||||
$unset("o") ++
|
||||
$push(
|
||||
"b" -> $doc(
|
||||
"$each" -> List(ban),
|
||||
"$slice" -> -30)
|
||||
)
|
||||
)
|
||||
).void
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,9 +62,10 @@ object Outcome {
|
|||
case object Good extends Outcome(0, "Nothing unusual")
|
||||
case object Abort extends Outcome(1, "Aborts the game")
|
||||
case object NoPlay extends Outcome(2, "Won't play a move")
|
||||
case object RageQuit extends Outcome(3, "Quit without resigning")
|
||||
case object RageQuit extends Outcome(3, "Quits without resigning")
|
||||
case object Sitting extends Outcome(4, "Lets time run out")
|
||||
|
||||
val all = List(Good, Abort, NoPlay, RageQuit)
|
||||
val all = List(Good, Abort, NoPlay, RageQuit, Sitting)
|
||||
|
||||
val byId = all map { v => (v.id, v) } toMap
|
||||
|
||||
|
|
|
@ -115,10 +115,9 @@ final class PrefApi(
|
|||
}
|
||||
|
||||
def unfollowableIds(userIds: List[String]): Fu[Set[String]] =
|
||||
coll.distinct("_id", BSONDocument(
|
||||
"_id" -> BSONDocument("$in" -> userIds),
|
||||
coll.distinct("_id", ($inIds(userIds) ++ $doc(
|
||||
"follow" -> false
|
||||
).some) map lila.db.BSON.asStringSet
|
||||
)).some) map lila.db.BSON.asStringSet
|
||||
|
||||
def followableIds(userIds: List[String]): Fu[Set[String]] =
|
||||
unfollowableIds(userIds) map userIds.toSet.diff
|
||||
|
|
|
@ -95,11 +95,11 @@ private[puzzle] final class PuzzleApi(
|
|||
def add(a: Attempt) = attemptColl insert a void
|
||||
|
||||
def hasPlayed(user: User, puzzle: Puzzle): Fu[Boolean] =
|
||||
attemptColl.count($doc(
|
||||
attemptColl.exists($doc(
|
||||
Attempt.BSONFields.id -> Attempt.makeId(puzzle.id, user.id)
|
||||
).some) map (0!=)
|
||||
))
|
||||
|
||||
def playedIds(user: User, max: Int): Fu[BSONArray] =
|
||||
def playedIds(user: User): Fu[BSONArray] =
|
||||
attemptColl.distinct(Attempt.BSONFields.puzzleId,
|
||||
$doc(Attempt.BSONFields.userId -> user.id).some
|
||||
) map BSONArray.apply
|
||||
|
|
|
@ -35,11 +35,11 @@ private[puzzle] final class Selector(
|
|||
puzzleColl.find(popularSelector(isMate) ++ mateSelector(isMate))
|
||||
.skip(Random nextInt anonSkipMax)
|
||||
.uno[Puzzle]
|
||||
case Some(user) if user.perfs.puzzle.nb > maxAttempts => fuccess(none)
|
||||
case Some(user) if user.perfs.puzzle.nb >= maxAttempts => fuccess(none)
|
||||
case Some(user) =>
|
||||
val rating = user.perfs.puzzle.intRating min 2300 max 900
|
||||
val step = toleranceStepFor(rating)
|
||||
api.attempt.playedIds(user, maxAttempts) flatMap { ids =>
|
||||
api.attempt.playedIds(user) flatMap { ids =>
|
||||
tryRange(rating, step, step, difficultyDecay(difficulty), ids, isMate)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,7 +60,7 @@ final class QaApi(
|
|||
questionColl.find($doc("_id" -> id)).uno[Question]
|
||||
|
||||
def findByIds(ids: List[QuestionId]): Fu[List[Question]] =
|
||||
questionColl.find($doc("_id" -> $doc("$in" -> ids.distinct))).cursor[Question]().gather[List]()
|
||||
questionColl.find($inIds(ids.distinct)).cursor[Question]().gather[List]()
|
||||
|
||||
def accept(q: Question) = questionColl.update(
|
||||
$doc("_id" -> q.id),
|
||||
|
@ -99,7 +99,7 @@ final class QaApi(
|
|||
.cursor[Question]().gather[List](max)
|
||||
|
||||
def byTags(tags: List[String], max: Int): Fu[List[Question]] =
|
||||
questionColl.find($doc("tags" -> $doc("$in" -> tags.map(_.toLowerCase)))).cursor[Question]().gather[List](max)
|
||||
questionColl.find($doc("tags" $in tags.map(_.toLowerCase))).cursor[Question]().gather[List](max)
|
||||
|
||||
def addComment(c: Comment)(q: Question) = questionColl.update(
|
||||
$doc("_id" -> q.id),
|
||||
|
@ -117,7 +117,7 @@ final class QaApi(
|
|||
}
|
||||
|
||||
def incViews(q: Question) = questionColl.update(
|
||||
$doc("_id" -> q.id),
|
||||
$id(q.id),
|
||||
$doc("$inc" -> $doc("views" -> BSONInteger(1))))
|
||||
|
||||
def recountAnswers(id: QuestionId) = answer.countByQuestionId(id) flatMap {
|
||||
|
|
|
@ -115,7 +115,7 @@ private[report] final class ReportApi(coll: Coll) {
|
|||
def processEngine(userId: String, byModId: String): Funit = coll.update(
|
||||
$doc(
|
||||
"user" -> userId,
|
||||
"reason" -> $in(Reason.Cheat.name, Reason.CheatPrint.name)
|
||||
"reason" $in List(Reason.Cheat.name, Reason.CheatPrint.name)
|
||||
) ++ unprocessedSelect,
|
||||
$set("processedBy" -> byModId),
|
||||
multi = true).void >>- monitorUnprocessed
|
||||
|
@ -123,7 +123,7 @@ private[report] final class ReportApi(coll: Coll) {
|
|||
def processTroll(userId: String, byModId: String): Funit = coll.update(
|
||||
$doc(
|
||||
"user" -> userId,
|
||||
"reason" -> $in(Reason.Insult.name, Reason.Troll.name, Reason.Other.name)
|
||||
"reason" $in List(Reason.Insult.name, Reason.Troll.name, Reason.Other.name)
|
||||
) ++ unprocessedSelect,
|
||||
$set("processedBy" -> byModId),
|
||||
multi = true).void >>- monitorUnprocessed
|
||||
|
|
|
@ -26,11 +26,12 @@ private[round] final class Finisher(
|
|||
}
|
||||
|
||||
def rageQuit(game: Game, winner: Option[Color])(implicit proxy: GameProxy): Fu[Events] =
|
||||
apply(game, _.Timeout, winner) >>- winner.?? { color => playban.rageQuit(game, !color) }
|
||||
apply(game, _.Timeout, winner) >>-
|
||||
winner.?? { color => playban.rageQuit(game, !color) }
|
||||
|
||||
def outOfTime(game: Game)(implicit proxy: GameProxy): Fu[Events] = {
|
||||
import lila.common.PlayApp
|
||||
if (!PlayApp.startedSinceSeconds(120) && game.updatedAt.exists(_ isBefore PlayApp.startedAt)) {
|
||||
if (!PlayApp.startedSinceSeconds(60) && game.updatedAt.exists(_ isBefore PlayApp.startedAt)) {
|
||||
logger.info(s"Aborting game last played before JVM boot: ${game.id}")
|
||||
other(game, _.Aborted, none)
|
||||
}
|
||||
|
@ -38,7 +39,8 @@ private[round] final class Finisher(
|
|||
val winner = Some(!game.player.color) filterNot { color =>
|
||||
game.toChess.board.variant.insufficientWinningMaterial(game.toChess.situation.board, color)
|
||||
}
|
||||
other(game, _.Outoftime, winner)
|
||||
apply(game, _.Outoftime, winner) >>-
|
||||
winner.?? { color => playban.sittingOrGood(game, !color) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ final class ForecastApi(coll: Coll, roundMap: akka.actor.ActorSelection) {
|
|||
private def saveSteps(pov: Pov, steps: Forecast.Steps): Funit = {
|
||||
lila.mon.round.forecast.create()
|
||||
coll.update(
|
||||
$doc("_id" -> pov.fullId),
|
||||
$id(pov.fullId),
|
||||
Forecast(
|
||||
_id = pov.fullId,
|
||||
steps = steps,
|
||||
|
@ -37,7 +37,7 @@ final class ForecastApi(coll: Coll, roundMap: akka.actor.ActorSelection) {
|
|||
}
|
||||
|
||||
def save(pov: Pov, steps: Forecast.Steps): Funit = firstStep(steps) match {
|
||||
case None => coll.remove($doc("_id" -> pov.fullId)).void
|
||||
case None => coll.remove($id(pov.fullId)).void
|
||||
case Some(step) if pov.game.turns == step.ply - 1 => saveSteps(pov, steps)
|
||||
case _ => fufail(Forecast.OutOfSync)
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ final class ForecastApi(coll: Coll, roundMap: akka.actor.ActorSelection) {
|
|||
}
|
||||
|
||||
def loadForDisplay(pov: Pov): Fu[Option[Forecast]] =
|
||||
pov.forecastable ?? coll.find($doc("_id" -> pov.fullId)).uno[Forecast] flatMap {
|
||||
pov.forecastable ?? coll.find($id(pov.fullId)).uno[Forecast] flatMap {
|
||||
case None => fuccess(none)
|
||||
case Some(fc) =>
|
||||
if (firstStep(fc.steps).exists(_.ply != pov.game.turns + 1)) clearPov(pov) inject none
|
||||
|
@ -67,7 +67,7 @@ final class ForecastApi(coll: Coll, roundMap: akka.actor.ActorSelection) {
|
|||
}
|
||||
|
||||
def loadForPlay(pov: Pov): Fu[Option[Forecast]] =
|
||||
pov.game.forecastable ?? coll.find($doc("_id" -> pov.fullId)).uno[Forecast] flatMap {
|
||||
pov.game.forecastable ?? coll.find($id(pov.fullId)).uno[Forecast] flatMap {
|
||||
case None => fuccess(none)
|
||||
case Some(fc) =>
|
||||
if (firstStep(fc.steps).exists(_.ply != pov.game.turns)) clearPov(pov) inject none
|
||||
|
@ -79,7 +79,7 @@ final class ForecastApi(coll: Coll, roundMap: akka.actor.ActorSelection) {
|
|||
case None => fuccess(none)
|
||||
case Some(fc) => fc(g, last) match {
|
||||
case Some((newFc, uciMove)) if newFc.steps.nonEmpty =>
|
||||
coll.update($doc("_id" -> fc._id), newFc) inject uciMove.some
|
||||
coll.update($id(fc._id), newFc) inject uciMove.some
|
||||
case Some((newFc, uciMove)) => clearPov(Pov player g) inject uciMove.some
|
||||
case _ => clearPov(Pov player g) inject none
|
||||
}
|
||||
|
@ -88,9 +88,7 @@ final class ForecastApi(coll: Coll, roundMap: akka.actor.ActorSelection) {
|
|||
|
||||
private def firstStep(steps: Forecast.Steps) = steps.headOption.flatMap(_.headOption)
|
||||
|
||||
def clearGame(g: Game) = coll.remove($doc(
|
||||
"_id" -> $doc("$in" -> chess.Color.all.map(g.fullIdOf))
|
||||
)).void
|
||||
def clearGame(g: Game) = coll.remove($inIds(chess.Color.all.map(g.fullIdOf))).void
|
||||
|
||||
def clearPov(pov: Pov) = coll.remove($doc("_id" -> pov.fullId)).void
|
||||
def clearPov(pov: Pov) = coll.remove($id(pov.fullId)).void
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ final class NoteApi(coll: Coll) {
|
|||
coll.primitiveOne[String]($id(makeId(gameId, userId)), "t") map (~_)
|
||||
|
||||
def set(gameId: String, userId: String, text: String) = {
|
||||
if (text.isEmpty) coll.remove(BSONDocument("_id" -> makeId(gameId, userId)))
|
||||
if (text.isEmpty) coll.remove($id(makeId(gameId, userId)))
|
||||
else coll.update(
|
||||
$id(makeId(gameId, userId)),
|
||||
$set("t" -> text),
|
||||
|
|
|
@ -100,8 +100,8 @@ final class Api(
|
|||
case values => coll.distinct(
|
||||
"user",
|
||||
$doc(
|
||||
field -> $doc("$in" -> values),
|
||||
"user" -> $doc("$ne" -> userId)
|
||||
field $in values,
|
||||
"user" $ne userId
|
||||
).some
|
||||
) map lila.db.BSON.asStrings
|
||||
}
|
||||
|
@ -115,7 +115,7 @@ final class Api(
|
|||
"user",
|
||||
$doc(
|
||||
field -> value,
|
||||
"date" -> $doc("$gt" -> DateTime.now.minusYears(1))
|
||||
"date" $gt DateTime.now.minusYears(1)
|
||||
).some
|
||||
) map lila.db.BSON.asStrings
|
||||
}
|
||||
|
|
|
@ -74,8 +74,8 @@ object UserSpy {
|
|||
values.nonEmpty ?? {
|
||||
coll.distinct("user",
|
||||
$doc(
|
||||
field -> $doc("$in" -> values),
|
||||
"user" -> $doc("$ne" -> user.id)
|
||||
field $in values,
|
||||
"user" $ne user.id
|
||||
).some
|
||||
) map lila.db.BSON.asStrings flatMap { userIds =>
|
||||
userIds.nonEmpty ?? (UserRepo byIds userIds) map (_.toSet)
|
||||
|
|
|
@ -15,9 +15,11 @@ final class Env(
|
|||
onStart: String => Unit,
|
||||
prefApi: lila.pref.PrefApi,
|
||||
relationApi: lila.relation.RelationApi,
|
||||
gameCache: lila.game.Cached,
|
||||
system: ActorSystem) {
|
||||
|
||||
private val FriendMemoTtl = config duration "friend.memo.ttl"
|
||||
private val MaxPlaying = config getInt "max_playing"
|
||||
private val CollectionUserConfig = config getString "collection.user_config"
|
||||
private val CollectionAnonConfig = config getString "collection.anon_config"
|
||||
|
||||
|
@ -30,6 +32,8 @@ final class Env(
|
|||
|
||||
lazy val processor = new Processor(
|
||||
lobby = hub.actor.lobby,
|
||||
gameCache = gameCache,
|
||||
maxPlaying = MaxPlaying,
|
||||
fishnetPlayer = fishnetPlayer,
|
||||
onStart = onStart)
|
||||
|
||||
|
@ -47,5 +51,6 @@ object Env {
|
|||
onStart = lila.game.Env.current.onStart,
|
||||
prefApi = lila.pref.Env.current.api,
|
||||
relationApi = lila.relation.Env.current.api,
|
||||
gameCache = lila.game.Env.current.cached,
|
||||
system = lila.common.PlayApp.system)
|
||||
}
|
||||
|
|
|
@ -15,6 +15,8 @@ import makeTimeout.short
|
|||
|
||||
private[setup] final class Processor(
|
||||
lobby: ActorSelection,
|
||||
gameCache: lila.game.Cached,
|
||||
maxPlaying: Int,
|
||||
fishnetPlayer: lila.fishnet.Player,
|
||||
onStart: String => Unit) {
|
||||
|
||||
|
@ -40,20 +42,24 @@ private[setup] final class Processor(
|
|||
configBase: HookConfig,
|
||||
uid: String,
|
||||
sid: Option[String],
|
||||
blocking: Set[String])(implicit ctx: UserContext): Fu[String] = {
|
||||
blocking: Set[String])(implicit ctx: UserContext): Fu[Processor.HookResult] = {
|
||||
import Processor.HookResult._
|
||||
val config = configBase.fixColor
|
||||
saveConfig(_ withHook config) >> {
|
||||
config.hook(uid, ctx.me, sid, blocking) match {
|
||||
case Left(hook) => fuccess {
|
||||
lobby ! AddHook(hook)
|
||||
hook.id
|
||||
Created(hook.id)
|
||||
}
|
||||
case Right(Some(seek)) => fuccess {
|
||||
lobby ! AddSeek(seek)
|
||||
seek.id
|
||||
case Right(Some(seek)) => ctx.userId.??(gameCache.nbPlaying) map { nbPlaying =>
|
||||
if (nbPlaying >= maxPlaying) Refused
|
||||
else {
|
||||
lobby ! AddSeek(seek)
|
||||
Created(seek.id)
|
||||
}
|
||||
}
|
||||
case Right(None) if ctx.me.isEmpty => fufail(new IllegalArgumentException("Anon can't create seek"))
|
||||
case _ => fufail("Can't create seek for some unknown reason")
|
||||
case Right(None) if ctx.me.isEmpty => fuccess(Refused)
|
||||
case _ => fuccess(Refused)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -64,3 +70,12 @@ private[setup] final class Processor(
|
|||
private def saveConfig(map: UserConfig => UserConfig)(implicit ctx: UserContext): Funit =
|
||||
ctx.me.fold(AnonConfigRepo.update(ctx.req) _)(user => UserConfigRepo.update(user) _)(map)
|
||||
}
|
||||
|
||||
object Processor {
|
||||
|
||||
sealed trait HookResult
|
||||
object HookResult {
|
||||
case class Created(id: String) extends HookResult
|
||||
case object Refused extends HookResult
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ final class ChapterRepo(coll: Coll) {
|
|||
|
||||
def namesByStudyIds(studyIds: Seq[Study.ID]): Fu[Map[Study.ID, Vector[String]]] =
|
||||
coll.find(
|
||||
$doc("studyId" -> $doc("$in" -> studyIds)),
|
||||
$doc("studyId" $in studyIds),
|
||||
$doc("studyId" -> true, "name" -> true)
|
||||
).sort($sort asc "order").list[Bdoc]().map { docs =>
|
||||
docs.foldLeft(Map.empty[Study.ID, Vector[String]]) {
|
||||
|
|
|
@ -33,5 +33,5 @@ object RequestRepo {
|
|||
|
||||
def selectId(teamId: ID, userId: ID) = $id(Request.makeId(teamId, userId))
|
||||
def teamQuery(teamId: ID) = $doc("team" -> teamId)
|
||||
def teamsQuery(teamIds: List[ID]) = $doc("team".$in(teamIds:_*))
|
||||
def teamsQuery(teamIds: List[ID]) = $doc("team" $in teamIds)
|
||||
}
|
||||
|
|
|
@ -21,9 +21,8 @@ private[timeline] final class UnsubApi(coll: Coll) {
|
|||
coll.count(select(channel, userId).some) map (0 !=)
|
||||
|
||||
def filterUnsub(channel: String, userIds: List[String]): Fu[List[String]] =
|
||||
coll.distinct("_id", $doc(
|
||||
"_id" $in (userIds.map { makeId(channel, _) }: _*)
|
||||
).some) map lila.db.BSON.asStrings map { unsubs =>
|
||||
userIds diff unsubs.map(_ takeWhile ('@' !=))
|
||||
}
|
||||
coll.distinct("_id", $inIds(userIds.map { makeId(channel, _) }).some) map
|
||||
lila.db.BSON.asStrings map { unsubs =>
|
||||
userIds diff unsubs.map(_ takeWhile ('@' !=))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ object PairingRepo {
|
|||
|
||||
def lastOpponents(tourId: String, userIds: Iterable[String], nb: Int): Fu[Pairing.LastOpponents] =
|
||||
coll.find(
|
||||
selectTour(tourId) ++ $doc("u" -> $doc("$in" -> userIds)),
|
||||
selectTour(tourId) ++ $doc("u" $in userIds),
|
||||
$doc("_id" -> false, "u" -> true)
|
||||
).sort(recentSort).cursor[Bdoc]().enumerate(nb) |>>>
|
||||
Iteratee.fold(scala.collection.immutable.Map.empty[String, String]) { (acc, doc) =>
|
||||
|
|
|
@ -131,7 +131,7 @@ object PlayerRepo {
|
|||
}
|
||||
|
||||
def byTourAndUserIds(tourId: String, userIds: Iterable[String]): Fu[List[Player]] =
|
||||
coll.find(selectTour(tourId) ++ $doc("uid" -> $doc("$in" -> userIds)))
|
||||
coll.find(selectTour(tourId) ++ $doc("uid" $in userIds))
|
||||
.list[Player]()
|
||||
.chronometer.logIfSlow(200, logger) { players =>
|
||||
s"PlayerRepo.byTourAndUserIds $tourId ${userIds.size} user IDs, ${players.size} players"
|
||||
|
|
|
@ -13,10 +13,8 @@ object TournamentRepo {
|
|||
|
||||
private lazy val coll = Env.current.tournamentColl
|
||||
|
||||
private def $id(id: String) = $doc("_id" -> id)
|
||||
|
||||
private val enterableSelect = $doc(
|
||||
"status" $in (Status.Created.id, Status.Started.id))
|
||||
"status" $in List(Status.Created.id, Status.Started.id))
|
||||
|
||||
private val createdSelect = $doc("status" -> Status.Created.id)
|
||||
private val startedSelect = $doc("status" -> Status.Started.id)
|
||||
|
@ -203,7 +201,7 @@ object TournamentRepo {
|
|||
def lastFinishedScheduledByFreq(freq: Schedule.Freq, since: DateTime): Fu[List[Tournament]] = coll.find(
|
||||
finishedSelect ++ sinceSelect(since) ++ variantSelect(chess.variant.Standard) ++ $doc(
|
||||
"schedule.freq" -> freq.name,
|
||||
"schedule.speed".$in(Schedule.Speed.mostPopular.map(_.name): _*)
|
||||
"schedule.speed" $in Schedule.Speed.mostPopular.map(_.name)
|
||||
)
|
||||
).sort($doc("startsAt" -> -1))
|
||||
.list[Tournament](Schedule.Speed.mostPopular.size.some)
|
||||
|
@ -213,18 +211,18 @@ object TournamentRepo {
|
|||
$doc("schedule.freq" -> Schedule.Freq.Daily.name)
|
||||
).sort($doc("startsAt" -> -1)).uno[Tournament]
|
||||
|
||||
def update(tour: Tournament) = coll.update($doc("_id" -> tour.id), tour)
|
||||
def update(tour: Tournament) = coll.update($id(tour.id), tour)
|
||||
|
||||
def insert(tour: Tournament) = coll.insert(tour)
|
||||
|
||||
def remove(tour: Tournament) = coll.remove($doc("_id" -> tour.id))
|
||||
def remove(tour: Tournament) = coll.remove($id(tour.id))
|
||||
|
||||
def exists(id: String) = coll.count($doc("_id" -> id).some) map (0 !=)
|
||||
def exists(id: String) = coll exists $id(id)
|
||||
|
||||
def toursToWithdrawWhenEntering(tourId: String): Fu[List[Tournament]] =
|
||||
coll.find(enterableSelect ++ $doc(
|
||||
"_id" -> $doc("$ne" -> tourId),
|
||||
"schedule.freq" $nin (
|
||||
"_id" $ne tourId,
|
||||
"schedule.freq" $nin List(
|
||||
Schedule.Freq.Marathon.name,
|
||||
Schedule.Freq.Unique.name
|
||||
)
|
||||
|
|
|
@ -28,8 +28,6 @@ final class Cached(
|
|||
f = userColl.count(UserRepo.enabledSelect.some),
|
||||
timeToLive = nbTtl)
|
||||
|
||||
def countEnabled: Fu[Int] = countCache(true)
|
||||
|
||||
private implicit val LightUserBSONHandler = Macros.handler[LightUser]
|
||||
private implicit val LightPerfBSONHandler = Macros.handler[LightPerf]
|
||||
private implicit val LightCountBSONHandler = Macros.handler[LightCount]
|
||||
|
|
|
@ -45,8 +45,6 @@ final class Env(
|
|||
|
||||
def isOnline(userId: String) = onlineUserIdMemo get userId
|
||||
|
||||
def countEnabled = cached.countEnabled
|
||||
|
||||
def cli = new lila.common.Cli {
|
||||
def process = {
|
||||
case "user" :: "email" :: userId :: email :: Nil =>
|
||||
|
|
|
@ -23,8 +23,8 @@ final class NoteApi(
|
|||
coll.find(
|
||||
$doc(
|
||||
"to" -> user.id,
|
||||
"from" -> $doc("$in" -> (myFriendIds + me.id))
|
||||
) ++ me.troll.fold($doc(), $doc("troll" -> false))
|
||||
"from" $in (myFriendIds + me.id)
|
||||
) ++ me.troll.fold($empty, $doc("troll" -> false))
|
||||
).sort($doc("date" -> -1)).cursor[Note]().gather[List](100)
|
||||
|
||||
def write(to: User, text: String, from: User) = {
|
||||
|
|
|
@ -17,7 +17,7 @@ final class RankingApi(
|
|||
lightUser: String => Option[lila.common.LightUser]) {
|
||||
|
||||
import RankingApi._
|
||||
private implicit val rankingBSONHandler = reactivemongo.bson.Macros.handler[Ranking]
|
||||
private implicit val rankingBSONHandler = Macros.handler[Ranking]
|
||||
|
||||
def save(userId: User.ID, perfType: Option[PerfType], perfs: Perfs): Funit =
|
||||
perfType ?? { pt =>
|
||||
|
@ -25,9 +25,7 @@ final class RankingApi(
|
|||
}
|
||||
|
||||
def save(userId: User.ID, perfType: PerfType, perf: Perf): Funit =
|
||||
(perf.nb >= 2) ?? coll.update($doc(
|
||||
"_id" -> s"$userId:${perfType.id}"
|
||||
), $doc(
|
||||
(perf.nb >= 2) ?? coll.update($id(makeId(userId, perfType)), $doc(
|
||||
"user" -> userId,
|
||||
"perf" -> perfType.id,
|
||||
"rating" -> perf.intRating,
|
||||
|
@ -38,16 +36,17 @@ final class RankingApi(
|
|||
|
||||
def remove(userId: User.ID): Funit = UserRepo byId userId flatMap {
|
||||
_ ?? { user =>
|
||||
coll.remove($doc(
|
||||
"_id" -> $doc("$in" -> PerfType.leaderboardable.filter { pt =>
|
||||
coll.remove($inIds(
|
||||
PerfType.leaderboardable.filter { pt =>
|
||||
user.perfs(pt).nonEmpty
|
||||
}.map { pt =>
|
||||
s"${user.id}:${pt.id}"
|
||||
})
|
||||
}.map { makeId(user.id, _) }
|
||||
)).void
|
||||
}
|
||||
}
|
||||
|
||||
private def makeId(userId: User.ID, perfType: PerfType) =
|
||||
s"${userId}:${perfType.id}"
|
||||
|
||||
def topPerf(perfId: Perf.ID, nb: Int): Fu[List[User.LightPerf]] =
|
||||
PerfType.id2key(perfId) ?? { perfKey =>
|
||||
coll.find($doc("perf" -> perfId, "stable" -> true))
|
||||
|
@ -82,10 +81,10 @@ final class RankingApi(
|
|||
val enumerator = coll.find(
|
||||
$doc("perf" -> perfId, "stable" -> true),
|
||||
$doc("user" -> true, "_id" -> false)
|
||||
).sort($doc("rating" -> -1)).cursor[BSONDocument]().enumerate()
|
||||
).sort($doc("rating" -> -1)).cursor[Bdoc]().enumerate()
|
||||
var rank = 1
|
||||
val b = Map.newBuilder[User.ID, Rank]
|
||||
val mapBuilder: Iteratee[BSONDocument, Unit] = Iteratee.foreach { doc =>
|
||||
val mapBuilder: Iteratee[Bdoc, Unit] = Iteratee.foreach { doc =>
|
||||
doc.getAs[User.ID]("user") foreach { user =>
|
||||
b += (user -> rank)
|
||||
rank = rank + 1
|
||||
|
@ -115,9 +114,9 @@ final class RankingApi(
|
|||
List(Project($doc(
|
||||
"_id" -> false,
|
||||
"r" -> $doc(
|
||||
"$subtract" -> BSONArray(
|
||||
"$subtract" -> $arr(
|
||||
"$rating",
|
||||
$doc("$mod" -> BSONArray("$rating", Stat.group))
|
||||
$doc("$mod" -> $arr("$rating", Stat.group))
|
||||
)
|
||||
)
|
||||
)),
|
||||
|
|
|
@ -85,7 +85,7 @@ object UserRepo {
|
|||
|
||||
def orderByGameCount(u1: String, u2: String): Fu[Option[(String, String)]] = {
|
||||
coll.find(
|
||||
$doc("_id".$in(u1, u2)),
|
||||
$inIds(List(u1, u2)),
|
||||
$doc(s"${F.count}.game" -> true)
|
||||
).cursor[Bdoc]().gather[List]() map { docs =>
|
||||
docs.sortBy {
|
||||
|
@ -100,7 +100,7 @@ object UserRepo {
|
|||
def firstGetsWhite(u1O: Option[String], u2O: Option[String]): Fu[Boolean] =
|
||||
(u1O |@| u2O).tupled.fold(fuccess(scala.util.Random.nextBoolean)) {
|
||||
case (u1, u2) => coll.find(
|
||||
$doc("_id".$in(u1, u2)),
|
||||
$inIds(List(u1, u2)),
|
||||
$id(true)
|
||||
).sort($doc(F.colorIt -> 1)).uno[Bdoc].map {
|
||||
_.fold(scala.util.Random.nextBoolean) { doc =>
|
||||
|
|
|
@ -41,7 +41,7 @@ private[video] final class VideoApi(
|
|||
private val maxPerPage = 18
|
||||
|
||||
def find(id: Video.ID): Fu[Option[Video]] =
|
||||
videoColl.find($doc("_id" -> id)).uno[Video]
|
||||
videoColl.find($id(id)).uno[Video]
|
||||
|
||||
def search(user: Option[User], query: String, page: Int): Fu[Paginator[VideoView]] = {
|
||||
val q = query.split(' ').map { word => s""""$word"""" } mkString " "
|
||||
|
@ -61,18 +61,16 @@ private[video] final class VideoApi(
|
|||
|
||||
def save(video: Video): Funit =
|
||||
videoColl.update(
|
||||
$doc("_id" -> video.id),
|
||||
$id(video.id),
|
||||
$doc("$set" -> video),
|
||||
upsert = true).void
|
||||
|
||||
def removeNotIn(ids: List[Video.ID]) =
|
||||
videoColl.remove(
|
||||
$doc("_id" $nin (ids: _*))
|
||||
).void
|
||||
videoColl.remove($doc("_id" $nin ids)).void
|
||||
|
||||
def setMetadata(id: Video.ID, metadata: Youtube.Metadata) =
|
||||
videoColl.update(
|
||||
$doc("_id" -> id),
|
||||
$id(id),
|
||||
$doc("$set" -> $doc("metadata" -> metadata)),
|
||||
upsert = false
|
||||
).void
|
||||
|
@ -115,7 +113,7 @@ private[video] final class VideoApi(
|
|||
|
||||
def similar(user: Option[User], video: Video, max: Int): Fu[Seq[VideoView]] =
|
||||
videoColl.find($doc(
|
||||
"tags" $in (video.tags: _*),
|
||||
"tags" $in video.tags,
|
||||
"_id" $ne video.id
|
||||
)).sort($doc("metadata.likes" -> -1))
|
||||
.cursor[Video]()
|
||||
|
|
1
public/vendor/stockfish.js
vendored
Submodule
1
public/vendor/stockfish.js
vendored
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit e3670f6cf46aec942d444d8bb8c6599eb89982e3
|
32
public/vendor/stockfish7.js
vendored
32
public/vendor/stockfish7.js
vendored
File diff suppressed because one or more lines are too long
|
@ -13,7 +13,7 @@ module.exports = function(possible, variant, emit) {
|
|||
var enabled = m.prop(possible() && allowed() && lichess.storage.get(storageKey) === '1');
|
||||
var started = false;
|
||||
var pool = makePool({
|
||||
path: '/assets/vendor/stockfish7.js', // Can't CDN because same-origin policy
|
||||
path: '/assets/vendor/stockfish.js/stockfish.js', // Can't CDN because same-origin policy
|
||||
minDepth: minDepth,
|
||||
maxDepth: maxDepth,
|
||||
variant: variant
|
||||
|
|
|
@ -13,6 +13,10 @@ function uncache(url) {
|
|||
return url + '?_=' + new Date().getTime();
|
||||
}
|
||||
|
||||
function reloadPage() {
|
||||
location.href = '/training/opening';
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
attempt: function(ctrl) {
|
||||
showLoading(ctrl);
|
||||
|
@ -35,6 +39,6 @@ module.exports = {
|
|||
}).then(function(cfg) {
|
||||
ctrl.reload(cfg);
|
||||
ctrl.pushState(cfg);
|
||||
});
|
||||
}, reloadPage);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -54,7 +54,7 @@ function retry(ctrl) {
|
|||
}
|
||||
|
||||
function reloadPage() {
|
||||
location.href = '/training';
|
||||
location.href = '/training';
|
||||
}
|
||||
|
||||
function setDifficulty(ctrl, d) {
|
||||
|
|
Loading…
Reference in a new issue