Merge branch 'master' into tournamentLeader
* master: (54 commits) fix email form the signum of zero longer crazyhouse and classical daily tournaments play opponent berserk sound crazyhouse comes first unparallize build script to catch errors Remove one odd 'entry' word bench when document doesn't exist, it's the same story benchmark mongodb exist check. count is faster disable powertips in game tournament standings the powertip on tournament pairings is mostly annoying fix unfortunate typo Revert "Revert "no longer log insights invalid games"" ja "日本語" translation #15702. Author: danjyo. sr "Српски језик" translation #15701. Author: Charles_Martel. la "lingua Latina" translation #15700. Author: Orsi. Checkmate in latin is 'mattus' - https://la.wikipedia.org/wiki/Scacci ps "پښتو" translation #15699. Author: qimari. updated many words/sentences. going to do more tomorrow ps "پښتو" translation #15698. Author: qimari. de "Deutsch" translation #15697. Author: AKA121. 273, 296, 297, 346, 348, 361, 364, 415, 457, 481, 483, 484, 499, 502 (Die meisten Änderungen betreffen Du/Sie Konfigurationen. Ich habe jetzt das meiste zu Du geändert, da der Großteil schon Du war.) sl "slovenščina" translation #15696. Author: woodswoods. Better translation for word "board" ...
This commit is contained in:
commit
47ee9d615c
|
@ -1 +1,22 @@
|
|||
See https://github.com/ornicar/lila/wiki/Lichess-Development-Onboarding
|
||||
#### I need help contributing code to Lichess.
|
||||
|
||||
__For setting up your development environment, [read this guide](https://github.com/ornicar/lila/wiki/Lichess-Development-Onboarding).__
|
||||
|
||||
If you experience any issues, __fix them yourself__ or __demonstrate your efforts and make it easy to help__. As stated in the read-me file, I do **not** offer support for your Lichess instance.
|
||||
|
||||
#### I want to report a bug or a problem about Lichess.
|
||||
|
||||
[__Make an issue ticket.__](https://github.com/ornicar/lila/issues/new?title=Submitting a forum thread with the word "thibault" in its title crashes my browser!) However, note that issues that provide little value compared to the required effort may be closed. Before creating an issue, make sure that:
|
||||
|
||||
1. You list the steps to reproduce the problem to show that other users may experience it as well, if the issue is not self-descriptive.
|
||||
2. Search to make sure it isn't a duplicate [The advanced search syntax](https://help.github.com/articles/searching-issues/) may come in handy.
|
||||
3. It is not a trivial problem or demand unrealistic dev time to fix - Pluralization bugs and the such fall under this category.
|
||||
|
||||
#### I want to suggest a feature for Lichess.
|
||||
|
||||
Issue tickets on features that lack potential or effectiveness are not useful and may be closed. Discussions regarding whether a proposed new feature would be useful can be done on [The Lichess Feedback Forum](http://lichess.org/forum/lichess-feedback) to gauge feedback. The developers may also discuss the idea there, and if it is exemplary, a corresponding issue ticket will be made. __When you're ready, [make an issue ticket](https://github.com/ornicar/lila/issues/new?title=Please implement this chess variant idea I came up with)__ and link relevant, constructive comments regarding it in your issue ticket (such as a detailed Reddit post; Linking to an empty forum thread with only your own commentary adds no value). Make sure that the feature you propose:
|
||||
|
||||
1. Is __effective in delivering a goal__. A feature that adds nothing new is purely fancy; Please develop a userscript or userstyle for your personal use instead.
|
||||
2. Doesn't rely on mundane assumptions. Non-technical people have the tendency to measure how difficult / easy a feature is to implement based on their unreliable instincts, and such assumptions wastes everyone's time. __Point out what needs to happen__, not what you think will happen.
|
||||
3. Is __unique, if you're aiming to solve a problem__. Features that can easily be replaced by easier ideas have little value and may not have to be brought up to begin with.
|
||||
4. Is __clear and concise__. If ambiguities exist, define them or propose options.
|
||||
|
|
|
@ -35,7 +35,7 @@ object Message extends LilaController {
|
|||
implicit me =>
|
||||
NotForKids {
|
||||
OptionFuOk(api.thread(id, me)) { thread =>
|
||||
relationApi.blocks(thread otherUserId me, me.id) map { blocked =>
|
||||
relationApi.fetchBlocks(thread otherUserId me, me.id) map { blocked =>
|
||||
html.message.thread(thread, forms.post, blocked,
|
||||
answerable = !Env.message.LichessSenders.contains(thread.creatorId))
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ object Message extends LilaController {
|
|||
OptionFuResult(api.thread(id, me)) { thread =>
|
||||
implicit val req = ctx.body
|
||||
forms.post.bindFromRequest.fold(
|
||||
err => relationApi.blocks(thread otherUserId me, me.id) map { blocked =>
|
||||
err => relationApi.fetchBlocks(thread otherUserId me, me.id) map { blocked =>
|
||||
BadRequest(html.message.thread(thread, err, blocked,
|
||||
answerable = !Env.message.LichessSenders.contains(thread.creatorId)))
|
||||
},
|
||||
|
|
|
@ -6,6 +6,7 @@ import play.twirl.api.Html
|
|||
|
||||
import lila.api.Context
|
||||
import lila.app._
|
||||
import lila.common.paginator.{ Paginator, AdapterLike }
|
||||
import lila.relation.Related
|
||||
import lila.user.{ User => UserModel, UserRepo }
|
||||
import views._
|
||||
|
@ -15,9 +16,9 @@ object Relation extends LilaController {
|
|||
private def env = Env.relation
|
||||
|
||||
private def renderActions(userId: String, mini: Boolean)(implicit ctx: Context) =
|
||||
(ctx.userId ?? { env.api.relation(_, userId) }) zip
|
||||
(ctx.userId ?? { env.api.fetchRelation(_, userId) }) zip
|
||||
(ctx.isAuth ?? { Env.pref.api followable userId }) zip
|
||||
(ctx.userId ?? { env.api.blocks(userId, _) }) flatMap {
|
||||
(ctx.userId ?? { env.api.fetchBlocks(userId, _) }) flatMap {
|
||||
case ((relation, followable), blocked) => negotiate(
|
||||
html = fuccess(Ok(mini.fold(
|
||||
html.relation.mini(userId, blocked = blocked, followable = followable, relation = relation),
|
||||
|
@ -25,8 +26,8 @@ object Relation extends LilaController {
|
|||
))),
|
||||
api = _ => fuccess(Ok(Json.obj(
|
||||
"followable" -> followable,
|
||||
"following" -> relation.exists(true ==),
|
||||
"blocking" -> relation.exists(false ==)
|
||||
"following" -> relation.contains(true),
|
||||
"blocking" -> relation.contains(false)
|
||||
)))
|
||||
)
|
||||
}
|
||||
|
@ -51,70 +52,46 @@ object Relation extends LilaController {
|
|||
env.api.unblock(me.id, userId).nevermind >> renderActions(userId, getBool("mini"))
|
||||
}
|
||||
|
||||
def following(username: String) = Open { implicit ctx =>
|
||||
def following(username: String, page: Int) = Open { implicit ctx =>
|
||||
OptionFuOk(UserRepo named username) { user =>
|
||||
env.api.following(user.id) flatMap followship flatMap { rels =>
|
||||
env.api nbFollowers user.id map { followers =>
|
||||
html.relation.following(user, rels, followers)
|
||||
env.api countFollowers user.id flatMap { nbFollowers =>
|
||||
RelatedPager(env.api.followingPaginatorAdapter(user.id), page) map { pag =>
|
||||
html.relation.following(user, pag, nbFollowers)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def followers(username: String) = Open { implicit ctx =>
|
||||
def followers(username: String, page: Int) = Open { implicit ctx =>
|
||||
OptionFuOk(UserRepo named username) { user =>
|
||||
env.api.followers(user.id) flatMap followship flatMap { rels =>
|
||||
env.api nbFollowing user.id map { following =>
|
||||
html.relation.followers(user, rels, following)
|
||||
env.api countFollowing user.id flatMap { nbFollowing =>
|
||||
RelatedPager(env.api.followersPaginatorAdapter(user.id), page) map { pag =>
|
||||
html.relation.followers(user, pag, nbFollowing)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def blocks = Auth { implicit ctx =>
|
||||
def blocks(page: Int) = Auth { implicit ctx =>
|
||||
me =>
|
||||
env.api.blocking(me.id) flatMap followship map { rels =>
|
||||
html.relation.blocks(me, rels)
|
||||
RelatedPager(env.api.blockingPaginatorAdapter(me.id), page) map { pag =>
|
||||
html.relation.blocks(me, pag)
|
||||
}
|
||||
}
|
||||
|
||||
private def followship(userIds: Set[String])(implicit ctx: Context): Fu[List[Related]] =
|
||||
private def RelatedPager(adapter: AdapterLike[String], page: Int)(implicit ctx: Context) = Paginator(
|
||||
adapter = adapter mapFutureList followship,
|
||||
currentPage = page,
|
||||
maxPerPage = 30)
|
||||
|
||||
private def followship(userIds: Seq[String])(implicit ctx: Context): Fu[List[Related]] =
|
||||
UserRepo byIds userIds flatMap { users =>
|
||||
(ctx.isAuth ?? { Env.pref.api.followableIds(users map (_.id)) }) flatMap { followables =>
|
||||
users.map { u =>
|
||||
ctx.userId ?? { env.api.relation(_, u.id) } map { rel =>
|
||||
ctx.userId ?? { env.api.fetchRelation(_, u.id) } map { rel =>
|
||||
lila.relation.Related(u, 0, followables(u.id), rel)
|
||||
}
|
||||
}.sequenceFu
|
||||
}
|
||||
}
|
||||
|
||||
def suggest(username: String) = Open { implicit ctx =>
|
||||
OptionFuResult(UserRepo named username) { user =>
|
||||
lila.game.BestOpponents(user.id, 50) flatMap { opponents =>
|
||||
Env.pref.api.followableIds(opponents map (_._1.id)) zip
|
||||
env.api.onlinePopularUsers(20) flatMap {
|
||||
case (followables, popular) =>
|
||||
popular.filterNot(user ==).foldLeft(opponents filter {
|
||||
case (u, _) => followables contains u.id
|
||||
}) {
|
||||
case (xs, x) => xs.exists(_._1 == x).fold(xs, xs :+ (x, 0))
|
||||
}.map {
|
||||
case (u, nb) => env.api.relation(user.id, u.id) map {
|
||||
lila.relation.Related(u, nb, true, _)
|
||||
}
|
||||
}.sequenceFu flatMap { rels =>
|
||||
negotiate(
|
||||
html = fuccess(Ok(html.relation.suggest(user, rels))),
|
||||
api = _ => fuccess {
|
||||
implicit val userWrites = play.api.libs.json.Writes[UserModel] { Env.user.jsonView(_, true) }
|
||||
Ok(Json.obj(
|
||||
"user" -> user,
|
||||
"suggested" -> play.api.libs.json.JsArray(rels.map(_.toJson))))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ object Report extends LilaController {
|
|||
|
||||
def thanks(reported: String) = Auth { implicit ctx =>
|
||||
implicit me =>
|
||||
Env.relation.api.blocks(me.id, reported) map { blocked =>
|
||||
Env.relation.api.fetchBlocks(me.id, reported) map { blocked =>
|
||||
html.report.thanks(reported, blocked)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,9 +61,9 @@ object Setup extends LilaController with TheftPrevention {
|
|||
|
||||
private def challenge(user: lila.user.User)(implicit ctx: Context): Fu[Option[String]] = ctx.me match {
|
||||
case None => fuccess("Only registered players can send challenges.".some)
|
||||
case Some(me) => Env.relation.api.blocks(user.id, me.id) flatMap {
|
||||
case Some(me) => Env.relation.api.fetchBlocks(user.id, me.id) flatMap {
|
||||
case true => fuccess(s"{{user}} doesn't accept challenges from you.".some)
|
||||
case false => Env.pref.api getPref user zip Env.relation.api.follows(user.id, me.id) map {
|
||||
case false => Env.pref.api getPref user zip Env.relation.api.fetchFollows(user.id, me.id) map {
|
||||
case (pref, follow) => lila.pref.Pref.Challenge.block(me, user, pref.challenge, follow,
|
||||
fromCheat = me.engine && !user.engine)
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ object Setup extends LilaController with TheftPrevention {
|
|||
|
||||
def friend(userId: Option[String]) = process(env.forms.friend) { config =>
|
||||
implicit ctx =>
|
||||
(ctx.userId ?? GameRepo.removeChallengesOf) >> {
|
||||
(ctx.userId ?? GameRepo.removeRecentChallengesOf) >> {
|
||||
env.processor friend config map { pov =>
|
||||
pov -> routes.Setup.await(pov.fullId, userId)
|
||||
}
|
||||
|
@ -113,7 +113,7 @@ object Setup extends LilaController with TheftPrevention {
|
|||
err => negotiate(
|
||||
html = BadRequest(errorsAsJson(err).toString).fuccess,
|
||||
api = _ => BadRequest(errorsAsJson(err)).fuccess),
|
||||
config => (ctx.userId ?? Env.relation.api.blocking) flatMap {
|
||||
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(Json.obj("error" -> e.getMessage)) as JSON
|
||||
|
@ -131,7 +131,7 @@ object Setup extends LilaController with TheftPrevention {
|
|||
GameRepo game gameId map {
|
||||
_.fold(config)(config.updateFrom)
|
||||
} flatMap { config =>
|
||||
(ctx.userId ?? Env.relation.api.blocking) flatMap { blocking =>
|
||||
(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(Json.obj("error" -> e.getMessage)) as JSON
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ object Team extends LilaController {
|
|||
me => OptionFuResult(api team id) { team =>
|
||||
Owner(team) {
|
||||
MemberRepo userIdsByTeam team.id map { userIds =>
|
||||
html.team.kick(team, userIds filterNot (me.id ==))
|
||||
html.team.kick(team, userIds.filterNot(me.id ==).toList.sorted)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,10 +43,10 @@ object User extends LilaController {
|
|||
OptionFuResult(UserRepo named username) { user =>
|
||||
GameRepo lastPlayedPlaying user zip
|
||||
Env.donation.isDonor(user.id) zip
|
||||
(ctx.userId ?? { relationApi.blocks(user.id, _) }) zip
|
||||
(ctx.userId ?? { relationApi.fetchBlocks(user.id, _) }) zip
|
||||
(ctx.userId ?? { Env.game.crosstableApi(user.id, _) }) zip
|
||||
(ctx.isAuth ?? { Env.pref.api.followable(user.id) }) zip
|
||||
(ctx.userId ?? { relationApi.relation(_, user.id) }) map {
|
||||
(ctx.userId ?? { relationApi.fetchRelation(_, user.id) }) map {
|
||||
case (((((pov, donor), blocked), crosstable), followable), relation) =>
|
||||
Ok(html.user.mini(user, pov, blocked, followable, relation, crosstable, donor))
|
||||
.withHeaders(CACHE_CONTROL -> "max-age=5")
|
||||
|
@ -102,12 +102,12 @@ object User extends LilaController {
|
|||
filter = filters.current,
|
||||
me = ctx.me,
|
||||
page = page)(ctx.body)
|
||||
relation <- ctx.userId ?? { relationApi.relation(_, u.id) }
|
||||
relation <- ctx.userId ?? { relationApi.fetchRelation(_, u.id) }
|
||||
notes <- ctx.me ?? { me =>
|
||||
relationApi friends me.id flatMap { env.noteApi.get(u, me, _) }
|
||||
relationApi fetchFriends me.id flatMap { env.noteApi.get(u, me, _) }
|
||||
}
|
||||
followable <- ctx.isAuth ?? { Env.pref.api followable u.id }
|
||||
blocked <- ctx.userId ?? { relationApi.blocks(u.id, _) }
|
||||
blocked <- ctx.userId ?? { relationApi.fetchBlocks(u.id, _) }
|
||||
searchForm = GameFilterMenu.searchForm(userGameSearch, filters.current)(ctx.body)
|
||||
} yield html.user.show(u, info, pag, filters, searchForm, relation, notes, followable, blocked)
|
||||
|
||||
|
@ -211,8 +211,8 @@ object User extends LilaController {
|
|||
fuccess(List.fill(50)(true))
|
||||
) flatMap { followables =>
|
||||
(ops zip followables).map {
|
||||
case ((u, nb), followable) => ctx.userId ?? { myId =>
|
||||
relationApi.relation(myId, u.id)
|
||||
case ((u, nb), followable) => ctx.userId ?? {
|
||||
relationApi.fetchRelation(_, u.id)
|
||||
} map { lila.relation.Related(u, nb, followable, _) }
|
||||
}.sequenceFu map { relateds =>
|
||||
html.user.opponents(user, relateds)
|
||||
|
|
|
@ -74,9 +74,9 @@ object UserInfo {
|
|||
gameCached.nbImportedBy(user.id) zip
|
||||
(ctx.me.filter(user!=) ?? { me => crosstableApi(me.id, user.id) }) zip
|
||||
getRatingChart(user) zip
|
||||
relationApi.nbFollowing(user.id) zip
|
||||
relationApi.nbFollowers(user.id) zip
|
||||
(ctx.me ?? Granter(_.UserSpy) ?? { relationApi.nbBlockers(user.id) map (_.some) }) zip
|
||||
relationApi.countFollowing(user.id) zip
|
||||
relationApi.countFollowers(user.id) zip
|
||||
(ctx.me ?? Granter(_.UserSpy) ?? { relationApi.countBlockers(user.id) map (_.some) }) zip
|
||||
postApi.nbByUser(user.id) zip
|
||||
isDonor(user.id) zip
|
||||
trophyApi.findByUser(user) zip
|
||||
|
|
|
@ -14,7 +14,7 @@ trait TeamHelper {
|
|||
def myTeam(teamId: String)(implicit ctx: Context): Boolean =
|
||||
ctx.me.??(me => api.belongsTo(teamId, me.id))
|
||||
|
||||
def teamIds(userId: String): List[String] = api teamIds userId
|
||||
def teamIds(userId: String): Set[String] = api teamIds userId
|
||||
|
||||
def teamIdToName(id: String): String = api teamName id getOrElse id
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ trait UserHelper { self: I18nHelper with StringHelper with NumberHelper =>
|
|||
|
||||
def miniViewSortedPerfTypes(u: User): List[PerfType] =
|
||||
best4Of(u, List(PerfType.Bullet, PerfType.Blitz, PerfType.Classical, PerfType.Correspondence)) :::
|
||||
best4Of(u, List(PerfType.Chess960, PerfType.KingOfTheHill, PerfType.ThreeCheck, PerfType.Antichess, PerfType.Atomic, PerfType.Horde, PerfType.RacingKings, PerfType.Crazyhouse))
|
||||
best4Of(u, List(PerfType.Crazyhouse, PerfType.Chess960, PerfType.KingOfTheHill, PerfType.ThreeCheck, PerfType.Antichess, PerfType.Atomic, PerfType.Horde, PerfType.RacingKings))
|
||||
|
||||
def showPerfRating(rating: Int, name: String, nb: Int, provisional: Boolean, icon: Char, klass: String)(implicit ctx: Context) = Html {
|
||||
val title = s"$name rating over ${nb.localize} games"
|
||||
|
@ -67,7 +67,7 @@ trait UserHelper { self: I18nHelper with StringHelper with NumberHelper =>
|
|||
|
||||
def showRatingDiff(diff: Int) = Html {
|
||||
diff match {
|
||||
case 0 => """<span class="rp null">+0</span>"""
|
||||
case 0 => """<span class="rp null">±0</span>"""
|
||||
case d if d > 0 => s"""<span class="rp up">+$d</span>"""
|
||||
case d => s"""<span class="rp down">$d</span>"""
|
||||
}
|
||||
|
@ -192,11 +192,12 @@ trait UserHelper { self: I18nHelper with StringHelper with NumberHelper =>
|
|||
userId: String,
|
||||
rating: Option[Int],
|
||||
cssClass: Option[String] = None,
|
||||
withPowerTip: Boolean = true,
|
||||
withTitle: Boolean = false,
|
||||
withOnline: Boolean = true) = {
|
||||
val user = lightUser(userId)
|
||||
val name = user.fold(userId)(_.name)
|
||||
val klass = userClass(userId, cssClass, withOnline)
|
||||
val klass = userClass(userId, cssClass, withOnline, withPowerTip)
|
||||
val href = userHref(name)
|
||||
val content = rating.fold(name)(e => s"$name ($e)")
|
||||
val titleS = titleTag(user.flatMap(_.title) ifTrue withTitle)
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
case false => {<span class="is-red" data-icon="L"></span>}
|
||||
}
|
||||
</h1>
|
||||
@form("email").value.map { email =>
|
||||
@form("email").value.filter(_.nonEmpty).map { email =>
|
||||
<p>You have already registered the email: @email</p>
|
||||
}.getOrElse {
|
||||
<p>@trans.emailIsOptional()</p>
|
||||
|
|
|
@ -178,7 +178,7 @@ withLangAnnotations: Boolean = true)(body: Html)(implicit ctx: Context)
|
|||
<div class="content list"></div>
|
||||
<div class="nobody">
|
||||
<span>@trans.noFriendsOnline()</span>
|
||||
<a class="find button" href="@routes.Relation.suggest(me.username)">
|
||||
<a class="find button" href="@routes.User.opponents(me.username)">
|
||||
<span class="is3 text" data-icon="h">@trans.findFriends()</span>
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
@(u: User, users: List[lila.relation.Related])(implicit ctx: Context)
|
||||
@(u: User, pag: Paginator[lila.relation.Related])(implicit ctx: Context)
|
||||
|
||||
@user.layout(title = u.username + " - " + trans.blocks(users.size)) {
|
||||
@user.layout(title = u.username + " - " + trans.blocks(pag.nbResults)) {
|
||||
<div class="content_box no_padding">
|
||||
<h1>
|
||||
@userLink(u, withOnline = false)
|
||||
@trans.blocks(users.size)
|
||||
@trans.blocks(pag.nbResults)
|
||||
</h1>
|
||||
@user.simpleTable(users)
|
||||
@user.simpleTable(pag, routes.Relation.blocks())
|
||||
</div>
|
||||
}
|
||||
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
@(u: User, users: List[lila.relation.Related], following: Int)(implicit ctx: Context)
|
||||
@(u: User, pag: Paginator[lila.relation.Related], nbFollowing: Int)(implicit ctx: Context)
|
||||
|
||||
@user.layout(title = u.username + " - " + trans.nbFollowers(users.size)) {
|
||||
@user.layout(title = u.username + " - " + trans.nbFollowers(pag.nbResults)) {
|
||||
<div class="content_box no_padding">
|
||||
<h1>
|
||||
@userLink(u, withOnline = false)
|
||||
@trans.nbFollowers(users.size)
|
||||
@trans.nbFollowers(pag.nbResults)
|
||||
&
|
||||
<a href="@routes.Relation.following(u.username)">@trans.nbFollowing(following)</a>
|
||||
<a href="@routes.Relation.following(u.username)">@trans.nbFollowing(nbFollowing)</a>
|
||||
</h1>
|
||||
@user.simpleTable(users)
|
||||
@user.simpleTable(pag, routes.Relation.followers(u.username))
|
||||
</div>
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
@(u: User, users: List[lila.relation.Related], followers: Int)(implicit ctx: Context)
|
||||
@(u: User, pag: Paginator[lila.relation.Related], nbFollowers: Int)(implicit ctx: Context)
|
||||
|
||||
@user.layout(title = u.username + " - " + trans.nbFollowing(users.size)) {
|
||||
@user.layout(title = u.username + " - " + trans.nbFollowing(pag.nbResults)) {
|
||||
<div class="content_box no_padding">
|
||||
<h1>
|
||||
@userLink(u, withOnline = false)
|
||||
@trans.nbFollowing(users.size)
|
||||
@trans.nbFollowing(pag.nbResults)
|
||||
&
|
||||
<a href="@routes.Relation.followers(u.username)">@trans.nbFollowers(followers)</a>
|
||||
<a href="@routes.Relation.followers(u.username)">@trans.nbFollowers(nbFollowers)</a>
|
||||
</h1>
|
||||
@user.simpleTable(users)
|
||||
@user.simpleTable(pag, routes.Relation.following(u.username))
|
||||
</div>
|
||||
}
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
@(u: User, sugs: List[lila.relation.Related])(implicit ctx: Context)
|
||||
|
||||
@title = @{ "%s - %s".format(u.username, trans.findFriends()) }
|
||||
|
||||
@user.layout(title = title) {
|
||||
<div class="content_box no_padding">
|
||||
<h1>@userLink(u, withOnline = false) @trans.findFriends()</h1>
|
||||
@user.relatedTable(u, sugs)
|
||||
</div>
|
||||
}
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
<span class="rank">@rank</span>
|
||||
}
|
||||
}
|
||||
@userInfosLink(player.userId, none, withOnline = false)
|
||||
@userInfosLink(player.userId, none, withOnline = false, withPowerTip = false)
|
||||
</td>
|
||||
<td class="total">
|
||||
<strong@if(player.fire) { class="is-gold" data-icon="Q" }>@player.score</strong>
|
||||
|
|
|
@ -68,7 +68,7 @@ description = describeUser(u)).some) {
|
|||
<a class="button hint--bottom-left" href="@routes.Account.profile" data-hint="@trans.editProfile()">
|
||||
<span data-icon="%"></span>
|
||||
</a>
|
||||
<a class="button hint--bottom-left" href="@routes.Relation.blocks" data-hint="@trans.listBlockedPlayers()">
|
||||
<a class="button hint--bottom-left" href="@routes.Relation.blocks()" data-hint="@trans.listBlockedPlayers()">
|
||||
<span data-icon="k"></span>
|
||||
</a>
|
||||
}
|
||||
|
@ -176,7 +176,7 @@ description = describeUser(u)).some) {
|
|||
<p>@trans.tpTimeSpentOnTV(showPeriod(tvPeriod))</p>
|
||||
}
|
||||
<div class="teams">
|
||||
@teamIds(u.id).sortBy(t => !myTeam(t)).map { teamId =>
|
||||
@teamIds(u.id).toList.sortBy(t => !myTeam(t)).map { teamId =>
|
||||
@teamLink(teamId, myTeam(teamId).option("mine"))
|
||||
}
|
||||
</div>
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
@(users: List[lila.relation.Related])(implicit ctx: Context)
|
||||
@(pager: Paginator[lila.relation.Related], call: Call)(implicit ctx: Context)
|
||||
|
||||
<table class="slist">
|
||||
@if(users.size > 0) {
|
||||
@if(pager.nbResults > 0) {
|
||||
<tbody class="infinitescroll">
|
||||
@users.map { r =>
|
||||
<tr>
|
||||
@pager.nextPage.map { np =>
|
||||
<tr><th class="pager none">
|
||||
<a href="@call.url?page=@np">Next</a>
|
||||
</th></tr>
|
||||
}
|
||||
@pager.currentPageResults.map { r =>
|
||||
<tr class="paginated_element">
|
||||
<td>@userLink(r.user)</td>
|
||||
<td>@showBestPerf(r.user)</td>
|
||||
<td>@r.user.count.game @trans.games()</td>
|
||||
|
|
26
bin/mongodb/bench/exists.js
Normal file
26
bin/mongodb/bench/exists.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
var limit = 50 * 1000;
|
||||
var coll = db.relation;
|
||||
var query = {
|
||||
_id: 'thibault/legendary22bcloud'
|
||||
};
|
||||
var expected = false;
|
||||
|
||||
function timer(name, f) {
|
||||
print('Start ' + name);
|
||||
var start = new Date().getTime();
|
||||
if (f() !== expected) print('FAILS');
|
||||
else {
|
||||
for (var i = 0; i < limit; i++) f();
|
||||
print(name + ': ' + (new Date().getTime() - start));
|
||||
}
|
||||
}
|
||||
|
||||
timer('count', function() {
|
||||
return coll.count(query) === 1;
|
||||
});
|
||||
timer('find', function() {
|
||||
return coll.find(query).limit(1).length() === 1;
|
||||
});
|
||||
timer('findOne', function() {
|
||||
return coll.findOne(query) !== null;
|
||||
});
|
|
@ -168,11 +168,10 @@ build_lila() {
|
|||
cd -- "$LILA_DIR"
|
||||
git submodule update --init --recursive
|
||||
|
||||
./ui/build &
|
||||
./bin/install-stockfish &
|
||||
./bin/gen/geoip &
|
||||
./bin/build-deps.sh &
|
||||
wait
|
||||
./ui/build
|
||||
./bin/install-stockfish
|
||||
./bin/gen/geoip
|
||||
./bin/build-deps.sh
|
||||
|
||||
sbt compile
|
||||
}
|
||||
|
@ -194,7 +193,7 @@ main() {
|
|||
local ip_address
|
||||
ip_address=$(get_ip_address)
|
||||
|
||||
info 'Lila is all set up! Add this entry entry to your hosts file on your'
|
||||
info 'Lila is all set up! Add this entry to your hosts file on your'
|
||||
info 'host machine (not the virtual machine, or else I would have done it'
|
||||
info 'for you):'
|
||||
info
|
||||
|
|
|
@ -9,7 +9,7 @@ net {
|
|||
ip = "5.196.91.160"
|
||||
asset {
|
||||
domain = ${net.domain}
|
||||
version = 818
|
||||
version = 819
|
||||
}
|
||||
}
|
||||
play {
|
||||
|
|
|
@ -270,7 +270,7 @@ progressToday=Fortschritt heute
|
|||
progressThisWeek=Fortschritt diese Woche
|
||||
progressThisMonth=Fortschritt diesen Monat
|
||||
leaderboardThisWeek=Führende Spieler diese Woche
|
||||
leaderboardThisMonth=Führende Spieler dieser Monat
|
||||
leaderboardThisMonth=Führende Spieler diesen Monat
|
||||
activeToday=Heute aktiv
|
||||
activePlayers=Aktive Spieler
|
||||
bewareTheGameIsRatedButHasNoClock=Achtung! Das Spiel wird zwar gewertet, aber ohne Uhr gespielt!
|
||||
|
@ -293,8 +293,8 @@ butYouCanKeepTrying=Aber Du kannst trotzdem weitermachen.
|
|||
victory=Geschafft!
|
||||
giveUp=Aufgeben
|
||||
puzzleSolvedInXSeconds=Rätsel in %s Sekunden gelöst.
|
||||
wasThisPuzzleAnyGood=Wie fandest du dieses Rätsel?
|
||||
pleaseVotePuzzle=Mache Lichess besser, indem du abstimmst.
|
||||
wasThisPuzzleAnyGood=Wie fandest Du dieses Rätsel?
|
||||
pleaseVotePuzzle=Mache Lichess besser, indem Du abstimmst.
|
||||
thankYou=Vielen Dank!
|
||||
ratingX=Schwierigkeitsgrad: %s
|
||||
playedXTimes=%s mal gespielt
|
||||
|
@ -343,9 +343,9 @@ cheat=Betrug
|
|||
insult=Beleidigung
|
||||
troll=Troll
|
||||
other=Anderes
|
||||
reportDescriptionHelp=Füge den Link zum Spiel ein und erkläre die Auffälligkeiten bezüglich des Spielerverhaltens. Bitte schreibe nicht einfach nur „dieser Spieler betrügt“, sondern begründe auch, wie du zu diesem Schluss kommst. Dein Bericht wird schneller bearbeitet, wenn er in englischer Sprache verfasst ist.
|
||||
reportDescriptionHelp=Füge den Link zum Spiel ein und erkläre die Auffälligkeiten bezüglich des Spielerverhaltens. Bitte schreibe nicht einfach nur „dieser Spieler betrügt“, sondern begründe auch, wie Du zu diesem Schluss kommst. Dein Bericht wird schneller bearbeitet, wenn er in englischer Sprache verfasst ist.
|
||||
by=von %s
|
||||
thisTopicIsNowClosed=Das Thema ist jetzt geschlossen.
|
||||
thisTopicIsNowClosed=Dieses Thema ist jetzt geschlossen.
|
||||
theming=Design
|
||||
donate=Spenden
|
||||
blog=Blog
|
||||
|
@ -358,10 +358,10 @@ materialDifference=Materialunterschiede
|
|||
closeAccount=Mitgliedschaft beenden
|
||||
closeYourAccount=Deine Mitgliedschaft beenden
|
||||
changedMindDoNotCloseAccount=Habe meine Meinung geändert, die Mitgliedschaft doch nicht beenden
|
||||
closeAccountExplanation=Möchten Sie die Mitgliedschaft wirklich beenden? Diese Entscheidung ist endgültig. Ein Login ist danach nicht mehr möglich und die Profilseite nicht mehr verfügbar.
|
||||
closeAccountExplanation=Möchtest Du die Mitgliedschaft wirklich beenden? Diese Entscheidung ist endgültig. Ein Login ist danach nicht mehr möglich und die Profilseite nicht mehr verfügbar.
|
||||
thisAccountIsClosed=Dieses Mitgliedskonto ist geschlossen.
|
||||
invalidUsernameOrPassword=Ungültiger Benutzername oder Passwort
|
||||
emailMeALink=Sende mir einen Link per E-Mail
|
||||
emailMeALink=Schicke mir einen Link per E-Mail
|
||||
currentPassword=Derzeitiges Passwort
|
||||
newPassword=Neues Passwort
|
||||
newPasswordAgain=Neues Passwort (wiederholen)
|
||||
|
@ -412,7 +412,7 @@ allInformationIsPublicAndOptional=Alle Informationen sind öffentlich und freiwi
|
|||
yourCityRegionOrDepartment=Deine Stadt, Region, Kanton oder Bundesland.
|
||||
biographyDescription=Über dich, was du am Schach magst, Lieblingseröffnungen, Spiele, Spieler…
|
||||
maximumNbCharacters=Maximal: %s Zeichen.
|
||||
blocks=%s gesperrt
|
||||
blocks=%s gesperrte Spieler
|
||||
listBlockedPlayers=Liste der gesperrten Spieler
|
||||
human=Mensch
|
||||
computer=Computer
|
||||
|
@ -454,7 +454,7 @@ blackCastlingKingside=Schwarz O-O
|
|||
blackCastlingQueenside=Schwarz O-O-O
|
||||
nbForumPosts=%s Forumbeiträge
|
||||
tpTimeSpentPlaying=Gesamte Spielzeit: %s
|
||||
watchGames=Spiele ansehen
|
||||
watchGames=Partien ansehen
|
||||
tpTimeSpentOnTV=Zeit im TV: %s
|
||||
watch=Zuschauen
|
||||
internationalEvents=Internationale Events
|
||||
|
@ -478,10 +478,10 @@ aboutSimulRealLife=Das Konzept ähnelt dem bei echten Simultanveranstaltungen, w
|
|||
aboutSimulRules=Beim Start des Simultan beginnt der Alleinspieler mit Weiß und spielt so lange mit wechselnden Gegnern, bis alle Partien beendet sind.
|
||||
aboutSimulSettings=Simultane sind immer ungewertet. Revanchen, Zugrücknahme und zusätzliche Zeit sind deaktiviert.
|
||||
create=Erstellen
|
||||
whenCreateSimul=Wenn Sie ein Simultan erzeugen, spielen Sie mit mehreren Gegnern gleichzeitig.
|
||||
whenCreateSimul=Wenn Du ein Simultan erzeugst, spielst Du mit mehreren Gegnern gleichzeitig.
|
||||
simulVariantsHint=Wurden mehrere Varianten gewählt, kann jeder Spieler eine Variante wählen.
|
||||
simulClockHint=Fischer Bedenkzeit. Je mehr Gegner Sie haben, desto mehr Zeit werden Sie benötigen.
|
||||
simulAddExtraTime=Je mehr zusätzliche Bedenkzeit Sie Ihrer eigenen Uhr gönnen, desto einfacher wird es für Sie die Lage zu meistern.
|
||||
simulClockHint=Fischer Bedenkzeit. Je mehr Gegner Du hast, desto mehr Zeit wirst Du benötigen.
|
||||
simulAddExtraTime=Du kannst zusätzliche Zeit hinzufügen, um mit dem Simultan zurechtzukommen.
|
||||
simulHostExtraTime=Extra Bedenkzeit für Alleinspieler
|
||||
lichessTournaments=Lichess Turnier
|
||||
tournamentFAQ=Arena Turnier FAQ
|
||||
|
@ -493,13 +493,13 @@ keyMoveBackwardOrForward=Zug zurück/vor
|
|||
keyGoToStartOrEnd=Zum Anfang/Ende
|
||||
keyShowOrHideComments=Zeige/verberge Kommentare
|
||||
keyEnterOrExitVariation=Variante wählen/verlassen
|
||||
keyYouCanDrawArrowsCirclesAndScrollToMove=Drücke Umschalt+Mausklick oder Rechtsklick um Kreise und Pfeile auf dem Brett zu zeichnen. Ebenso ist scrollen über dem Brett möglich, um die Züge zu durchlaufen.
|
||||
keyYouCanDrawArrowsCirclesAndScrollToMove=Drücke Umschalt+Mausklick oder Rechtsklick, um Kreise und Pfeile auf dem Brett zu zeichnen. Ebenso ist scrollen über dem Brett möglich, um die Züge zu durchlaufen.
|
||||
newTournament=Neues Turnier
|
||||
tournamentHomeTitle=Schachturnier mit verschiedenen Zeitkontrollen und Schachvarianten
|
||||
tournamentHomeDescription=Spielen Sie richtig schnelle Turniere! Treten Sie einem geplanten Turnier bei oder eröffnen Sie ein Neues. Bullet, Blitz, Classical, Chess960, King of the Hill, Threecheck und weitere Varianten/Optionen sind möglich für grenzenlosen Spaß.
|
||||
tournamentHomeDescription=Spielen Sie richtig schnelle Turniere! Treten Sie einem geplanten Turnier bei oder erstellen Sie ein Neues. Bullet, Blitz, Classical, Chess960, King of the Hill, Threecheck und weitere Varianten für grenzenlosen Spaß!
|
||||
tournamentNotFound=Turnier nicht gefunden
|
||||
tournamentDoesNotExist=Dieses Turnier existiert nicht.
|
||||
tournamentMayHaveBeenCanceled=Womöglich wurde es abgesagt, falls kein Spieler zu Turnierbeginn (mehr) registriert war.
|
||||
tournamentMayHaveBeenCanceled=Womöglich wurde es abgesagt, weil kein Spieler zu Turnierbeginn (mehr) registriert war.
|
||||
returnToTournamentsHomepage=Zurück zur Turnier Homepage
|
||||
monthlyPerfTypeRatingDistribution=Monatliche %s-Wertungsverteilung
|
||||
nbPerfTypePlayersThisMonth=%s %s Spieler diesen Monat.
|
||||
|
|
|
@ -107,7 +107,7 @@ declineInvitation=招待を断る
|
|||
cancel=キャンセル
|
||||
timeOut=時間切れ
|
||||
drawOfferSent=引き分けの申し込みを送信しました
|
||||
drawOfferDeclined=引き分けの申し込みは断られました
|
||||
drawOfferDeclined=引き分けの申し込みは拒否されました
|
||||
drawOfferAccepted=引き分けの申し込みに合意しました
|
||||
drawOfferCanceled=引き分けの申し込みをキャンセルしました
|
||||
whiteOffersDraw=白が引き分けを申し込んでいます
|
||||
|
@ -116,7 +116,7 @@ whiteDeclinesDraw=白が引き分けを拒否しました
|
|||
blackDeclinesDraw=黒が引き分けを拒否しました
|
||||
yourOpponentOffersADraw=相手が引き分けを申し込みました
|
||||
accept=承諾
|
||||
decline=断る
|
||||
decline=拒否
|
||||
playingRightNow=対局中
|
||||
finished=終了したトーナメント
|
||||
abortGame=対局を中止する
|
||||
|
@ -183,7 +183,7 @@ joinTeam=チームに参加
|
|||
quitTeam=チームを辞める
|
||||
anyoneCanJoin=誰でも参加可能
|
||||
aConfirmationIsRequiredToJoin=参加には確認が必要
|
||||
joiningPolicy=参加ポリシー
|
||||
joiningPolicy=参加規則
|
||||
teamLeader=チームリーダー
|
||||
teamBestPlayers=チームのベストプレイヤー
|
||||
teamRecentMembers=最新チームメンバー
|
||||
|
@ -295,7 +295,7 @@ giveUp=ギブアップ
|
|||
puzzleSolvedInXSeconds=パズルを解くのにかかった時間は%s秒です。
|
||||
wasThisPuzzleAnyGood=このパズルが気に入りましたか?
|
||||
pleaseVotePuzzle=改善のためご意見をお聞かせください。上矢印、下矢印をお使いください:
|
||||
thankYou=謝辞
|
||||
thankYou=ありが
|
||||
ratingX=レーティング: %s
|
||||
playedXTimes=挑戦回数%s回
|
||||
fromGameLink=ゲーム%sから
|
||||
|
|
|
@ -201,7 +201,7 @@ fromPosition=정해진 보드판에서 시작
|
|||
continueFromHere=여기서부터 시작
|
||||
importGame=게임 불러오기
|
||||
nbImportedGames=불러온 게임 %s개
|
||||
thisIsAChessCaptcha=자동 가입을 방지하기 위한 체스 퀴즈입니다.
|
||||
thisIsAChessCaptcha=자동 기입을 방지하기 위한 체스 퀴즈입니다.
|
||||
clickOnTheBoardToMakeYourMove=보드를 클릭해서 체스 퍼즐을 풀고 당신이 사람임을 알려주세요.
|
||||
notACheckmate=체크메이트가 아닙니다.
|
||||
colorPlaysCheckmateInOne=%s의 차례입니다. 한 수 만에 체크메이트하세요.
|
||||
|
@ -494,3 +494,5 @@ tournamentNotFound=토너먼트를 찾을 수 없습니다
|
|||
tournamentDoesNotExist=존재하지 않는 토너먼트 입니다
|
||||
tournamentMayHaveBeenCanceled=모든 플레이어가 퇴장하여 취소된 게임일 수 있습니다
|
||||
returnToTournamentsHomepage=토너먼트 홈으로 돌아가기
|
||||
yourPerfTypeRatingisRating=당신의 %s 레이팅은 %s입니다.
|
||||
youDoNotHaveAnEstablishedPerfTypeRating=아직 확정된 %s 레이팅을 갖지 않으셨습니다.
|
||||
|
|
|
@ -11,7 +11,7 @@ toggleTheChat=Sermonem aperi aut claude
|
|||
toggleSound=Sonum permitte aut nega
|
||||
chat=Sermo
|
||||
resign=Decede
|
||||
checkmate=Rex alligatus
|
||||
checkmate=Mattus
|
||||
stalemate=Rex impeditus
|
||||
white=Albus
|
||||
black=Niger
|
||||
|
@ -203,7 +203,7 @@ importGame=Lusionem imponere
|
|||
nbImportedGames=%s impositae lusiones
|
||||
thisIsAChessCaptcha=Hic ludus CAPTCHA est
|
||||
clickOnTheBoardToMakeYourMove=Age motum ut tuum esse humanum confirmem.
|
||||
notACheckmate=rex non captus est
|
||||
notACheckmate=Nulus mattus
|
||||
colorPlaysCheckmateInOne=%s agens; mattus moto uno
|
||||
retry=Adparare iterum
|
||||
reconnecting=Reconectens
|
||||
|
|
|
@ -5,22 +5,35 @@ gameOver=د لوبې پای
|
|||
waitingForOpponent=سیال ته تم شئ
|
||||
waiting=تم کېدل
|
||||
yourTurn=ستا نوبت
|
||||
level=پوړ
|
||||
aiNameLevelAiLevel=%s سويه %s
|
||||
level=سويه
|
||||
toggleTheChat=دبنډار څرنګتیاونج تڼۍ
|
||||
toggleSound=غږ څرنګتیاونج تڼۍ
|
||||
chat=بنډار
|
||||
resign=پرښودنه
|
||||
checkmate=ټولمات
|
||||
white=سپن
|
||||
stalemate=بندون
|
||||
white=سپين
|
||||
black=تور
|
||||
randomColor=توکلي
|
||||
createAGame=لوبه جوړه کړئ
|
||||
whiteIsVictorious=سپین سوبمن دی
|
||||
blackIsVictorious=تور سوبمن دی
|
||||
whiteIsVictorious=سپین فاتح دی
|
||||
blackIsVictorious=تور فاتح دی
|
||||
kingInTheCenter=باچا مينځ کښې
|
||||
threeChecks=درې کشت
|
||||
variantEnding=مختلف پای
|
||||
playWithTheSameOpponentAgain=همدا سیال سره بیا لوبه وکړئ
|
||||
newOpponent=نوی سیال
|
||||
playWithAnotherOpponent=د بل سیال سره لوبه وکړئ
|
||||
yourOpponentWantsToPlayANewGameWithYou=سیال مو نوې لوبه پیلول غواړي
|
||||
joinTheGame=يوځلی بيا
|
||||
whitePlays=سپین لوبه کوی
|
||||
blackPlays=تور لوبه کوی
|
||||
talkInChat=مرکځای
|
||||
theOtherPlayerHasLeftTheGameYouCanForceResignationOrWaitForHim=-سيال کېدې شي چی لوبه پرېښودلی وي -اقتدار لری چی لوبه ترلاسه كړی, يا مساوي كړی, او يا صبر وکړی
|
||||
makeYourOpponentResign=په زوره لوبه وګټی
|
||||
forceResignation=لوبه ترلاسه كړی
|
||||
forceDraw=مساوي كړی
|
||||
talkInChat=!لطفاٌ ښه وينه وکړی
|
||||
whiteCreatesTheGame=سپین د لوبې جوړونکی دی
|
||||
blackCreatesTheGame=تور د لوبې جوړونکی دی
|
||||
whiteResigned=سپین غاړه کېښود
|
||||
|
|
|
@ -5,7 +5,7 @@ gameOver=Konec igre
|
|||
waitingForOpponent=Čakam nasprotnika
|
||||
waiting=Čakam
|
||||
yourTurn=Ti si na potezi
|
||||
aiNameLevelAiLevel=Ime: %s, stopnja: %s
|
||||
aiNameLevelAiLevel=%s, stopnja: %s
|
||||
level=Stopnja
|
||||
toggleTheChat=Omogoči/onemogoči klepet
|
||||
toggleSound=Vključi/Izključi zvok
|
||||
|
@ -474,7 +474,7 @@ noSimulExplanation=Ta simultanka ne obstaja.
|
|||
returnToSimulHomepage=Vrni se na Simultanke
|
||||
aboutSimul=Simultanka je igra enega proti več igralcem hkrati.
|
||||
aboutSimulImage=Od 50 nasprotnikov, je Fischer zmagal 47 partij, 2 remiziral in 1 izgubil.
|
||||
aboutSimulRealLife=Koncept je vzet iz vsakdanjega življenja. V realnem svetu se igralec premika od deske do deske in odigra eno potezo.
|
||||
aboutSimulRealLife=Koncept je vzet iz vsakdanjega življenja. V realnem svetu se igralec premika od šahovnice do šahovnice in odigra eno potezo.
|
||||
aboutSimulRules=V simultanki ima igralec-gostitelj zmeraj bele figure. Simultanka se zaključi, ko so vse partije končane.
|
||||
aboutSimulSettings=Simultanke so zmeraj nerangirane. Revanše, popravki potez in dodajanje časa niso možni.
|
||||
create=Ustvari
|
||||
|
|
|
@ -502,3 +502,7 @@ tournamentDoesNotExist=Turnir ne postoji
|
|||
tournamentMayHaveBeenCanceled=Možda je otkazan, jer su igrači napustili turnir pre njegovog početka
|
||||
returnToTournamentsHomepage=Vratite se na "tournaments" početnu stranu
|
||||
monthlyPerfTypeRatingDistribution=Месечна %s дитрибуција рејтинга
|
||||
nbPerfTypePlayersThisMonth=%s %s играчи овај месец.
|
||||
yourPerfTypeRatingisRating=твоје %s рејтинг је %s.
|
||||
youAreBetterThanPercentOfPerfTypePlayers=ви сте бољи од %s од %s играча.
|
||||
youDoNotHaveAnEstablishedPerfTypeRating=Немате утврђен %s рејтинг.
|
||||
|
|
|
@ -37,10 +37,9 @@ POST /rel/follow/:userId controllers.Relation.follow(userId: Strin
|
|||
POST /rel/unfollow/:userId controllers.Relation.unfollow(userId: String)
|
||||
POST /rel/block/:userId controllers.Relation.block(userId: String)
|
||||
POST /rel/unblock/:userId controllers.Relation.unblock(userId: String)
|
||||
GET /@/:username/following controllers.Relation.following(username: String)
|
||||
GET /@/:username/followers controllers.Relation.followers(username: String)
|
||||
GET /@/:username/suggestions controllers.Relation.suggest(username: String)
|
||||
GET /rel/blocks controllers.Relation.blocks
|
||||
GET /@/:username/following controllers.Relation.following(username: String, page: Int ?= 1)
|
||||
GET /@/:username/followers controllers.Relation.followers(username: String, page: Int ?= 1)
|
||||
GET /rel/blocks controllers.Relation.blocks(page: Int ?= 1)
|
||||
|
||||
# Insight
|
||||
POST /insights/refresh/:username controllers.Insight.refresh(username: String)
|
||||
|
|
|
@ -54,7 +54,7 @@ final class Env(
|
|||
_.flatMap(_.getAs[BSONNumberLike]("version"))
|
||||
.fold(Net.AssetVersion)(_.toInt max Net.AssetVersion)
|
||||
},
|
||||
timeToLive = 1 minute,
|
||||
timeToLive = 30.seconds,
|
||||
default = Net.AssetVersion)
|
||||
def get = cache get true
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ private[api] final class UserApi(
|
|||
token: Option[String],
|
||||
nb: Option[Int],
|
||||
engine: Option[Boolean]): Fu[JsObject] = (team match {
|
||||
case Some(teamId) => lila.team.MemberRepo.userIdsByTeam(teamId) flatMap UserRepo.enabledByIds
|
||||
case Some(teamId) => lila.team.MemberRepo userIdsByTeam teamId flatMap UserRepo.enabledByIds
|
||||
case None => $find(pimpQB($query(
|
||||
UserRepo.enabledSelect ++ (engine ?? UserRepo.engineSelect)
|
||||
)) sort UserRepo.sortPerfDesc(lila.rating.PerfType.Standard.key), makeNb(nb, token))
|
||||
|
@ -45,12 +45,12 @@ private[api] final class UserApi(
|
|||
case None => fuccess(none)
|
||||
case Some(u) => GameRepo mostUrgentGame u zip
|
||||
(ctx.me.filter(u!=) ?? { me => crosstableApi.nbGames(me.id, u.id) }) zip
|
||||
relationApi.nbFollowing(u.id) zip
|
||||
relationApi.nbFollowers(u.id) zip
|
||||
relationApi.countFollowing(u.id) zip
|
||||
relationApi.countFollowers(u.id) zip
|
||||
ctx.isAuth.?? { prefApi followable u.id } zip
|
||||
ctx.userId.?? { relationApi.relation(_, u.id) } zip
|
||||
ctx.userId.?? { relationApi.relation(u.id, _) } map {
|
||||
case ((((((gameOption, nbGamesWithMe), following), followers), followable), relation), revRelation) =>
|
||||
ctx.userId.?? { relationApi.fetchRelation(_, u.id) } zip
|
||||
ctx.userId.?? { relationApi.fetchFollows(u.id, _) } map {
|
||||
case ((((((gameOption, nbGamesWithMe), following), followers), followable), relation), isFollowed) =>
|
||||
jsonView(u, extended = true) ++ {
|
||||
Json.obj(
|
||||
"url" -> makeUrl(s"@/$username"),
|
||||
|
@ -71,9 +71,9 @@ private[api] final class UserApi(
|
|||
"me" -> nbGamesWithMe)
|
||||
) ++ ctx.isAuth.??(Json.obj(
|
||||
"followable" -> followable,
|
||||
"following" -> relation.exists(true ==),
|
||||
"blocking" -> relation.exists(false ==),
|
||||
"followsYou" -> revRelation.exists(true ==)
|
||||
"following" -> relation.contains(true),
|
||||
"blocking" -> relation.contains(false),
|
||||
"followsYou" -> isFollowed
|
||||
))
|
||||
}.noNull
|
||||
} map (_.some)
|
||||
|
|
|
@ -2,6 +2,8 @@ package lila.bookmark
|
|||
|
||||
import org.joda.time.DateTime
|
||||
import play.api.libs.json._
|
||||
import reactivemongo.bson._
|
||||
|
||||
import lila.db.api._
|
||||
import lila.db.Implicits._
|
||||
import tube.bookmarkTube
|
||||
|
@ -9,6 +11,7 @@ import tube.bookmarkTube
|
|||
case class Bookmark(game: lila.game.Game, user: lila.user.User)
|
||||
|
||||
private[bookmark] object BookmarkRepo {
|
||||
|
||||
def toggle(gameId: String, userId: String): Fu[Boolean] =
|
||||
$count exists selectId(gameId, userId) flatMap { e =>
|
||||
e.fold(
|
||||
|
@ -17,8 +20,8 @@ private[bookmark] object BookmarkRepo {
|
|||
) inject !e
|
||||
}
|
||||
|
||||
def gameIdsByUserId(userId: String): Fu[List[String]] =
|
||||
$primitive(userIdQuery(userId), "g")(_.asOpt[String])
|
||||
def gameIdsByUserId(userId: String): Fu[Set[String]] =
|
||||
bookmarkTube.coll.distinct("g", BSONDocument("u" -> userId).some) map lila.db.BSON.asStringSet
|
||||
|
||||
def removeByGameId(gameId: String): Funit =
|
||||
$remove(Json.obj("g" -> gameId))
|
||||
|
|
|
@ -6,7 +6,7 @@ import lila.memo.MixedCache
|
|||
private[bookmark] final class Cached {
|
||||
|
||||
private[bookmark] val gameIdsCache = MixedCache[String, Set[String]](
|
||||
(userId: String) => BookmarkRepo gameIdsByUserId userId map (_.toSet),
|
||||
BookmarkRepo.gameIdsByUserId,
|
||||
timeToLive = 1 day,
|
||||
default = _ => Set.empty)
|
||||
|
||||
|
|
|
@ -15,6 +15,9 @@ object PlayApp {
|
|||
def startedSinceMinutes(minutes: Int) =
|
||||
startedAt.isBefore(DateTime.now minusMinutes minutes)
|
||||
|
||||
def startedSinceSeconds(seconds: Int) =
|
||||
startedAt.isBefore(DateTime.now minusSeconds seconds)
|
||||
|
||||
def loadConfig: Config = withApp(_.configuration.underlying)
|
||||
|
||||
def loadConfig(prefix: String): Config = loadConfig getConfig prefix
|
||||
|
|
|
@ -24,7 +24,7 @@ trait AdapterLike[A] {
|
|||
def nbResults = AdapterLike.this.nbResults
|
||||
|
||||
def slice(offset: Int, length: Int) =
|
||||
AdapterLike.this.slice(offset, length) map (_.toList) map2 f
|
||||
AdapterLike.this.slice(offset, length) map { _ map f }
|
||||
}
|
||||
|
||||
def mapFuture[B](f: A => Fu[B]): AdapterLike[B] = new AdapterLike[B] {
|
||||
|
|
|
@ -187,10 +187,21 @@ object BSON {
|
|||
case (k, v) => s"$k: ${debug(v)}"
|
||||
}).mkString("{", ", ", "}")
|
||||
|
||||
def asString(v: BSONValue): Option[String] = v match {
|
||||
case BSONString(s) => Some(s)
|
||||
case _ => None
|
||||
def asStrings(vs: List[BSONValue]): List[String] = {
|
||||
val b = new scala.collection.mutable.ListBuffer[String]
|
||||
vs foreach {
|
||||
case BSONString(s) => b += s
|
||||
case _ =>
|
||||
}
|
||||
b.toList
|
||||
}
|
||||
|
||||
def asStrings(vs: List[BSONValue]): List[String] = vs flatMap asString
|
||||
def asStringSet(vs: List[BSONValue]): Set[String] = {
|
||||
val b = Set.newBuilder[String]
|
||||
vs foreach {
|
||||
case BSONString(s) => b += s
|
||||
case _ =>
|
||||
}
|
||||
b.result
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,15 @@ final class DonationApi(
|
|||
userId => donatedByUser(userId).map(_ >= minAmount),
|
||||
maxCapacity = 5000)
|
||||
|
||||
// in $ cents
|
||||
private def donatedByUser(userId: String): Fu[Int] =
|
||||
coll.aggregate(
|
||||
Match(decentAmount ++ BSONDocument("userId" -> userId)), List(
|
||||
Group(BSONNull)("net" -> SumField("net"))
|
||||
)).map {
|
||||
~_.documents.headOption.flatMap { _.getAs[Int]("net") }
|
||||
}
|
||||
|
||||
private val decentAmount = BSONDocument("gross" -> BSONDocument("$gte" -> BSONInteger(minAmount)))
|
||||
|
||||
def list(nb: Int) = coll.find(decentAmount)
|
||||
|
@ -32,9 +41,7 @@ final class DonationApi(
|
|||
GroupField("userId")("total" -> SumField("net")),
|
||||
Sort(Descending("total")),
|
||||
Limit(nb))).map {
|
||||
_.documents.flatMap { obj =>
|
||||
obj.getAs[String]("_id")
|
||||
}
|
||||
_.documents.flatMap { _.getAs[String]("_id") }
|
||||
}
|
||||
|
||||
def isDonor(userId: String) =
|
||||
|
@ -53,15 +60,6 @@ final class DonationApi(
|
|||
progress = prog.percent), 'donation)
|
||||
}
|
||||
|
||||
// in $ cents
|
||||
def donatedByUser(userId: String): Fu[Int] =
|
||||
coll.find(
|
||||
decentAmount ++ BSONDocument("userId" -> userId),
|
||||
BSONDocument("net" -> true, "_id" -> false)
|
||||
).cursor[BSONDocument]().collect[List]() map2 { (obj: BSONDocument) =>
|
||||
~obj.getAs[Int]("net")
|
||||
} map (_.sum)
|
||||
|
||||
def progress: Fu[Progress] = {
|
||||
val from = DateTime.now withDayOfMonth 1 withHourOfDay 0 withMinuteOfHour 0 withSecondOfMinute 0
|
||||
val to = from plusMonths 1
|
||||
|
|
|
@ -9,7 +9,7 @@ import tube._
|
|||
|
||||
private[forum] final class CategApi(env: Env) {
|
||||
|
||||
def list(teams: List[String], troll: Boolean): Fu[List[CategView]] = for {
|
||||
def list(teams: Set[String], troll: Boolean): Fu[List[CategView]] = for {
|
||||
categs ← CategRepo withTeams teams
|
||||
views ← (categs map { categ =>
|
||||
env.postApi get (categ lastPostId troll) map { topicPost =>
|
||||
|
|
|
@ -10,7 +10,7 @@ object CategRepo {
|
|||
|
||||
def bySlug(slug: String) = $find byId slug
|
||||
|
||||
def withTeams(teams: List[String]): Fu[List[Categ]] =
|
||||
def withTeams(teams: Set[String]): Fu[List[Categ]] =
|
||||
$find($query($or(Seq(
|
||||
Json.obj("team" -> $exists(false)),
|
||||
Json.obj("team" -> $in(teams))
|
||||
|
@ -22,6 +22,6 @@ object CategRepo {
|
|||
_ sort $sort.desc("pos")
|
||||
)(_.asOpt[Int]) map (~_ + 1)
|
||||
|
||||
def nbPosts(id: String): Fu[Int] =
|
||||
def nbPosts(id: String): Fu[Int] =
|
||||
$primitive.one($select(id), "nbPosts")(_.asOpt[Int]) map (~_)
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ private[forum] final class Recent(
|
|||
nb: Int,
|
||||
publicCategIds: List[String]) {
|
||||
|
||||
private type GetTeams = String => List[String]
|
||||
private type GetTeams = String => Set[String]
|
||||
|
||||
def apply(user: Option[User], getTeams: GetTeams): Fu[List[MiniForumPost]] =
|
||||
userCacheKey(user, getTeams) |> { key => cache(key)(fetch(key)) }
|
||||
|
@ -32,7 +32,7 @@ private[forum] final class Recent(
|
|||
user.fold("en")(_.langs.mkString(",")) :: {
|
||||
(user.??(_.troll) ?? List("[troll]")) :::
|
||||
(user ?? MasterGranter(Permission.StaffForum)).fold(staffCategIds, publicCategIds) :::
|
||||
((user.map(_.id) ?? getTeams) map teamSlug)
|
||||
((user.map(_.id) ?? getTeams) map teamSlug).toList
|
||||
} mkString ";"
|
||||
|
||||
private lazy val staffCategIds = "staff" :: publicCategIds
|
||||
|
|
|
@ -6,12 +6,12 @@ object BestOpponents {
|
|||
|
||||
def apply(userId: String, limit: Int): Fu[List[(User, Int)]] =
|
||||
GameRepo.bestOpponents(userId, limit) flatMap { opponents =>
|
||||
UserRepo enabledByIds opponents.map(_._1) map { users =>
|
||||
(users map { user =>
|
||||
UserRepo enabledByIds opponents.map(_._1) map {
|
||||
_ flatMap { user =>
|
||||
opponents find (_._1 == user.id) map { opponent =>
|
||||
user -> opponent._2
|
||||
}
|
||||
}).flatten sortBy (-_._2)
|
||||
} sortBy (-_._2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,11 +77,11 @@ final class CrosstableApi(coll: Coll) {
|
|||
BSONDocument(Game.BSONFields.winnerId -> true)
|
||||
).sort(BSONDocument(Game.BSONFields.createdAt -> -1))
|
||||
.cursor[BSONDocument]().collect[List](maxGames).map {
|
||||
_.map { doc =>
|
||||
_.flatMap { doc =>
|
||||
doc.getAs[String](Game.BSONFields.id).map { id =>
|
||||
Result(id, doc.getAs[String](Game.BSONFields.winnerId))
|
||||
}
|
||||
}.flatten.reverse
|
||||
}.reverse
|
||||
}
|
||||
nbGames <- gameColl.count(selector.some)
|
||||
ctDraft = Crosstable(Crosstable.User(su1, 0), Crosstable.User(su2, 0), localResults, nbGames)
|
||||
|
|
|
@ -187,7 +187,10 @@ object GameRepo {
|
|||
$count.exists($select(id) ++ Query.analysed(true))
|
||||
|
||||
def filterAnalysed(ids: Seq[String]): Fu[Set[String]] =
|
||||
$primitive(($select byIds ids) ++ Query.analysed(true), "_id")(_.asOpt[String]) map (_.toSet)
|
||||
gameTube.coll.distinct("_id", BSONDocument(
|
||||
"_id" -> BSONDocument("$in" -> ids),
|
||||
F.analysed -> true
|
||||
).some) map lila.db.BSON.asStringSet
|
||||
|
||||
def incBookmarks(id: ID, value: Int) =
|
||||
$update($select(id), $incBson(F.bookmarks -> value))
|
||||
|
@ -255,8 +258,9 @@ object GameRepo {
|
|||
$insert bson bson
|
||||
}
|
||||
|
||||
def removeChallengesOf(userId: String) =
|
||||
$remove(Query.created ++ Query.friend ++ Query.user(userId))
|
||||
def removeRecentChallengesOf(userId: String) =
|
||||
$remove(Query.created ++ Query.friend ++ Query.user(userId) ++
|
||||
Query.createdSince(DateTime.now minusHours 1))
|
||||
|
||||
def setCheckAt(g: Game, at: DateTime) =
|
||||
$update($select(g.id), BSONDocument("$set" -> BSONDocument(F.checkAt -> at)))
|
||||
|
@ -313,7 +317,6 @@ object GameRepo {
|
|||
}).sequenceFu
|
||||
|
||||
def bestOpponents(userId: String, limit: Int): Fu[List[(String, Int)]] = {
|
||||
val col = gameTube.coll
|
||||
import reactivemongo.api.collections.bson.BSONBatchCommands.AggregationFramework, AggregationFramework.{
|
||||
Descending,
|
||||
GroupField,
|
||||
|
@ -323,8 +326,7 @@ object GameRepo {
|
|||
SumValue,
|
||||
Unwind
|
||||
}
|
||||
|
||||
col.aggregate(Match(BSONDocument(F.playerUids -> userId)), List(
|
||||
gameTube.coll.aggregate(Match(BSONDocument(F.playerUids -> userId)), List(
|
||||
Match(BSONDocument(F.playerUids -> BSONDocument("$size" -> 2))),
|
||||
Sort(Descending(F.createdAt)),
|
||||
Limit(1000), // only look in the last 1000 games
|
||||
|
@ -378,15 +380,6 @@ object GameRepo {
|
|||
)
|
||||
).one[BSONDocument] map { _ flatMap extractPgnMoves }
|
||||
|
||||
def associatePgn(ids: Seq[ID]): Fu[Map[String, PgnMoves]] =
|
||||
gameTube.coll.find($select byIds ids)
|
||||
.cursor[BSONDocument]()
|
||||
.collect[List]() map2 { (obj: BSONDocument) =>
|
||||
extractPgnMoves(obj) flatMap { moves =>
|
||||
obj.getAs[String]("_id") map (_ -> moves)
|
||||
}
|
||||
} map (_.flatten.toMap)
|
||||
|
||||
def lastGameBetween(u1: String, u2: String, since: DateTime): Fu[Option[Game]] = {
|
||||
$find.one(Json.obj(
|
||||
F.playerUids -> Json.obj("$all" -> List(u1, u2)),
|
||||
|
@ -410,7 +403,6 @@ object GameRepo {
|
|||
).one[BSONDocument] map { ~_.flatMap(_.getAs[List[String]](F.playerUids)) }
|
||||
|
||||
def activePlayersSince(since: DateTime, max: Int): Fu[List[UidNb]] = {
|
||||
val col = gameTube.coll
|
||||
import reactivemongo.api.collections.bson.BSONBatchCommands.AggregationFramework, AggregationFramework.{
|
||||
Descending,
|
||||
GroupField,
|
||||
|
@ -421,7 +413,7 @@ object GameRepo {
|
|||
Unwind
|
||||
}
|
||||
|
||||
col.aggregate(Match(BSONDocument(
|
||||
gameTube.coll.aggregate(Match(BSONDocument(
|
||||
F.createdAt -> BSONDocument("$gt" -> since),
|
||||
F.status -> BSONDocument("$gte" -> chess.Status.Mate.id),
|
||||
s"${F.playerUids}.0" -> BSONDocument("$exists" -> true)
|
||||
|
|
|
@ -56,7 +56,7 @@ object Env {
|
|||
lazy val current: Env = "insight" boot new Env(
|
||||
config = lila.common.PlayApp loadConfig "insight",
|
||||
getPref = lila.pref.Env.current.api.getPrefById,
|
||||
areFriends = lila.relation.Env.current.api.areFriends,
|
||||
areFriends = lila.relation.Env.current.api.fetchAreFriends,
|
||||
lightUser = lila.user.Env.current.lightUser,
|
||||
system = lila.common.PlayApp.system,
|
||||
lifecycle = lila.common.PlayApp.lifecycle)
|
||||
|
|
|
@ -29,9 +29,7 @@ private final class Indexer(storage: Storage, sequencer: ActorRef) {
|
|||
def update(game: Game, userId: String, previous: Entry): Funit =
|
||||
PovToEntry(game, userId, previous.provisional) flatMap {
|
||||
case Right(e) => storage update e.copy(number = previous.number)
|
||||
case Left(g) =>
|
||||
logwarn(s"[insight $userId] invalid game http://l.org/${g.id}")
|
||||
funit
|
||||
case _ => funit
|
||||
}
|
||||
|
||||
private def compute(user: User): Funit = storage.fetchLast(user.id) flatMap {
|
||||
|
@ -71,12 +69,7 @@ private final class Indexer(storage: Storage, sequencer: ActorRef) {
|
|||
PovToEntry(game, user.id, provisional = nb < 10).addFailureEffect { e =>
|
||||
println(e)
|
||||
e.printStackTrace
|
||||
} map {
|
||||
case Right(e) => e.some
|
||||
case Left(g) =>
|
||||
logwarn(s"[insight ${user.username}] invalid game http://lichess.org/${g.id}")
|
||||
none
|
||||
}
|
||||
} map (_.toOption)
|
||||
}
|
||||
val query = $query(gameQuery(user) ++ Json.obj(Game.BSONFields.createdAt -> $gte($date(from))))
|
||||
pimpQB(query)
|
||||
|
|
|
@ -45,7 +45,7 @@ private final class Storage(coll: Coll) {
|
|||
def find(id: String) = coll.find(selectId(id)).one[Entry]
|
||||
|
||||
def ecos(userId: String): Fu[Set[String]] =
|
||||
coll.distinct(F.eco, selectUserId(userId).some) map lila.db.BSON.asStrings map (_.toSet)
|
||||
coll.distinct(F.eco, selectUserId(userId).some) map lila.db.BSON.asStringSet
|
||||
|
||||
def nbByPerf(userId: String): Fu[Map[PerfType, Int]] = coll.aggregate(
|
||||
Match(BSONDocument(F.userId -> userId)),
|
||||
|
|
|
@ -92,7 +92,7 @@ object Env {
|
|||
db = lila.db.Env.current,
|
||||
hub = lila.hub.Env.current,
|
||||
onStart = lila.game.Env.current.onStart,
|
||||
blocking = lila.relation.Env.current.api.blocking,
|
||||
blocking = lila.relation.Env.current.api.fetchBlocking,
|
||||
playban = lila.playban.Env.current.api.currentBan _,
|
||||
system = lila.common.PlayApp.system,
|
||||
scheduler = lila.common.PlayApp.scheduler)
|
||||
|
|
|
@ -61,8 +61,8 @@ object Env {
|
|||
db = lila.db.Env.current,
|
||||
shutup = lila.hub.Env.current.actor.shutup,
|
||||
mongoCache = lila.memo.Env.current.mongoCache,
|
||||
blocks = lila.relation.Env.current.api.blocks,
|
||||
follows = lila.relation.Env.current.api.follows,
|
||||
blocks = lila.relation.Env.current.api.fetchBlocks,
|
||||
follows = lila.relation.Env.current.api.fetchFollows,
|
||||
getPref = lila.pref.Env.current.api.getPref,
|
||||
system = lila.common.PlayApp.system)
|
||||
}
|
||||
|
|
|
@ -5,9 +5,9 @@ import scala.concurrent.duration.Duration
|
|||
|
||||
import lila.db.BSON
|
||||
import lila.db.Types._
|
||||
import lila.hub.actorApi.SendTo
|
||||
import lila.memo.AsyncCache
|
||||
import lila.user.User
|
||||
import lila.hub.actorApi.SendTo
|
||||
import reactivemongo.bson._
|
||||
|
||||
final class PrefApi(
|
||||
|
@ -115,15 +115,13 @@ final class PrefApi(
|
|||
}
|
||||
|
||||
def unfollowableIds(userIds: List[String]): Fu[Set[String]] =
|
||||
coll.find(BSONDocument(
|
||||
coll.distinct("_id", BSONDocument(
|
||||
"_id" -> BSONDocument("$in" -> userIds),
|
||||
"follow" -> false
|
||||
), BSONDocument("_id" -> true)).cursor[BSONDocument]().collect[List]() map {
|
||||
_.flatMap(_.getAs[String]("_id")).toSet
|
||||
}
|
||||
).some) map lila.db.BSON.asStringSet
|
||||
|
||||
def followableIds(userIds: List[String]): Fu[Set[String]] =
|
||||
unfollowableIds(userIds) map (uns => userIds.toSet diff uns)
|
||||
unfollowableIds(userIds) map userIds.toSet.diff
|
||||
|
||||
def followables(userIds: List[String]): Fu[List[Boolean]] =
|
||||
followableIds(userIds) map { followables => userIds map followables.contains }
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
package lila.relation
|
||||
|
||||
import lila.memo.AsyncCache
|
||||
|
||||
private[relation] final class Cached {
|
||||
|
||||
private[relation] val followers = AsyncCache(RelationRepo.followers, maxCapacity = 2000)
|
||||
private[relation] val following = AsyncCache(RelationRepo.following, maxCapacity = 2000)
|
||||
private[relation] val blockers = AsyncCache(RelationRepo.blockers, maxCapacity = 2000)
|
||||
private[relation] val blocking = AsyncCache(RelationRepo.blocking, maxCapacity = 2000)
|
||||
private[relation] val relation = AsyncCache(findRelation, maxCapacity = 20000)
|
||||
|
||||
private def findRelation(pair: (String, String)): Fu[Option[Relation]] = pair match {
|
||||
case (u1, u2) => following(u1) flatMap { f =>
|
||||
f(u2).fold(fuccess(true.some), blocking(u1) map { b =>
|
||||
b(u2).fold(false.some, none)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private[relation] def invalidate(u1: ID, u2: ID): Funit =
|
||||
(List(followers, following, blockers, blocking) flatMap { cache =>
|
||||
List(u1, u2) map cache.remove
|
||||
}).sequenceFu.void >> relation.remove(u1, u2)
|
||||
}
|
|
@ -26,18 +26,15 @@ final class Env(
|
|||
import settings._
|
||||
|
||||
lazy val api = new RelationApi(
|
||||
cached = cached,
|
||||
coll = relationColl,
|
||||
actor = hub.actor.relation,
|
||||
bus = system.lilaBus,
|
||||
getOnlineUserIds = getOnlineUserIds,
|
||||
timeline = hub.actor.timeline,
|
||||
reporter = hub.actor.report,
|
||||
followable = followable,
|
||||
maxFollow = MaxFollow,
|
||||
maxBlock = MaxBlock)
|
||||
|
||||
private lazy val cached = new Cached
|
||||
|
||||
private[relation] val actor = system.actorOf(Props(new RelationActor(
|
||||
getOnlineUserIds = getOnlineUserIds,
|
||||
lightUser = lightUser,
|
||||
|
@ -47,7 +44,7 @@ final class Env(
|
|||
{
|
||||
import scala.concurrent.duration._
|
||||
|
||||
scheduler.once(10 seconds) {
|
||||
scheduler.once(15 seconds) {
|
||||
scheduler.message(ActorNotifyFreq) {
|
||||
actor -> actorApi.NotifyMovement
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package lila.relation
|
|||
import akka.actor.{ Actor, ActorSelection }
|
||||
import akka.pattern.{ ask, pipe }
|
||||
import play.api.libs.json.Json
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import actorApi._
|
||||
import lila.common.LightUser
|
||||
|
@ -44,13 +45,13 @@ private[relation] final class RelationActor(
|
|||
private def onlineIds: Set[ID] = onlines.keySet
|
||||
|
||||
private def onlineFriends(userId: String): Fu[OnlineFriends] =
|
||||
api following userId map { ids =>
|
||||
api fetchFollowing userId map { ids =>
|
||||
OnlineFriends(ids.flatMap(onlines.get).toList)
|
||||
}
|
||||
|
||||
private def notifyFollowers(users: List[LightUser], message: String) {
|
||||
users foreach { user =>
|
||||
api followers user.id map (_ filter onlines.contains) foreach { ids =>
|
||||
api fetchFollowers user.id map (_ filter onlines.contains) foreach { ids =>
|
||||
if (ids.nonEmpty) bus.publish(SendTos(ids.toSet, message, user.titleName), 'users)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,100 +5,142 @@ import scala.util.Success
|
|||
|
||||
import lila.db.api._
|
||||
import lila.db.Implicits._
|
||||
import lila.game.GameRepo
|
||||
import lila.hub.actorApi.relation.ReloadOnlineFriends
|
||||
import lila.db.paginator._
|
||||
import lila.hub.actorApi.timeline.{ Propagate, Follow => FollowUser }
|
||||
import lila.user.tube.userTube
|
||||
import lila.user.{ User => UserModel, UserRepo }
|
||||
import tube.relationTube
|
||||
|
||||
import BSONHandlers._
|
||||
import reactivemongo.api.collections.bson.BSONBatchCommands.AggregationFramework._
|
||||
import reactivemongo.bson._
|
||||
|
||||
final class RelationApi(
|
||||
cached: Cached,
|
||||
coll: Coll,
|
||||
actor: ActorSelection,
|
||||
bus: lila.common.Bus,
|
||||
getOnlineUserIds: () => Set[String],
|
||||
timeline: ActorSelection,
|
||||
reporter: ActorSelection,
|
||||
followable: String => Fu[Boolean],
|
||||
followable: ID => Fu[Boolean],
|
||||
maxFollow: Int,
|
||||
maxBlock: Int) {
|
||||
|
||||
def followers(userId: ID) = cached followers userId
|
||||
def following(userId: ID) = cached following userId
|
||||
def blockers(userId: ID) = cached blockers userId
|
||||
def blocking(userId: ID) = cached blocking userId
|
||||
import RelationRepo.makeId
|
||||
|
||||
def blocks(userId: ID) = blockers(userId) ⊹ blocking(userId)
|
||||
def fetchRelation(u1: ID, u2: ID): Fu[Option[Relation]] = coll.find(
|
||||
BSONDocument("u1" -> u1, "u2" -> u2),
|
||||
BSONDocument("r" -> true, "_id" -> false)
|
||||
).one[BSONDocument].map {
|
||||
_.flatMap(_.getAs[Boolean]("r"))
|
||||
}
|
||||
|
||||
def nbFollowers(userId: ID) = followers(userId) map (_.size)
|
||||
def nbFollowing(userId: ID) = following(userId) map (_.size)
|
||||
def nbBlocking(userId: ID) = blocking(userId) map (_.size)
|
||||
def nbBlockers(userId: ID) = blockers(userId) map (_.size)
|
||||
def fetchFollowing = RelationRepo following _
|
||||
|
||||
def friends(userId: ID) = following(userId) zip followers(userId) map {
|
||||
case (f1, f2) => f1 intersect f2
|
||||
def fetchFollowers = RelationRepo followers _
|
||||
|
||||
def fetchBlocking = RelationRepo blocking _
|
||||
|
||||
def fetchFriends(userId: ID) = coll.aggregate(Match(BSONDocument(
|
||||
"$or" -> BSONArray(BSONDocument("u1" -> userId), BSONDocument("u2" -> userId)),
|
||||
"r" -> Follow
|
||||
)), List(
|
||||
Group(BSONNull)(
|
||||
"u1" -> AddToSet("u1"),
|
||||
"u2" -> AddToSet("u2")),
|
||||
Project(BSONDocument(
|
||||
"_id" -> BSONDocument("$setIntersection" -> BSONArray("$u1", "$u2"))
|
||||
))
|
||||
)).map {
|
||||
~_.documents.headOption.flatMap(_.getAs[Set[String]]("_id")) - userId
|
||||
}
|
||||
|
||||
def areFriends(u1: ID, u2: ID) = friends(u1) map (_ contains u2)
|
||||
def fetchFollows(u1: ID, u2: ID) =
|
||||
coll.count(BSONDocument("_id" -> makeId(u1, u2), "r" -> Follow).some).map(0!=)
|
||||
|
||||
def follows(u1: ID, u2: ID) = following(u1) map (_ contains u2)
|
||||
def blocks(u1: ID, u2: ID) = blocking(u1) map (_ contains u2)
|
||||
def fetchBlocks(u1: ID, u2: ID) =
|
||||
coll.count(BSONDocument("_id" -> makeId(u1, u2), "r" -> Block).some).map(0!=)
|
||||
|
||||
def relation(u1: ID, u2: ID): Fu[Option[Relation]] = cached.relation(u1, u2)
|
||||
def fetchAreFriends(u1: ID, u2: ID) =
|
||||
fetchFollows(u1, u2) flatMap { _ ?? fetchFollows(u2, u1) }
|
||||
|
||||
def onlinePopularUsers(max: Int): Fu[List[UserModel]] =
|
||||
(getOnlineUserIds().toList map { id =>
|
||||
nbFollowers(id) map (id -> _)
|
||||
}).sequenceFu map (_ sortBy (-_._2) take max map (_._1)) flatMap UserRepo.byOrderedIds
|
||||
def countFollowing(userId: ID) =
|
||||
coll.count(BSONDocument("u1" -> userId, "r" -> Follow).some)
|
||||
|
||||
def countFollowers(userId: ID) =
|
||||
coll.count(BSONDocument("u2" -> userId, "r" -> Follow).some)
|
||||
|
||||
def countBlocking(userId: ID) =
|
||||
coll.count(BSONDocument("u1" -> userId, "r" -> Block).some)
|
||||
|
||||
def countBlockers(userId: ID) =
|
||||
coll.count(BSONDocument("u2" -> userId, "r" -> Block).some)
|
||||
|
||||
def followingPaginatorAdapter(userId: ID) = new BSONAdapter[Followed](
|
||||
collection = coll,
|
||||
selector = BSONDocument("u1" -> userId, "r" -> Follow),
|
||||
projection = BSONDocument("u2" -> true, "_id" -> false),
|
||||
sort = BSONDocument()).map(_.userId)
|
||||
|
||||
def followersPaginatorAdapter(userId: ID) = new BSONAdapter[Follower](
|
||||
collection = coll,
|
||||
selector = BSONDocument("u2" -> userId, "r" -> Follow),
|
||||
projection = BSONDocument("u1" -> true, "_id" -> false),
|
||||
sort = BSONDocument()).map(_.userId)
|
||||
|
||||
def blockingPaginatorAdapter(userId: ID) = new BSONAdapter[Blocked](
|
||||
collection = coll,
|
||||
selector = BSONDocument("u1" -> userId, "r" -> Block),
|
||||
projection = BSONDocument("u2" -> true, "_id" -> false),
|
||||
sort = BSONDocument()).map(_.userId)
|
||||
|
||||
def follow(u1: ID, u2: ID): Funit =
|
||||
if (u1 == u2) funit
|
||||
else followable(u2) zip relation(u1, u2) zip relation(u2, u1) flatMap {
|
||||
case ((false, _), _) => funit
|
||||
case ((_, Some(Follow)), _) => funit
|
||||
case ((_, _), Some(Block)) => funit
|
||||
case _ => RelationRepo.follow(u1, u2) >> limitFollow(u1) >>
|
||||
refresh(u1, u2) >>-
|
||||
(timeline ! Propagate(
|
||||
FollowUser(u1, u2)
|
||||
).toFriendsOf(u1).toUsers(List(u2)))
|
||||
else followable(u2) flatMap {
|
||||
case false => funit
|
||||
case true => fetchRelation(u1, u2) zip fetchRelation(u2, u1) flatMap {
|
||||
case (Some(Follow), _) => funit
|
||||
case (_, Some(Block)) => funit
|
||||
case _ => RelationRepo.follow(u1, u2) >> limitFollow(u1) >>-
|
||||
reloadOnlineFriends(u1, u2) >>-
|
||||
(timeline ! Propagate(FollowUser(u1, u2)).toFriendsOf(u1).toUsers(List(u2)))
|
||||
}
|
||||
}
|
||||
|
||||
private def limitFollow(u: ID) = nbFollowing(u) flatMap { nb =>
|
||||
private def limitFollow(u: ID) = countFollowing(u) flatMap { nb =>
|
||||
(nb >= maxFollow) ?? RelationRepo.drop(u, true, nb - maxFollow + 1)
|
||||
}
|
||||
|
||||
private def limitBlock(u: ID) = nbBlocking(u) flatMap { nb =>
|
||||
private def limitBlock(u: ID) = countBlocking(u) flatMap { nb =>
|
||||
(nb >= maxBlock) ?? RelationRepo.drop(u, false, nb - maxBlock + 1)
|
||||
}
|
||||
|
||||
def block(u1: ID, u2: ID): Funit =
|
||||
if (u1 == u2) funit
|
||||
else relation(u1, u2) flatMap {
|
||||
case Some(Block) => funit
|
||||
case _ => RelationRepo.block(u1, u2) >> limitBlock(u1) >> refresh(u1, u2) >>-
|
||||
bus.publish(lila.hub.actorApi.relation.Block(u1, u2), 'relation) >>-
|
||||
(nbBlockers(u2) zip nbFollowers(u2))
|
||||
else fetchBlocks(u1, u2) flatMap {
|
||||
case true => funit
|
||||
case _ => RelationRepo.block(u1, u2) >> limitBlock(u1) >>- reloadOnlineFriends(u1, u2) >>-
|
||||
bus.publish(lila.hub.actorApi.relation.Block(u1, u2), 'relation)
|
||||
}
|
||||
|
||||
def unfollow(u1: ID, u2: ID): Funit =
|
||||
if (u1 == u2) funit
|
||||
else relation(u1, u2) flatMap {
|
||||
case Some(Follow) => RelationRepo.unfollow(u1, u2) >> refresh(u1, u2)
|
||||
case _ => funit
|
||||
else fetchFollows(u1, u2) flatMap {
|
||||
case true => RelationRepo.unfollow(u1, u2) >>- reloadOnlineFriends(u1, u2)
|
||||
case _ => funit
|
||||
}
|
||||
|
||||
def unfollowAll(u1: ID): Funit = RelationRepo.unfollowAll(u1)
|
||||
|
||||
def unblock(u1: ID, u2: ID): Funit =
|
||||
if (u1 == u2) funit
|
||||
else relation(u1, u2) flatMap {
|
||||
case Some(Block) => RelationRepo.unblock(u1, u2) >> refresh(u1, u2) >>-
|
||||
else fetchBlocks(u1, u2) flatMap {
|
||||
case true => RelationRepo.unblock(u1, u2) >>- reloadOnlineFriends(u1, u2) >>-
|
||||
bus.publish(lila.hub.actorApi.relation.UnBlock(u1, u2), 'relation)
|
||||
case _ => funit
|
||||
}
|
||||
|
||||
private def refresh(u1: ID, u2: ID): Funit =
|
||||
cached.invalidate(u1, u2) >>-
|
||||
List(u1, u2).foreach(actor ! ReloadOnlineFriends(_))
|
||||
private def reloadOnlineFriends(u1: ID, u2: ID) {
|
||||
import lila.hub.actorApi.relation.ReloadOnlineFriends
|
||||
List(u1, u2).foreach(actor ! ReloadOnlineFriends(_))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package lila.relation
|
||||
|
||||
import play.api.libs.json._
|
||||
import reactivemongo.bson._
|
||||
|
||||
import lila.common.PimpedJson._
|
||||
import lila.db.api._
|
||||
|
@ -9,10 +10,7 @@ import tube.relationTube
|
|||
|
||||
private[relation] object RelationRepo {
|
||||
|
||||
def relation(id: ID): Fu[Option[Relation]] =
|
||||
$primitive.one($select byId id, "r")(_.asOpt[Boolean])
|
||||
|
||||
def relation(u1: ID, u2: ID): Fu[Option[Relation]] = relation(makeId(u1, u2))
|
||||
val coll = relationTube.coll
|
||||
|
||||
def followers(userId: ID) = relaters(userId, Follow)
|
||||
def following(userId: ID) = relating(userId, Follow)
|
||||
|
@ -21,14 +19,16 @@ private[relation] object RelationRepo {
|
|||
def blocking(userId: ID) = relating(userId, Block)
|
||||
|
||||
private def relaters(userId: ID, relation: Relation): Fu[Set[ID]] =
|
||||
$projection(Json.obj("u2" -> userId), Seq("u1", "r")) { obj =>
|
||||
obj str "u1" map { _ -> ~(obj boolean "r") }
|
||||
} map (_.filter(_._2 == relation).map(_._1).toSet)
|
||||
coll.distinct("u1", BSONDocument(
|
||||
"u2" -> userId,
|
||||
"r" -> relation
|
||||
).some) map lila.db.BSON.asStringSet
|
||||
|
||||
private def relating(userId: ID, relation: Relation): Fu[Set[ID]] =
|
||||
$projection(Json.obj("u1" -> userId), Seq("u2", "r")) { obj =>
|
||||
obj str "u2" map { _ -> ~(obj boolean "r") }
|
||||
} map (_.filter(_._2 == relation).map(_._1).toSet)
|
||||
coll.distinct("u2", BSONDocument(
|
||||
"u1" -> userId,
|
||||
"r" -> relation
|
||||
).some) map lila.db.BSON.asStringSet
|
||||
|
||||
def follow(u1: ID, u2: ID): Funit = save(u1, u2, Follow)
|
||||
def unfollow(u1: ID, u2: ID): Funit = remove(u1, u2)
|
||||
|
@ -55,5 +55,5 @@ private[relation] object RelationRepo {
|
|||
$remove(Json.obj("_id" -> $in(ids)))
|
||||
}
|
||||
|
||||
private def makeId(u1: String, u2: String) = u1 + "/" + u2
|
||||
def makeId(u1: String, u2: String) = s"$u1/$u2"
|
||||
}
|
||||
|
|
22
modules/relation/src/main/model.scala
Normal file
22
modules/relation/src/main/model.scala
Normal file
|
@ -0,0 +1,22 @@
|
|||
package lila.relation
|
||||
|
||||
import reactivemongo.bson._
|
||||
|
||||
case class Follower(u1: String) {
|
||||
def userId = u1
|
||||
}
|
||||
|
||||
case class Followed(u2: String) {
|
||||
def userId = u2
|
||||
}
|
||||
|
||||
case class Blocked(u2: String) {
|
||||
def userId = u2
|
||||
}
|
||||
|
||||
object BSONHandlers {
|
||||
|
||||
private[relation] implicit val followerBSONHandler = Macros.handler[Follower]
|
||||
private[relation] implicit val followedBSONHandler = Macros.handler[Followed]
|
||||
private[relation] implicit val blockedBSONHandler = Macros.handler[Blocked]
|
||||
}
|
|
@ -84,6 +84,8 @@ final class Firewall(
|
|||
def clear { cache.clear }
|
||||
def contains(ip: String) = apply map (_ contains strToIp(ip))
|
||||
def fetch: Fu[Set[IP]] =
|
||||
$primitive($select.all, "_id")(_.asOpt[String]) map { _.map(strToIp).toSet }
|
||||
firewallTube.coll.distinct("_id") map { res =>
|
||||
lila.db.BSON.asStringSet(res) map strToIp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ private[setup] final class Challenger(
|
|||
case msg@RemindChallenge(gameId, from, to) =>
|
||||
UserRepo.named(from) zip UserRepo.named(to) zip (renderer ? msg) flatMap {
|
||||
case ((Some(fromU), Some(toU)), html: Html) =>
|
||||
prefApi.getPref(toU) zip relationApi.follows(toU.id, fromU.id) flatMap {
|
||||
prefApi.getPref(toU) zip relationApi.fetchFollows(toU.id, fromU.id) flatMap {
|
||||
case (pref, follows) =>
|
||||
lila.pref.Pref.Challenge.block(fromU, toU, pref.challenge, follows,
|
||||
fromCheat = fromU.engine && !toU.engine) match {
|
||||
|
|
|
@ -50,6 +50,6 @@ object Env {
|
|||
config = lila.common.PlayApp loadConfig "shutup",
|
||||
reporter = lila.hub.Env.current.actor.report,
|
||||
system = lila.common.PlayApp.system,
|
||||
follows = lila.relation.Env.current.api.follows _,
|
||||
follows = lila.relation.Env.current.api.fetchFollows _,
|
||||
db = lila.db.Env.current)
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ final class ShutupApi(
|
|||
def getPublicLines(userId: String): Fu[List[String]] =
|
||||
coll.find(BSONDocument("_id" -> userId), BSONDocument("pub" -> 1))
|
||||
.one[BSONDocument].map {
|
||||
~_.map(~_.getAs[List[String]]("pub"))
|
||||
~_.flatMap(_.getAs[List[String]]("pub"))
|
||||
}
|
||||
|
||||
def publicForumMessage(userId: String, text: String) = record(userId, text, TextType.PublicForumMessage)
|
||||
|
|
|
@ -11,9 +11,10 @@ private[team] final class Cached {
|
|||
|
||||
def name(id: String) = nameCache get id
|
||||
|
||||
private[team] val teamIdsCache = MixedCache[String, List[String]](MemberRepo.teamIdsByUser,
|
||||
private[team] val teamIdsCache = MixedCache[String, Set[String]](
|
||||
MemberRepo.teamIdsByUser,
|
||||
timeToLive = 2 hours,
|
||||
default = _ => Nil)
|
||||
default = _ => Set.empty)
|
||||
|
||||
def teamIds(userId: String) = teamIdsCache get userId
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ package lila.team
|
|||
|
||||
import play.api.libs.json.Json
|
||||
import reactivemongo.api._
|
||||
import reactivemongo.bson._
|
||||
|
||||
import lila.db.api._
|
||||
import tube.memberTube
|
||||
|
@ -10,25 +11,25 @@ object MemberRepo {
|
|||
|
||||
type ID = String
|
||||
|
||||
def userIdsByTeam(teamId: ID): Fu[List[ID]] =
|
||||
$primitive(teamQuery(teamId), "user")(_.asOpt[ID])
|
||||
def userIdsByTeam(teamId: ID): Fu[Set[ID]] =
|
||||
memberTube.coll.distinct("user", BSONDocument("team" -> teamId).some) map lila.db.BSON.asStringSet
|
||||
|
||||
def teamIdsByUser(userId: ID): Fu[List[ID]] =
|
||||
$primitive(userQuery(userId), "team")(_.asOpt[ID])
|
||||
def teamIdsByUser(userId: ID): Fu[Set[ID]] =
|
||||
memberTube.coll.distinct("team", BSONDocument("user" -> userId).some) map lila.db.BSON.asStringSet
|
||||
|
||||
def removeByteam(teamId: ID): Funit =
|
||||
def removeByteam(teamId: ID): Funit =
|
||||
$remove(teamQuery(teamId))
|
||||
|
||||
def removeByUser(userId: ID): Funit =
|
||||
def removeByUser(userId: ID): Funit =
|
||||
$remove(userQuery(userId))
|
||||
|
||||
def exists(teamId: ID, userId: ID): Fu[Boolean] =
|
||||
def exists(teamId: ID, userId: ID): Fu[Boolean] =
|
||||
$count.exists(selectId(teamId, userId))
|
||||
|
||||
def add(teamId: String, userId: String): Funit =
|
||||
def add(teamId: String, userId: String): Funit =
|
||||
$insert(Member.make(team = teamId, user = userId))
|
||||
|
||||
def remove(teamId: String, userId: String): Funit =
|
||||
def remove(teamId: String, userId: String): Funit =
|
||||
$remove(selectId(teamId, userId))
|
||||
|
||||
def countByTeam(teamId: String): Fu[Int] =
|
||||
|
|
|
@ -4,6 +4,7 @@ import org.joda.time.{ DateTime, Period }
|
|||
import play.api.libs.json.Json
|
||||
import play.modules.reactivemongo.json.ImplicitBSONHandlers.JsObjectWriter
|
||||
import reactivemongo.api._
|
||||
import reactivemongo.bson._
|
||||
|
||||
import lila.db.api._
|
||||
import lila.user.User
|
||||
|
@ -17,7 +18,7 @@ object TeamRepo {
|
|||
$find.one($select(id) ++ Json.obj("createdBy" -> createdBy))
|
||||
|
||||
def teamIdsByCreator(userId: String): Fu[List[String]] =
|
||||
$primitive(Json.obj("createdBy" -> userId), "_id")(_.asOpt[String])
|
||||
teamTube.coll.distinct("_id", BSONDocument("createdBy" -> userId).some) map lila.db.BSON.asStrings
|
||||
|
||||
def name(id: String): Fu[Option[String]] =
|
||||
$primitive.one($select(id), "name")(_.asOpt[String])
|
||||
|
|
|
@ -55,8 +55,8 @@ object Env {
|
|||
config = lila.common.PlayApp loadConfig "timeline",
|
||||
db = lila.db.Env.current,
|
||||
hub = lila.hub.Env.current,
|
||||
getFriendIds = lila.relation.Env.current.api.friends _,
|
||||
getFollowerIds = lila.relation.Env.current.api.followers _,
|
||||
getFriendIds = lila.relation.Env.current.api.fetchFriends,
|
||||
getFollowerIds = lila.relation.Env.current.api.fetchFollowers,
|
||||
lobbySocket = lila.hub.Env.current.socket.lobby,
|
||||
renderer = lila.hub.Env.current.actor.renderer,
|
||||
system = lila.common.PlayApp.system)
|
||||
|
|
|
@ -41,8 +41,8 @@ private[timeline] final class Push(
|
|||
private def propagate(propagations: List[Propagation]): Fu[List[String]] =
|
||||
propagations.map {
|
||||
case Users(ids) => fuccess(ids)
|
||||
case Followers(id) => getFollowerIds(id) map (_.toList)
|
||||
case Friends(id) => getFriendIds(id) map (_.toList)
|
||||
case Followers(id) => getFollowerIds(id)
|
||||
case Friends(id) => getFriendIds(id)
|
||||
case StaffFriends(id) => getFriendIds(id) flatMap UserRepo.byIds map {
|
||||
_ filter Granter(_.StaffForum) map (_.id)
|
||||
}
|
||||
|
|
|
@ -22,11 +22,9 @@ 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.find(BSONDocument(
|
||||
coll.distinct("_id", BSONDocument(
|
||||
"_id" -> BSONDocument("$in" -> userIds.map { makeId(channel, _) })
|
||||
)).cursor[BSONDocument]().collect[List]() map { docs =>
|
||||
userIds diff docs.flatMap {
|
||||
_.getAs[String]("_id") map (_ takeWhile ('@' !=))
|
||||
}
|
||||
).some) map lila.db.BSON.asStrings map { unsubs =>
|
||||
userIds diff unsubs.map(_ takeWhile ('@' !=))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -91,9 +91,11 @@ object Schedule {
|
|||
|
||||
case (Daily | Eastern, HyperBullet | Bullet, _) => 60
|
||||
case (Daily | Eastern, SuperBlitz, _) => 90
|
||||
case (Daily | Eastern, Blitz, Standard) => 90
|
||||
case (Daily | Eastern, Blitz, Standard) => 120
|
||||
case (Daily | Eastern, Classical, _) => 150
|
||||
|
||||
case (Daily | Eastern, Blitz, Crazyhouse) => 120
|
||||
case (Daily | Eastern, Blitz, _) => 60 // variant daily is shorter
|
||||
case (Daily | Eastern, Classical, _) => 60 * 2
|
||||
|
||||
case (Weekly, HyperBullet | Bullet, _) => 60 * 2
|
||||
case (Weekly, SuperBlitz, _) => 60 * 2 + 30
|
||||
|
|
|
@ -57,9 +57,9 @@ trait UserRepo {
|
|||
y.??(yy => users.find(_.id == yy))
|
||||
}
|
||||
|
||||
def byOrderedIds(ids: Iterable[ID]): Fu[List[User]] = $find byOrderedIds ids
|
||||
def byOrderedIds(ids: Seq[ID]): Fu[List[User]] = $find byOrderedIds ids
|
||||
|
||||
def enabledByIds(ids: Seq[ID]): Fu[List[User]] = $find(enabledSelect ++ $select.byIds(ids))
|
||||
def enabledByIds(ids: Iterable[ID]): Fu[List[User]] = $find(enabledSelect ++ $select.byIds(ids))
|
||||
|
||||
def enabledById(id: ID): Fu[Option[User]] =
|
||||
$find.one(enabledSelect ++ $select.byId(id))
|
||||
|
@ -213,7 +213,8 @@ trait UserRepo {
|
|||
def nameExists(username: String): Fu[Boolean] = idExists(normalize(username))
|
||||
def idExists(id: String): Fu[Boolean] = $count exists id
|
||||
|
||||
def engineIds: Fu[Set[String]] = $primitive(Json.obj("engine" -> true), "_id")(_.asOpt[String]) map (_.toSet)
|
||||
def engineIds: Fu[Set[String]] =
|
||||
coll.distinct("_id", BSONDocument("engine" -> true).some) map lila.db.BSON.asStringSet
|
||||
|
||||
def usernamesLike(username: String, max: Int = 10): Fu[List[String]] = {
|
||||
import java.util.regex.Matcher.quoteReplacement
|
||||
|
@ -285,11 +286,12 @@ trait UserRepo {
|
|||
}
|
||||
|
||||
def recentlySeenNotKidIds(since: DateTime) =
|
||||
$primitive(enabledSelect ++ Json.obj(
|
||||
"seenAt" -> $gt($date(since)),
|
||||
"count.game" -> $gt(4),
|
||||
"kid" -> $ne(true)
|
||||
), "_id")(_.asOpt[String])
|
||||
coll.distinct("_id", BSONDocument(
|
||||
F.enabled -> true,
|
||||
"seenAt" -> BSONDocument("$gt" -> since),
|
||||
"count.game" -> BSONDocument("$gt" -> 9),
|
||||
"kid" -> BSONDocument("$ne" -> true)
|
||||
).some) map lila.db.BSON.asStrings
|
||||
|
||||
def setLang(id: ID, lang: String) = $update.field(id, "lang", lang)
|
||||
|
||||
|
|
|
@ -78,12 +78,7 @@ private[video] final class VideoApi(
|
|||
).void
|
||||
|
||||
def allIds: Fu[List[Video.ID]] =
|
||||
videoColl.find(
|
||||
BSONDocument(),
|
||||
BSONDocument("_id" -> true)
|
||||
).cursor[BSONDocument]().collect[List]() map { doc =>
|
||||
doc flatMap (_.getAs[String]("_id"))
|
||||
}
|
||||
videoColl.distinct("_id", none) map lila.db.BSON.asStrings
|
||||
|
||||
def popular(user: Option[User], page: Int): Fu[Paginator[VideoView]] = Paginator(
|
||||
adapter = new BSONAdapter[Video](
|
||||
|
@ -160,16 +155,12 @@ private[video] final class VideoApi(
|
|||
).some) map (0!=)
|
||||
|
||||
def seenVideoIds(user: User, videos: Seq[Video]): Fu[Set[Video.ID]] =
|
||||
viewColl.find(
|
||||
viewColl.distinct(View.BSONFields.videoId,
|
||||
BSONDocument(
|
||||
"_id" -> BSONDocument("$in" -> videos.map { v =>
|
||||
View.makeId(v.id, user.id)
|
||||
})
|
||||
),
|
||||
BSONDocument(View.BSONFields.videoId -> true, "_id" -> false)
|
||||
).cursor[BSONDocument]().collect[List]() map { docs =>
|
||||
docs.flatMap(_.getAs[String](View.BSONFields.videoId)).toSet
|
||||
}
|
||||
).some) map lila.db.BSON.asStringSet
|
||||
}
|
||||
|
||||
object tag {
|
||||
|
|
|
@ -54,7 +54,7 @@ function renderPlot(ctrl, hook) {
|
|||
intentPollInterval: 100,
|
||||
fadeInTime: 0,
|
||||
fadeOutTime: 0,
|
||||
placement: hook.rating > 2200 ? 'se' : 'ne',
|
||||
placement: hook.rating > 1800 ? 'se' : 'ne',
|
||||
mouseOnToPopup: true,
|
||||
closeDelay: 200,
|
||||
popupId: 'hook'
|
||||
|
|
|
@ -354,6 +354,7 @@ module.exports = function(opts) {
|
|||
this.setBerserk = function(color) {
|
||||
if (this.vm.goneBerserk[color]) return;
|
||||
this.vm.goneBerserk[color] = true;
|
||||
if (color !== this.data.player.color) $.sound.berserk();
|
||||
m.redraw();
|
||||
}.bind(this);
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ module.exports = function(opts) {
|
|||
steps: [{
|
||||
title: "Racing Kings",
|
||||
content: "This is a game of racing kings. " +
|
||||
'Would you like to check out <a target="_blank" href="http://lichess.org/racing-kings">the rules</a>?',
|
||||
'You might want to check out <a target="_blank" href="http://lichess.org/racing-kings">the rules</a>.',
|
||||
target: "div.game_infos .variant-link",
|
||||
placement: "bottom"
|
||||
}]
|
||||
|
@ -44,7 +44,7 @@ module.exports = function(opts) {
|
|||
steps: [{
|
||||
title: "Crazyhouse",
|
||||
content: "This is a game of crazyhouse. " +
|
||||
'Would you like to check out <a target="_blank" href="http://lichess.org/crazyhouse">the rules</a>?',
|
||||
'You might want to check out <a target="_blank" href="http://lichess.org/crazyhouse">the rules</a>.',
|
||||
target: "div.game_infos .variant-link",
|
||||
placement: "bottom"
|
||||
}]
|
||||
|
|
|
@ -3,7 +3,7 @@ var game = require('game').game;
|
|||
|
||||
function ratingDiff(player) {
|
||||
if (typeof player.ratingDiff === 'undefined') return null;
|
||||
if (player.ratingDiff === 0) return m('span.rp.null', 0);
|
||||
if (player.ratingDiff === 0) return m('span.rp.null', '±0');
|
||||
if (player.ratingDiff > 0) return m('span.rp.up', '+' + player.ratingDiff);
|
||||
if (player.ratingDiff < 0) return m('span.rp.down', player.ratingDiff);
|
||||
}
|
||||
|
|
|
@ -22,8 +22,7 @@ module.exports = function(ctrl) {
|
|||
tag: 'a',
|
||||
attrs: {
|
||||
key: p.id,
|
||||
href: '/' + p.id,
|
||||
class: 'glpt'
|
||||
href: '/' + p.id
|
||||
},
|
||||
children: [
|
||||
user(p, 0),
|
||||
|
|
|
@ -16,7 +16,7 @@ function result(win, stat) {
|
|||
|
||||
function playerTitle(player) {
|
||||
return m('h2', [
|
||||
player.withdraw ? m('span.text[data-icon=b]') : m('span.rank', player.rank + '. '),
|
||||
m('span.rank', player.rank + '. '),
|
||||
util.player(player)
|
||||
]);
|
||||
}
|
||||
|
@ -60,7 +60,12 @@ module.exports = function(ctrl) {
|
|||
return m('tr', {
|
||||
key: p.id,
|
||||
'data-href': '/' + p.id + '/' + p.color,
|
||||
class: 'glpt' + (res === '1' ? ' win' : (res === '0' ? ' loss' : ''))
|
||||
class: 'glpt' + (res === '1' ? ' win' : (res === '0' ? ' loss' : '')),
|
||||
config: function(el, isUpdate, ctx) {
|
||||
if (!isUpdate) ctx.onunload = function() {
|
||||
$.powerTip.destroy(el);
|
||||
};
|
||||
}
|
||||
}, [
|
||||
m('th', Math.max(nb.game, pairingsLen) - i),
|
||||
m('td', (p.op.title ? p.op.title + ' ' : '') + p.op.name),
|
||||
|
|
|
@ -71,6 +71,11 @@ module.exports = {
|
|||
var fullName = (p.title ? p.title + ' ' : '') + p.name;
|
||||
var attrs = {
|
||||
class: 'ulpt user_link' + (fullName.length > 15 ? ' long' : ''),
|
||||
config: function(el, isUpdate, ctx) {
|
||||
if (!isUpdate) ctx.onunload = function() {
|
||||
$.powerTip.destroy(el);
|
||||
};
|
||||
}
|
||||
};
|
||||
attrs[tag === 'a' ? 'href' : 'data-href'] = '/@/' + p.name;
|
||||
return {
|
||||
|
|
Loading…
Reference in a new issue