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

273 lines
8.5 KiB
Scala
Raw Normal View History

2014-01-07 18:43:20 -07:00
package lila.api
2018-05-06 16:40:17 -06:00
import chess.format.FEN
2017-04-24 03:42:44 -06:00
import org.joda.time.DateTime
2014-01-07 18:43:20 -07:00
import play.api.libs.json._
2019-11-29 19:16:11 -07:00
import reactivemongo.api.bson._
2019-12-07 17:43:22 -07:00
import reactivemongo.api.ReadPreference
2014-01-07 18:43:20 -07:00
2019-12-08 10:35:26 -07:00
import lila.analyse.{ JsonView => analysisJson, Analysis }
2019-12-03 23:45:33 -07:00
import lila.common.config._
2019-12-07 17:43:22 -07:00
import lila.common.Json.jodaWrites
2018-05-06 16:40:17 -06:00
import lila.common.paginator.{ Paginator, PaginatorJson }
2016-04-01 11:50:57 -06:00
import lila.db.dsl._
2016-04-02 02:19:34 -06:00
import lila.db.paginator.{ Adapter, CachedAdapter }
2016-01-21 23:45:07 -07:00
import lila.game.BSONHandlers._
2014-02-17 02:12:19 -07:00
import lila.game.Game.{ BSONFields => G }
2018-05-06 16:40:17 -06:00
import lila.game.JsonView._
2019-12-13 07:30:20 -07:00
import lila.game.{ CrosstableApi, Game, PerfPicker }
2016-01-21 23:45:07 -07:00
import lila.user.User
2014-01-07 18:43:20 -07:00
2019-12-13 07:30:20 -07:00
final private[api] class GameApi(
2019-12-03 23:45:33 -07:00
net: NetConfig,
apiToken: Secret,
gameRepo: lila.game.GameRepo,
gameCache: lila.game.Cached,
2019-12-03 23:45:33 -07:00
analysisRepo: lila.analyse.AnalysisRepo,
crosstableApi: CrosstableApi
)(implicit ec: scala.concurrent.ExecutionContext) {
2014-01-07 18:43:20 -07:00
2017-04-24 03:42:44 -06:00
import GameApi.WithFlags
2016-02-25 03:03:09 -07:00
2016-01-21 23:45:07 -07:00
def byUser(
2019-12-13 07:30:20 -07:00
user: User,
rated: Option[Boolean],
playing: Option[Boolean],
analysed: Option[Boolean],
withFlags: WithFlags,
nb: MaxPerPage,
page: Int
): Fu[JsObject] =
Paginator(
adapter = new CachedAdapter(
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)
2019-12-13 07:30:20 -07:00
case _ => $doc("$exists" -> false)
}
2019-12-13 07:30:20 -07:00
),
projection = none,
sort = $doc(G.createdAt -> -1),
readPreference = ReadPreference.secondaryPreferred
2017-08-23 17:56:39 -06:00
),
2019-12-13 07:30:20 -07:00
nbResults =
if (~playing) gameCache.nbPlaying(user.id)
else
fuccess {
rated.fold(user.count.game) {
case true => user.count.rated
case _ => user.count.casual
}
}
2017-08-23 17:56:39 -06:00
),
2019-12-13 07:30:20 -07:00
currentPage = page,
maxPerPage = nb
) flatMap { pag =>
2017-08-23 17:56:39 -06:00
gamesJson(withFlags = withFlags)(pag.currentPageResults) map { games =>
PaginatorJson(pag withCurrentPageResults games)
}
2016-01-20 22:56:59 -07:00
}
2017-04-24 03:42:44 -06:00
def one(id: String, withFlags: WithFlags): Fu[Option[JsObject]] =
2019-12-03 23:45:33 -07:00
gameRepo game id flatMap {
_ ?? { g =>
2017-04-24 03:42:44 -06:00
gamesJson(withFlags)(List(g)) map (_.headOption)
}
}
def byUsersVs(
2019-12-13 07:30:20 -07:00
users: (User, User),
rated: Option[Boolean],
playing: Option[Boolean],
analysed: Option[Boolean],
withFlags: WithFlags,
nb: MaxPerPage,
page: Int
): Fu[JsObject] =
Paginator(
adapter = new CachedAdapter(
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)
2019-12-13 07:30:20 -07:00
case _ => $doc("$exists" -> false)
}
2019-12-13 07:30:20 -07:00
),
projection = none,
sort = $doc(G.createdAt -> -1),
readPreference = ReadPreference.secondaryPreferred
2017-08-23 17:56:39 -06:00
),
2019-12-13 07:30:20 -07:00
nbResults =
if (~playing) gameCache.nbPlaying(users._1.id)
2020-04-03 07:03:57 -06:00
else crosstableApi(users._1.id, users._2.id).dmap(_.nbGames)
2017-08-23 17:56:39 -06:00
),
2019-12-13 07:30:20 -07:00
currentPage = page,
maxPerPage = nb
) flatMap { pag =>
2017-08-23 17:56:39 -06:00
gamesJson(withFlags.copy(fens = false))(pag.currentPageResults) map { games =>
PaginatorJson(pag withCurrentPageResults games)
}
2017-04-24 03:42:44 -06:00
}
def byUsersVs(
2019-12-13 07:30:20 -07:00
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)
2019-12-13 07:30:20 -07:00
case _ => $doc("$exists" -> false)
},
G.createdAt $gte since
),
projection = none,
sort = $doc(G.createdAt -> -1),
readPreference = ReadPreference.secondaryPreferred
2017-08-23 17:56:39 -06:00
),
2019-12-13 07:30:20 -07:00
currentPage = page,
maxPerPage = nb
) flatMap { pag =>
2017-08-23 17:56:39 -06:00
gamesJson(withFlags.copy(fens = false))(pag.currentPageResults) map { games =>
PaginatorJson(pag withCurrentPageResults games)
}
}
2020-08-16 02:10:50 -06:00
private def makeUrl(game: Game) = s"${net.baseUrl}/${game.id}/${game.naturalOrientation.name}"
2014-08-30 03:03:55 -06:00
2017-04-24 03:42:44 -06:00
private def gamesJson(withFlags: WithFlags)(games: Seq[Game]): Fu[Seq[JsObject]] = {
2017-01-22 14:04:25 -07:00
val allAnalysis =
2019-12-03 23:45:33 -07:00
if (withFlags.analysis) analysisRepo byIds games.map(_.id)
2017-01-22 14:04:25 -07:00
else fuccess(List.fill(games.size)(none[Analysis]))
allAnalysis flatMap { analysisOptions =>
2019-12-03 23:45:33 -07:00
(games map gameRepo.initialFen).sequenceFu map { initialFens =>
2020-09-21 01:28:28 -06:00
games zip analysisOptions zip initialFens map { case ((g, analysisOption), initialFen) =>
gameToJson(g, analysisOption, initialFen, checkToken(withFlags))
}
2014-03-13 03:21:54 -06:00
}
2014-01-07 18:43:20 -07:00
}
2017-01-22 14:04:25 -07:00
}
2014-06-06 03:08:43 -06:00
2019-12-03 23:45:33 -07:00
private def checkToken(withFlags: WithFlags) = withFlags applyToken apiToken.value
2014-01-27 15:20:08 -07:00
2014-06-06 05:52:35 -06:00
private def gameToJson(
2019-12-13 07:30:20 -07:00
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)
)
2019-12-13 07:30:20 -07:00
.noNull
2014-01-27 15:20:08 -07:00
}
2017-04-24 03:42:44 -06:00
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
) {
2020-05-05 22:11:15 -06:00
def applyToken(validToken: String) =
copy(
blurs = token has validToken
)
2017-04-24 03:42:44 -06:00
}
}