lila/modules/api/src/main/GameApi.scala

269 lines
8.3 KiB
Scala

package lila.api
import chess.format.FEN
import org.joda.time.DateTime
import play.api.libs.json._
import reactivemongo.api.bson._
import reactivemongo.api.ReadPreference
import lila.analyse.{ JsonView => analysisJson, Analysis }
import lila.common.config._
import lila.common.Json._
import lila.common.paginator.{ Paginator, PaginatorJson }
import lila.db.dsl._
import lila.db.paginator.Adapter
import lila.game.BSONHandlers._
import lila.game.Game.{ BSONFields => G }
import lila.game.JsonView._
import lila.game.{ CrosstableApi, Game, PerfPicker }
import lila.user.User
final private[api] class GameApi(
net: NetConfig,
apiToken: Secret,
gameRepo: lila.game.GameRepo,
gameCache: lila.game.Cached,
analysisRepo: lila.analyse.AnalysisRepo,
crosstableApi: CrosstableApi
)(implicit ec: scala.concurrent.ExecutionContext) {
import GameApi.WithFlags
def byUser(
user: User,
rated: Option[Boolean],
playing: Option[Boolean],
analysed: Option[Boolean],
withFlags: WithFlags,
nb: MaxPerPage,
page: Int
): Fu[JsObject] =
Paginator(
adapter = new Adapter[Game](
collection = gameRepo.coll,
selector = {
if (~playing) lila.game.Query.nowPlaying(user.id)
else
$doc(
G.playerUids -> user.id,
G.status $gte chess.Status.Mate.id,
G.analysed -> analysed.map[BSONValue] {
case true => BSONBoolean(true)
case _ => $doc("$exists" -> false)
}
)
} ++ $doc(
G.rated -> rated.map[BSONValue] {
case true => BSONBoolean(true)
case _ => $doc("$exists" -> false)
}
),
projection = none,
sort = $doc(G.createdAt -> -1),
readPreference = ReadPreference.secondaryPreferred
).withNbResults(
if (~playing) gameCache.nbPlaying(user.id)
else
fuccess {
rated.fold(user.count.game) {
case true => user.count.rated
case _ => user.count.casual
}
}
),
currentPage = page,
maxPerPage = nb
) flatMap { pag =>
gamesJson(withFlags = withFlags)(pag.currentPageResults) map { games =>
PaginatorJson(pag withCurrentPageResults games)
}
}
def one(id: String, withFlags: WithFlags): Fu[Option[JsObject]] =
gameRepo game id flatMap {
_ ?? { g =>
gamesJson(withFlags)(List(g)) map (_.headOption)
}
}
def byUsersVs(
users: (User, User),
rated: Option[Boolean],
playing: Option[Boolean],
analysed: Option[Boolean],
withFlags: WithFlags,
nb: MaxPerPage,
page: Int
): Fu[JsObject] =
Paginator(
adapter = new Adapter[Game](
collection = gameRepo.coll,
selector = {
if (~playing) lila.game.Query.nowPlayingVs(users._1.id, users._2.id)
else
lila.game.Query.opponents(users._1, users._2) ++ $doc(
G.status $gte chess.Status.Mate.id,
G.analysed -> analysed.map[BSONValue] {
case true => BSONBoolean(true)
case _ => $doc("$exists" -> false)
}
)
} ++ $doc(
G.rated -> rated.map[BSONValue] {
case true => BSONBoolean(true)
case _ => $doc("$exists" -> false)
}
),
projection = none,
sort = $doc(G.createdAt -> -1),
readPreference = ReadPreference.secondaryPreferred
).withNbResults(
if (~playing) gameCache.nbPlaying(users._1.id)
else crosstableApi(users._1.id, users._2.id).dmap(_.nbGames)
),
currentPage = page,
maxPerPage = nb
) flatMap { pag =>
gamesJson(withFlags.copy(fens = false))(pag.currentPageResults) map { games =>
PaginatorJson(pag withCurrentPageResults games)
}
}
def byUsersVs(
userIds: Iterable[User.ID],
rated: Option[Boolean],
playing: Option[Boolean],
analysed: Option[Boolean],
withFlags: WithFlags,
since: DateTime,
nb: MaxPerPage,
page: Int
): Fu[JsObject] =
Paginator(
adapter = new Adapter[Game](
collection = gameRepo.coll,
selector = {
if (~playing) lila.game.Query.nowPlayingVs(userIds)
else
lila.game.Query.opponents(userIds) ++ $doc(
G.status $gte chess.Status.Mate.id,
G.analysed -> analysed.map[BSONValue] {
case true => BSONBoolean(true)
case _ => $doc("$exists" -> false)
}
)
} ++ $doc(
G.rated -> rated.map[BSONValue] {
case true => BSONBoolean(true)
case _ => $doc("$exists" -> false)
},
G.createdAt $gte since
),
projection = none,
sort = $doc(G.createdAt -> -1),
readPreference = ReadPreference.secondaryPreferred
),
currentPage = page,
maxPerPage = nb
) flatMap { pag =>
gamesJson(withFlags.copy(fens = false))(pag.currentPageResults) map { games =>
PaginatorJson(pag withCurrentPageResults games)
}
}
private def makeUrl(game: Game) = s"${net.baseUrl}/${game.id}/${game.naturalOrientation.name}"
private def gamesJson(withFlags: WithFlags)(games: Seq[Game]): Fu[Seq[JsObject]] = {
val allAnalysis =
if (withFlags.analysis) analysisRepo byIds games.map(_.id)
else fuccess(List.fill(games.size)(none[Analysis]))
allAnalysis flatMap { analysisOptions =>
(games map gameRepo.initialFen).sequenceFu map { initialFens =>
games zip analysisOptions zip initialFens map { case ((g, analysisOption), initialFen) =>
gameToJson(g, analysisOption, initialFen, checkToken(withFlags))
}
}
}
}
private def checkToken(withFlags: WithFlags) = withFlags applyToken apiToken.value
private def gameToJson(
g: Game,
analysisOption: Option[Analysis],
initialFen: Option[FEN],
withFlags: WithFlags
) =
Json
.obj(
"id" -> g.id,
"initialFen" -> initialFen,
"rated" -> g.rated,
"variant" -> g.variant.key,
"speed" -> g.speed.key,
"perf" -> PerfPicker.key(g),
"createdAt" -> g.createdAt,
"lastMoveAt" -> g.movedAt,
"turns" -> g.turns,
"color" -> g.turnColor.name,
"status" -> g.status.name,
"clock" -> g.clock.map { clock =>
Json.obj(
"initial" -> clock.limitSeconds,
"increment" -> clock.incrementSeconds,
"totalTime" -> clock.estimateTotalSeconds
)
},
"daysPerTurn" -> g.daysPerTurn,
"players" -> JsObject(g.players map { p =>
p.color.name -> Json
.obj(
"userId" -> p.userId,
"rating" -> p.rating,
"ratingDiff" -> p.ratingDiff
)
.add("name", p.name)
.add("provisional" -> p.provisional)
.add("moveCentis" -> withFlags.moveTimes ?? g.moveTimes(p.color).map(_.map(_.centis)))
.add("blurs" -> withFlags.blurs.option(p.blurs.nb))
.add("analysis" -> analysisOption.flatMap(analysisJson.player(g pov p.color)))
}),
"analysis" -> analysisOption.ifTrue(withFlags.analysis).map(analysisJson.moves(_)),
"moves" -> withFlags.moves.option(g.pgnMoves mkString " "),
"opening" -> withFlags.opening.??(g.opening),
"fens" -> (withFlags.fens && g.finished) ?? {
chess.Replay
.boards(
moveStrs = g.pgnMoves,
initialFen = initialFen,
variant = g.variant
)
.toOption map { boards =>
JsArray(boards map chess.format.Forsyth.exportBoard map JsString.apply)
}
},
"winner" -> g.winnerColor.map(_.name),
"url" -> makeUrl(g)
)
.noNull
}
object GameApi {
case class WithFlags(
analysis: Boolean = false,
moves: Boolean = false,
fens: Boolean = false,
opening: Boolean = false,
moveTimes: Boolean = false,
blurs: Boolean = false,
token: Option[String] = none
) {
def applyToken(validToken: String) =
copy(
blurs = token has validToken
)
}
}