Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions app/controllers/Api.scala
Original file line number Diff line number Diff line change
Expand Up @@ -216,14 +216,6 @@ final class Api(
}
}

def tournamentsByOwner(name: UserStr, status: List[Int]) = Anon:
Found(meOrFetch(name).map(_.filterNot(_.is(UserId.lichess)))): user =>
val nb = getInt("nb") | Int.MaxValue
jsonDownload:
env.tournament.api
.byOwnerStream(user, status.flatMap(lila.core.tournament.Status.byId.get), MaxPerSecond(20), nb)
.mapAsync(1)(env.tournament.apiJsonView.fullJson)

def swissGames(id: SwissId) = AnonOrScoped(): ctx ?=>
Found(env.swiss.cache.swissCache.byId(id)): swiss =>
val config = GameApiV2.BySwissConfig(
Expand Down
18 changes: 17 additions & 1 deletion app/controllers/UserTournament.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package controllers

import lila.app.{ *, given }

final class UserTournament(env: Env) extends LilaController(env):
final class UserTournament(env: Env, apiC: => Api) extends LilaController(env):

def path(username: UserStr, path: String, page: Int) = Open:
Reasonable(page):
Expand Down Expand Up @@ -32,3 +32,19 @@ final class UserTournament(env: Env) extends LilaController(env):
Found(ctx.me): me =>
Redirect(routes.UserTournament.path(me.username, "upcoming"))
case _ => notFound

def apiTournamentsByOwner(name: UserStr, status: List[Int]) = Anon:
Found(meOrFetch(name).map(_.filterNot(_.is(UserId.lichess)))): user =>
val nb = getInt("nb") | Int.MaxValue
apiC.jsonDownload:
env.tournament.api
.byOwnerStream(user, status.flatMap(lila.core.tournament.Status.byId.get), MaxPerSecond(20), nb)
.mapAsync(1)(env.tournament.apiJsonView.fullJson)

def apiTournamentsByPlayer(name: UserStr) = Anon:
Found(meOrFetch(name).map(_.filterNot(_.is(UserId.lichess)))): user =>
val nb = getInt("nb") | Int.MaxValue
apiC.jsonDownload:
env.tournament.leaderboardApi
.byPlayerStream(user, MaxPerSecond(20), nb)
.map(env.tournament.apiJsonView.byPlayer)
3 changes: 2 additions & 1 deletion conf/routes
Original file line number Diff line number Diff line change
Expand Up @@ -671,7 +671,8 @@ GET /api/puzzle/dashboard/$days<\d+> controllers.Puzzle.apiDashboard(days: Int
GET /api/puzzle/$id<\w{5}> controllers.Puzzle.apiShow(id: PuzzleId)
GET /api/puzzle/batch/:angle controllers.Puzzle.apiBatchSelect(angle)
POST /api/puzzle/batch/:angle controllers.Puzzle.apiBatchSolve(angle)
GET /api/user/:user/tournament/created controllers.Api.tournamentsByOwner(user: UserStr, status: List[Int])
GET /api/user/:user/tournament/created controllers.UserTournament.apiTournamentsByOwner(user: UserStr, status: List[Int])
GET /api/user/:user/tournament/played controllers.UserTournament.apiTournamentsByPlayer(user: UserStr)
GET /api/user/:user controllers.Api.user(user: UserStr)
GET /api/user/:user/activity controllers.Api.activity(user: UserStr)
GET /api/user/:user/note controllers.User.apiReadNote(user: UserStr)
Expand Down
15 changes: 9 additions & 6 deletions modules/tournament/src/main/ApiJsonView.scala
Original file line number Diff line number Diff line change
Expand Up @@ -77,15 +77,18 @@ final class ApiJsonView(lightUserApi: lila.core.user.LightUserApi)(using Executo
)

def fullJson(tour: Tournament)(using Translate): Fu[JsObject] =
(tour.winnerId.so(lightUserApi.async)).map { winner =>
baseJson(tour).add("winner" -> winner.map(userJson))
tour.winnerId.so(lightUserApi.async).map { winner =>
baseJson(tour).add("winner" -> winner)
}

private def userJson(u: lila.core.LightUser) =
def byPlayer(e: LeaderboardApi.TourEntry)(using Translate): JsObject =
Json.obj(
"id" -> u.id,
"name" -> u.name,
"title" -> u.title
"tournament" -> baseJson(e.tour),
"player" -> Json.obj(
"games" -> e.entry.nbGames,
"score" -> e.entry.score,
"rank" -> e.entry.rank
)
)

private val perfPositions: Map[PerfKey, Int] = {
Expand Down
82 changes: 48 additions & 34 deletions modules/tournament/src/main/LeaderboardApi.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package lila.tournament

import akka.stream.scaladsl.*
import reactivemongo.api.bson.*
import reactivemongo.akkastream.cursorProducer
import scalalib.Maths
import scalalib.paginator.{ AdapterLike, Paginator }

Expand All @@ -10,15 +12,15 @@ import lila.db.dsl.{ *, given }
import lila.rating.PerfType

final class LeaderboardApi(
repo: LeaderboardRepo,
val repo: LeaderboardRepo,
tournamentRepo: TournamentRepo
)(using Executor)
)(using Executor, akka.stream.Materializer)
extends lila.core.tournament.leaderboard.Api:

import LeaderboardApi.*
import BSONHandlers.given

private val maxPerPage = MaxPerPage(15)
private val maxPerPage = MaxPerPage(20)

def recentByUser(user: User, page: Int) = paginator(user, page, sortBest = false)

Expand Down Expand Up @@ -58,19 +60,42 @@ final class LeaderboardApi(
}
.sortLike(lila.rating.PerfType.leaderboardable, _._1)

def getAndDeleteRecent(userId: UserId, since: Instant): Fu[List[TourId]] =
def getAndDeleteRecent(userId: UserId, since: Instant): Fu[List[TourId]] = for
entries <- repo.coll.list[Entry]($doc("u" -> userId, "d".$gt(since)))
_ <- entries.nonEmpty.so:
repo.coll.delete.one($inIds(entries.map(_.id))).void
yield entries.map(_.tourId)

def byPlayerStream(user: User, perSecond: MaxPerSecond, nb: Int): Source[TourEntry, ?] =
repo.coll
.list[Entry](
$doc(
"u" -> userId,
"d".$gt(since)
.aggregateWith[Bdoc](): fw =>
aggregateByPlayer(user, fw, fw.Descending("d"), nb, offset = 0).toList
.documentSource()
.mapConcat(readTourEntry)

private def aggregateByPlayer(
user: User,
framework: repo.coll.AggregationFramework.type,
sort: framework.SortOrder,
nb: Int,
offset: Int = 0
): NonEmptyList[framework.PipelineOperator] =
import framework.*
NonEmptyList.of(
Match($doc("u" -> user.id)),
Sort(sort),
Skip(offset),
Limit(nb),
PipelineOperator(
$lookup.simple(
from = tournamentRepo.coll,
as = "tour",
local = "t",
foreign = "_id"
)
)
.flatMap { entries =>
(entries.nonEmpty
.so(repo.coll.delete.one($inIds(entries.map(_.id))).void))
.inject(entries.map(_.tourId))
}
),
UnwindField("tour")
)

private def paginator(user: User, page: Int, sortBest: Boolean): Fu[Paginator[TourEntry]] =
Paginator(
Expand All @@ -83,28 +108,17 @@ final class LeaderboardApi(
repo.coll
.aggregateList(length, _.sec): framework =>
import framework.*
Match(selector) -> List(
Sort(if sortBest then Ascending("w") else Descending("d")),
Skip(offset),
Limit(length),
PipelineOperator(
$lookup.simple(
from = tournamentRepo.coll,
as = "tour",
local = "t",
foreign = "_id"
)
),
UnwindField("tour")
)
.map: docs =>
for
doc <- docs
entry <- doc.asOpt[Entry]
tour <- doc.getAsOpt[Tournament]("tour")
yield TourEntry(tour, entry)
val sort = if sortBest then framework.Ascending("w") else framework.Descending("d")
val pipe = aggregateByPlayer(user, framework, sort, length, offset)
pipe.head -> pipe.tail
.map(_.flatMap(readTourEntry))
)

private def readTourEntry(doc: Bdoc): Option[TourEntry] = for
entry <- doc.asOpt[Entry]
tour <- doc.getAsOpt[Tournament]("tour")
yield TourEntry(tour, entry)

object LeaderboardApi:

import lila.core.tournament.leaderboard.Ratio
Expand Down
1 change: 1 addition & 0 deletions modules/tournament/src/main/TournamentApi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ final class TournamentApi(
pairingSystem: arena.PairingSystem,
callbacks: TournamentApi.Callbacks,
socket: TournamentSocket,
leaderboard: LeaderboardApi,
roundApi: lila.core.round.RoundApi,
gameProxy: lila.core.game.GameProxy,
trophyApi: lila.core.user.TrophyApi,
Expand Down