more DB code rewrite
parent
a6ee522bfe
commit
b141f54761
|
@ -5,9 +5,8 @@ import reactivemongo.bson._
|
|||
import chess.Mode
|
||||
import chess.variant.Variant
|
||||
import lila.db.BSON
|
||||
import lila.db.BSON._
|
||||
import lila.db.BSON.BSONJodaDateTimeHandler
|
||||
import lila.db.Implicits._
|
||||
import lila.db.BSON.{ Reader, Writer }
|
||||
import lila.db.dsl._
|
||||
import lila.rating.PerfType
|
||||
|
||||
private object BSONHandlers {
|
||||
|
@ -37,9 +36,9 @@ private object BSONHandlers {
|
|||
r intO "d" map TimeControl.Correspondence.apply
|
||||
} getOrElse TimeControl.Unlimited
|
||||
def writes(w: Writer, t: TimeControl) = t match {
|
||||
case TimeControl.Clock(l, i) => BSONDocument("l" -> l, "i" -> i)
|
||||
case TimeControl.Correspondence(d) => BSONDocument("d" -> d)
|
||||
case TimeControl.Unlimited => BSONDocument()
|
||||
case TimeControl.Clock(l, i) => $doc("l" -> l, "i" -> i)
|
||||
case TimeControl.Correspondence(d) => $doc("d" -> d)
|
||||
case TimeControl.Unlimited => $empty
|
||||
}
|
||||
}
|
||||
implicit val VariantBSONHandler = new BSONHandler[BSONInteger, Variant] {
|
||||
|
@ -56,19 +55,19 @@ private object BSONHandlers {
|
|||
}
|
||||
implicit val RatingBSONHandler = new BSON[Rating] {
|
||||
def reads(r: Reader) = Rating(r.int("i"), r.boolD("p"))
|
||||
def writes(w: Writer, r: Rating) = BSONDocument(
|
||||
def writes(w: Writer, r: Rating) = $doc(
|
||||
"i" -> r.int,
|
||||
"p" -> w.boolO(r.provisional))
|
||||
}
|
||||
implicit val RegisteredBSONHandler = new BSON[Registered] {
|
||||
def reads(r: Reader) = Registered(r.str("id"), r.get[Rating]("r"))
|
||||
def writes(w: Writer, r: Registered) = BSONDocument(
|
||||
def writes(w: Writer, r: Registered) = $doc(
|
||||
"id" -> r.id,
|
||||
"r" -> r.rating)
|
||||
}
|
||||
implicit val AnonymousBSONHandler = new BSON[Anonymous] {
|
||||
def reads(r: Reader) = Anonymous(r.str("s"))
|
||||
def writes(w: Writer, a: Anonymous) = BSONDocument(
|
||||
def writes(w: Writer, a: Anonymous) = $doc(
|
||||
"s" -> a.secret)
|
||||
}
|
||||
implicit val EitherChallengerBSONHandler = new BSON[EitherChallenger] {
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
package lila.challenge
|
||||
|
||||
import org.joda.time.DateTime
|
||||
import reactivemongo.bson.{ BSONDocument, BSONInteger, BSONRegex, BSONArray, BSONBoolean }
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import lila.db.BSON.BSONJodaDateTimeHandler
|
||||
import lila.db.Implicits.LilaBSONDocumentZero
|
||||
import lila.db.Types.Coll
|
||||
import lila.db.dsl._
|
||||
import lila.user.{ User, UserRepo }
|
||||
|
||||
private final class ChallengeRepo(coll: Coll, maxPerUser: Int) {
|
||||
|
@ -14,9 +11,9 @@ private final class ChallengeRepo(coll: Coll, maxPerUser: Int) {
|
|||
import BSONHandlers._
|
||||
import Challenge._
|
||||
|
||||
def byId(id: Challenge.ID) = coll.find(selectId(id)).one[Challenge]
|
||||
def byId(id: Challenge.ID) = coll.find($id(id)).one[Challenge]
|
||||
|
||||
def exists(id: Challenge.ID) = coll.count(selectId(id).some).map(0<)
|
||||
def exists(id: Challenge.ID) = coll.count($id(id).some).map(0<)
|
||||
|
||||
def insert(c: Challenge): Funit =
|
||||
coll.insert(c) >> c.challenger.right.toOption.?? { challenger =>
|
||||
|
@ -27,55 +24,55 @@ private final class ChallengeRepo(coll: Coll, maxPerUser: Int) {
|
|||
}
|
||||
|
||||
def createdByChallengerId(userId: String): Fu[List[Challenge]] =
|
||||
coll.find(selectCreated ++ BSONDocument("challenger.id" -> userId))
|
||||
.sort(BSONDocument("createdAt" -> 1))
|
||||
coll.find(selectCreated ++ $doc("challenger.id" -> userId))
|
||||
.sort($doc("createdAt" -> 1))
|
||||
.cursor[Challenge]().collect[List]()
|
||||
|
||||
def createdByDestId(userId: String): Fu[List[Challenge]] =
|
||||
coll.find(selectCreated ++ BSONDocument("destUser.id" -> userId))
|
||||
.sort(BSONDocument("createdAt" -> 1))
|
||||
coll.find(selectCreated ++ $doc("destUser.id" -> userId))
|
||||
.sort($doc("createdAt" -> 1))
|
||||
.cursor[Challenge]().collect[List]()
|
||||
|
||||
def removeByUserId(userId: String): Funit =
|
||||
coll.remove(BSONDocument("$or" -> BSONArray(
|
||||
BSONDocument("challenger.id" -> userId),
|
||||
BSONDocument("destUser.id" -> userId)
|
||||
coll.remove($doc("$or" -> $arr(
|
||||
$doc("challenger.id" -> userId),
|
||||
$doc("destUser.id" -> userId)
|
||||
))).void
|
||||
|
||||
def like(c: Challenge) = ~(for {
|
||||
challengerId <- c.challengerUserId
|
||||
destUserId <- c.destUserId
|
||||
if c.active
|
||||
} yield coll.find(selectCreated ++ BSONDocument(
|
||||
} yield coll.find(selectCreated ++ $doc(
|
||||
"challenger.id" -> challengerId,
|
||||
"destUser.id" -> destUserId)).one[Challenge])
|
||||
|
||||
private[challenge] def countCreatedByDestId(userId: String): Fu[Int] =
|
||||
coll.count(Some(selectCreated ++ BSONDocument("destUser.id" -> userId)))
|
||||
coll.count(Some(selectCreated ++ $doc("destUser.id" -> userId)))
|
||||
|
||||
private[challenge] def realTimeUnseenSince(date: DateTime, max: Int): Fu[List[Challenge]] =
|
||||
coll.find(selectCreated ++ selectClock ++ BSONDocument(
|
||||
"seenAt" -> BSONDocument("$lt" -> date)
|
||||
coll.find(selectCreated ++ selectClock ++ $doc(
|
||||
"seenAt" -> $doc("$lt" -> date)
|
||||
)).cursor[Challenge]().collect[List](max)
|
||||
|
||||
private[challenge] def expiredIds(max: Int): Fu[List[Challenge.ID]] =
|
||||
coll.distinct(
|
||||
"_id",
|
||||
BSONDocument("expiresAt" -> BSONDocument("$lt" -> DateTime.now)).some
|
||||
$doc("expiresAt" -> $doc("$lt" -> DateTime.now)).some
|
||||
) map lila.db.BSON.asStrings
|
||||
|
||||
def setSeenAgain(id: Challenge.ID) = coll.update(
|
||||
selectId(id),
|
||||
BSONDocument(
|
||||
"$set" -> BSONDocument(
|
||||
$id(id),
|
||||
$doc(
|
||||
"$set" -> $doc(
|
||||
"status" -> Status.Created.id,
|
||||
"seenAt" -> DateTime.now,
|
||||
"expiresAt" -> inTwoWeeks))
|
||||
).void
|
||||
|
||||
def setSeen(id: Challenge.ID) = coll.update(
|
||||
selectId(id),
|
||||
BSONDocument("$set" -> BSONDocument("seenAt" -> DateTime.now))
|
||||
$id(id),
|
||||
$doc("$set" -> $doc("seenAt" -> DateTime.now))
|
||||
).void
|
||||
|
||||
def offline(challenge: Challenge) = setStatus(challenge, Status.Offline, Some(_ plusHours 3))
|
||||
|
@ -84,25 +81,24 @@ private final class ChallengeRepo(coll: Coll, maxPerUser: Int) {
|
|||
def accept(challenge: Challenge) = setStatus(challenge, Status.Accepted, Some(_ plusHours 3))
|
||||
|
||||
def statusById(id: Challenge.ID) = coll.find(
|
||||
selectId(id),
|
||||
BSONDocument("status" -> true, "_id" -> false)
|
||||
).one[BSONDocument].map { _.flatMap(_.getAs[Status]("status")) }
|
||||
$id(id),
|
||||
$doc("status" -> true, "_id" -> false)
|
||||
).one[Bdoc].map { _.flatMap(_.getAs[Status]("status")) }
|
||||
|
||||
private def setStatus(
|
||||
challenge: Challenge,
|
||||
status: Status,
|
||||
expiresAt: Option[DateTime => DateTime] = None) = coll.update(
|
||||
selectCreated ++ selectId(challenge.id),
|
||||
BSONDocument("$set" -> BSONDocument(
|
||||
selectCreated ++ $id(challenge.id),
|
||||
$doc("$set" -> $doc(
|
||||
"status" -> status.id,
|
||||
"expiresAt" -> expiresAt.fold(inTwoWeeks) { _(DateTime.now) }
|
||||
))
|
||||
).void
|
||||
|
||||
private[challenge] def remove(id: Challenge.ID) = coll.remove(selectId(id)).void
|
||||
private[challenge] def remove(id: Challenge.ID) = coll.remove($id(id)).void
|
||||
|
||||
private def selectId(id: Challenge.ID) = BSONDocument("_id" -> id)
|
||||
private val selectCreated = BSONDocument("status" -> Status.Created.id)
|
||||
private val selectClock = BSONDocument("timeControl.l" -> BSONDocument("$exists" -> true))
|
||||
private val selectCreated = $doc("status" -> Status.Created.id)
|
||||
private val selectClock = $doc("timeControl.l" $exists true)
|
||||
}
|
||||
|
||||
|
|
|
@ -21,8 +21,9 @@ trait CollExt {
|
|||
def byId[D: BSONDocumentReader, I: BSONValueWriter](id: I): Fu[Option[D]] =
|
||||
one[D]($id(id))
|
||||
|
||||
def byId[D: BSONDocumentReader](id: String): Fu[Option[D]] =
|
||||
one[D]($id(id))
|
||||
def byId[D: BSONDocumentReader](id: String): Fu[Option[D]] = one[D]($id(id))
|
||||
|
||||
def byId[D: BSONDocumentReader](id: Int): Fu[Option[D]] = one[D]($id(id))
|
||||
|
||||
def byIds[D: BSONDocumentReader, I: BSONValueWriter](ids: Iterable[I]): Fu[List[D]] =
|
||||
list[D]($inIds(ids))
|
||||
|
|
|
@ -25,6 +25,7 @@ trait dsl {
|
|||
|
||||
type Coll = reactivemongo.api.collections.bson.BSONCollection
|
||||
type Bdoc = BSONDocument
|
||||
type Barr = BSONArray
|
||||
|
||||
type QueryBuilder = GenericQueryBuilder[BSONSerializationPack.type]
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ import lila.user.{ User, UidNb }
|
|||
object GameRepo {
|
||||
|
||||
// dirty
|
||||
private val coll = Env.current.gameColl
|
||||
val coll = Env.current.gameColl
|
||||
|
||||
type ID = String
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import reactivemongo.api.collections.bson.BSONBatchCommands.AggregationFramework
|
|||
import reactivemongo.bson._
|
||||
import scalaz.NonEmptyList
|
||||
|
||||
import lila.db.Implicits._
|
||||
import lila.db.dsl._
|
||||
|
||||
private final class AggregationPipeline {
|
||||
|
||||
|
@ -14,20 +14,20 @@ private final class AggregationPipeline {
|
|||
|
||||
private lazy val movetimeIdDispatcher =
|
||||
MovetimeRange.reversedNoInf.foldLeft[BSONValue](BSONInteger(MovetimeRange.MTRInf.id)) {
|
||||
case (acc, mtr) => BSONDocument(
|
||||
"$cond" -> BSONArray(
|
||||
BSONDocument("$lte" -> BSONArray("$" + F.moves("t"), mtr.tenths.last)),
|
||||
case (acc, mtr) => $doc(
|
||||
"$cond" -> $arr(
|
||||
$doc("$lte" -> $arr("$" + F.moves("t"), mtr.tenths.last)),
|
||||
mtr.id,
|
||||
acc))
|
||||
}
|
||||
private lazy val materialIdDispatcher = BSONDocument(
|
||||
"$cond" -> BSONArray(
|
||||
BSONDocument("$eq" -> BSONArray("$" + F.moves("i"), 0)),
|
||||
private lazy val materialIdDispatcher = $doc(
|
||||
"$cond" -> $arr(
|
||||
$doc("$eq" -> $arr("$" + F.moves("i"), 0)),
|
||||
MaterialRange.Equal.id,
|
||||
MaterialRange.reversedButEqualAndLast.foldLeft[BSONValue](BSONInteger(MaterialRange.Up4.id)) {
|
||||
case (acc, mat) => BSONDocument(
|
||||
"$cond" -> BSONArray(
|
||||
BSONDocument(mat.negative.fold("$lt", "$lte") -> BSONArray("$" + F.moves("i"), mat.imbalance)),
|
||||
case (acc, mat) => $doc(
|
||||
"$cond" -> $arr(
|
||||
$doc(mat.negative.fold("$lt", "$lte") -> $arr("$" + F.moves("i"), mat.imbalance)),
|
||||
mat.id,
|
||||
acc))
|
||||
}))
|
||||
|
@ -48,7 +48,7 @@ private final class AggregationPipeline {
|
|||
"nb" -> SumValue(1),
|
||||
"ids" -> AddToSet("_id")
|
||||
).some
|
||||
private def groupMulti(d: Dimension[_], metricDbKey: String) = Group(BSONDocument(
|
||||
private def groupMulti(d: Dimension[_], metricDbKey: String) = Group($doc(
|
||||
"dimension" -> dimensionGroupId(d),
|
||||
"metric" -> ("$" + metricDbKey)))(
|
||||
"v" -> SumValue(1),
|
||||
|
@ -60,17 +60,17 @@ private final class AggregationPipeline {
|
|||
"stack" -> PushMulti(
|
||||
"metric" -> "_id.metric",
|
||||
"v" -> "v")).some
|
||||
private val sliceIds = Project(BSONDocument(
|
||||
private val sliceIds = Project($doc(
|
||||
"_id" -> true,
|
||||
"v" -> true,
|
||||
"nb" -> true,
|
||||
"ids" -> BSONDocument("$slice" -> BSONArray("$ids", 4))
|
||||
"ids" -> $doc("$slice" -> $arr("$ids", 4))
|
||||
)).some
|
||||
private val sliceStackedIds = Project(BSONDocument(
|
||||
private val sliceStackedIds = Project($doc(
|
||||
"_id" -> true,
|
||||
"nb" -> true,
|
||||
"stack" -> true,
|
||||
"ids" -> BSONDocument("$slice" -> BSONArray("$ids", 4))
|
||||
"ids" -> $doc("$slice" -> $arr("$ids", 4))
|
||||
)).some
|
||||
|
||||
def apply(question: Question[_], userId: String): NonEmptyList[PipelineOperator] = {
|
||||
|
@ -78,11 +78,11 @@ private final class AggregationPipeline {
|
|||
val gameMatcher = combineDocs(question.filters.collect {
|
||||
case f if f.dimension.isInGame => f.matcher
|
||||
})
|
||||
def matchMoves(extraMatcher: BSONDocument = BSONDocument()) =
|
||||
def matchMoves(extraMatcher: Bdoc = $empty) =
|
||||
combineDocs(extraMatcher :: question.filters.collect {
|
||||
case f if f.dimension.isInMove => f.matcher
|
||||
}).some.filterNot(_.isEmpty) map Match
|
||||
def projectForMove = Project(BSONDocument({
|
||||
def projectForMove = Project($doc({
|
||||
metric.dbKey :: dimension.dbKey :: filters.collect {
|
||||
case Filter(d, _) if d.isInMove => d.dbKey
|
||||
}
|
||||
|
@ -92,10 +92,10 @@ private final class AggregationPipeline {
|
|||
Match(
|
||||
selectUserId(userId) ++
|
||||
gameMatcher ++
|
||||
(dimension == Dimension.Opening).??(BSONDocument(F.eco -> BSONDocument("$exists" -> true))) ++
|
||||
Metric.requiresAnalysis(metric).??(BSONDocument(F.analysed -> true)) ++
|
||||
(dimension == Dimension.Opening).??($doc(F.eco -> $doc("$exists" -> true))) ++
|
||||
Metric.requiresAnalysis(metric).??($doc(F.analysed -> true)) ++
|
||||
(Metric.requiresStableRating(metric) || Dimension.requiresStableRating(dimension)).?? {
|
||||
BSONDocument(F.provisional -> BSONDocument("$ne" -> true))
|
||||
$doc(F.provisional -> $doc("$ne" -> true))
|
||||
}
|
||||
),
|
||||
/* sortDate :: */ sampleGames :: ((metric match {
|
||||
|
@ -118,15 +118,15 @@ private final class AggregationPipeline {
|
|||
case M.Opportunism => List(
|
||||
projectForMove,
|
||||
unwindMoves,
|
||||
matchMoves(BSONDocument(F.moves("o") -> BSONDocument("$exists" -> true))),
|
||||
matchMoves($doc(F.moves("o") -> $doc("$exists" -> true))),
|
||||
sampleMoves,
|
||||
group(dimension, GroupFunction("$push", BSONDocument(
|
||||
"$cond" -> BSONArray("$" + F.moves("o"), 1, 0)
|
||||
group(dimension, GroupFunction("$push", $doc(
|
||||
"$cond" -> $arr("$" + F.moves("o"), 1, 0)
|
||||
))),
|
||||
sliceIds,
|
||||
Project(BSONDocument(
|
||||
Project($doc(
|
||||
"_id" -> true,
|
||||
"v" -> BSONDocument("$multiply" -> BSONArray(100, BSONDocument("$avg" -> "$v"))),
|
||||
"v" -> $doc("$multiply" -> $arr(100, $doc("$avg" -> "$v"))),
|
||||
"nb" -> true,
|
||||
"ids" -> true
|
||||
)).some
|
||||
|
@ -134,15 +134,15 @@ private final class AggregationPipeline {
|
|||
case M.Luck => List(
|
||||
projectForMove,
|
||||
unwindMoves,
|
||||
matchMoves(BSONDocument(F.moves("l") -> BSONDocument("$exists" -> true))),
|
||||
matchMoves($doc(F.moves("l") -> $doc("$exists" -> true))),
|
||||
sampleMoves,
|
||||
group(dimension, GroupFunction("$push", BSONDocument(
|
||||
"$cond" -> BSONArray("$" + F.moves("l"), 1, 0)
|
||||
group(dimension, GroupFunction("$push", $doc(
|
||||
"$cond" -> $arr("$" + F.moves("l"), 1, 0)
|
||||
))),
|
||||
sliceIds,
|
||||
Project(BSONDocument(
|
||||
Project($doc(
|
||||
"_id" -> true,
|
||||
"v" -> BSONDocument("$multiply" -> BSONArray(100, BSONDocument("$avg" -> "$v"))),
|
||||
"v" -> $doc("$multiply" -> $arr(100, $doc("$avg" -> "$v"))),
|
||||
"nb" -> true,
|
||||
"ids" -> true
|
||||
)).some
|
||||
|
@ -153,17 +153,17 @@ private final class AggregationPipeline {
|
|||
matchMoves(),
|
||||
sampleMoves,
|
||||
group(dimension, SumValue(1)),
|
||||
Project(BSONDocument(
|
||||
Project($doc(
|
||||
"v" -> true,
|
||||
"ids" -> true,
|
||||
"nb" -> BSONDocument("$size" -> "$ids")
|
||||
"nb" -> $doc("$size" -> "$ids")
|
||||
)).some,
|
||||
Project(BSONDocument(
|
||||
"v" -> BSONDocument(
|
||||
"$divide" -> BSONArray("$v", "$nb")
|
||||
Project($doc(
|
||||
"v" -> $doc(
|
||||
"$divide" -> $arr("$v", "$nb")
|
||||
),
|
||||
"nb" -> true,
|
||||
"ids" -> BSONDocument("$slice" -> BSONArray("$ids", 4))
|
||||
"ids" -> $doc("$slice" -> $arr("$ids", 4))
|
||||
)).some
|
||||
)
|
||||
case M.Movetime => List(
|
||||
|
@ -172,7 +172,7 @@ private final class AggregationPipeline {
|
|||
matchMoves(),
|
||||
sampleMoves,
|
||||
group(dimension, GroupFunction("$avg",
|
||||
BSONDocument("$divide" -> BSONArray("$" + F.moves("t"), 10))
|
||||
$doc("$divide" -> $arr("$" + F.moves("t"), 10))
|
||||
)),
|
||||
sliceIds
|
||||
)
|
||||
|
|
|
@ -5,8 +5,7 @@ import reactivemongo.bson.Macros
|
|||
|
||||
import chess.{ Role, Color }
|
||||
import lila.db.BSON
|
||||
import lila.db.BSON._
|
||||
import lila.db.Implicits._
|
||||
import lila.db.dsl._
|
||||
import lila.game.BSONHandlers.StatusBSONHandler
|
||||
import lila.rating.PerfType
|
||||
|
||||
|
@ -61,7 +60,7 @@ private object BSONHandlers {
|
|||
def write(e: MaterialRange) = BSONInteger(e.id)
|
||||
}
|
||||
implicit def MoveBSONHandler = new BSON[Move] {
|
||||
def reads(r: Reader) = Move(
|
||||
def reads(r: BSON.Reader) = Move(
|
||||
phase = r.get[Phase]("p"),
|
||||
tenths = r.get[Int]("t"),
|
||||
role = r.get[Role]("r"),
|
||||
|
@ -71,7 +70,7 @@ private object BSONHandlers {
|
|||
material = r.int("i"),
|
||||
opportunism = r.boolO("o"),
|
||||
luck = r.boolO("l"))
|
||||
def writes(w: Writer, b: Move) = BSONDocument(
|
||||
def writes(w: BSON.Writer, b: Move) = BSONDocument(
|
||||
"p" -> b.phase,
|
||||
"t" -> b.tenths,
|
||||
"r" -> b.role,
|
||||
|
@ -85,7 +84,7 @@ private object BSONHandlers {
|
|||
|
||||
implicit def EntryBSONHandler = new BSON[Entry] {
|
||||
import Entry.BSONFields._
|
||||
def reads(r: Reader) = Entry(
|
||||
def reads(r: BSON.Reader) = Entry(
|
||||
id = r.str(id),
|
||||
number = r.int(number),
|
||||
userId = r.str(userId),
|
||||
|
@ -104,7 +103,7 @@ private object BSONHandlers {
|
|||
analysed = r.boolD(analysed),
|
||||
provisional = r.boolD(provisional),
|
||||
date = r.date(date))
|
||||
def writes(w: Writer, e: Entry) = BSONDocument(
|
||||
def writes(w: BSON.Writer, e: Entry) = BSONDocument(
|
||||
id -> e.id,
|
||||
number -> e.number,
|
||||
userId -> e.userId,
|
||||
|
|
|
@ -4,7 +4,7 @@ import play.twirl.api.Html
|
|||
import reactivemongo.bson._
|
||||
|
||||
import chess.{ Color, Role }
|
||||
import lila.db.Types._
|
||||
import lila.db.dsl._
|
||||
import lila.rating.PerfType
|
||||
|
||||
sealed abstract class Dimension[A: BSONValueHandler](
|
||||
|
|
|
@ -3,15 +3,12 @@ package lila.insight
|
|||
import akka.actor.ActorRef
|
||||
import org.joda.time.DateTime
|
||||
import play.api.libs.iteratee._
|
||||
import play.api.libs.json.Json
|
||||
import reactivemongo.bson._
|
||||
|
||||
import lila.db.dsl._
|
||||
import lila.db.BSON._
|
||||
import lila.db.Implicits._
|
||||
import lila.db.dsl._
|
||||
import lila.game.BSONHandlers.gameBSONHandler
|
||||
import lila.game.tube.gameTube
|
||||
import lila.game.{ Game, Query }
|
||||
import lila.game.{ Game, GameRepo, Query }
|
||||
import lila.hub.Sequencer
|
||||
import lila.rating.PerfType
|
||||
import lila.user.User
|
||||
|
@ -55,10 +52,15 @@ private final class Indexer(storage: Storage, sequencer: ActorRef) {
|
|||
private def fetchFirstGame(user: User): Fu[Option[Game]] =
|
||||
if (user.count.rated == 0) fuccess(none)
|
||||
else {
|
||||
(user.count.rated >= maxGames) ??
|
||||
pimpQB($query(gameQuery(user))).sort(Query.sortCreated).skip(maxGames - 1).one[Game]
|
||||
} orElse
|
||||
pimpQB($query(gameQuery(user))).sort(Query.sortChronological).one[Game]
|
||||
(user.count.rated >= maxGames) ?? GameRepo.coll
|
||||
.find(gameQuery(user))
|
||||
.sort(Query.sortCreated)
|
||||
.skip(maxGames - 1)
|
||||
.one[Game]
|
||||
} orElse GameRepo.coll
|
||||
.find(gameQuery(user))
|
||||
.sort(Query.sortCreated)
|
||||
.one[Game]
|
||||
|
||||
private def computeFrom(user: User, from: DateTime, fromNumber: Int): Funit = {
|
||||
storage nbByPerf user.id flatMap { nbs =>
|
||||
|
@ -71,10 +73,8 @@ private final class Indexer(storage: Storage, sequencer: ActorRef) {
|
|||
e.printStackTrace
|
||||
} map (_.toOption)
|
||||
}
|
||||
val query = $query(gameQuery(user) ++ Json.obj(Game.BSONFields.createdAt -> $gte($date(from))))
|
||||
pimpQB(query)
|
||||
.sort(Query.sortChronological)
|
||||
.cursor[Game]()
|
||||
val query = gameQuery(user) ++ $doc(Game.BSONFields.createdAt $gte from)
|
||||
GameRepo.sortedCursor(query, Query.sortChronological)
|
||||
.enumerate(maxGames, stopOnError = true) &>
|
||||
Enumeratee.grouped(Iteratee takeUpTo 4) &>
|
||||
Enumeratee.mapM[Seq[Game]].apply[Seq[Entry]] { games =>
|
||||
|
|
|
@ -4,7 +4,7 @@ import org.joda.time.DateTime
|
|||
import reactivemongo.api.collections.bson.BSONBatchCommands.AggregationFramework._
|
||||
import reactivemongo.bson._
|
||||
|
||||
import lila.db.Implicits._
|
||||
import lila.db.dsl._
|
||||
import lila.game.{ Game, GameRepo, Pov }
|
||||
import lila.user.User
|
||||
|
||||
|
@ -39,7 +39,7 @@ final class InsightApi(
|
|||
def userStatus(user: User): Fu[UserStatus] =
|
||||
GameRepo lastFinishedRatedNotFromPosition user flatMap {
|
||||
case None => fuccess(UserStatus.NoGame)
|
||||
case Some(game) => storage fetchLast user map {
|
||||
case Some(game) => storage fetchLast user.id map {
|
||||
case None => UserStatus.Empty
|
||||
case Some(entry) if entry.date isBefore game.createdAt => UserStatus.Stale
|
||||
case _ => UserStatus.Fresh
|
||||
|
|
|
@ -7,8 +7,7 @@ import reactivemongo.bson._
|
|||
import scala.concurrent.duration._
|
||||
import scalaz.NonEmptyList
|
||||
|
||||
import lila.db.BSON._
|
||||
import lila.db.Implicits._
|
||||
import lila.db.dsl._
|
||||
import lila.user.UserRepo
|
||||
import lila.rating.PerfType
|
||||
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
package lila.insight
|
||||
|
||||
import org.joda.time.DateTime
|
||||
import play.api.libs.iteratee._
|
||||
import reactivemongo.api.collections.bson.BSONBatchCommands.AggregationFramework._
|
||||
import reactivemongo.bson._
|
||||
import reactivemongo.bson.Macros
|
||||
|
||||
import lila.db.BSON._
|
||||
import lila.db.Implicits._
|
||||
import lila.db.dsl._
|
||||
|
||||
case class UserCache(
|
||||
_id: String, // user id
|
||||
|
@ -22,11 +19,9 @@ private final class UserCacheApi(coll: Coll) {
|
|||
|
||||
private implicit val userCacheBSONHandler = Macros.handler[UserCache]
|
||||
|
||||
def find(id: String) = coll.find(selectId(id)).one[UserCache]
|
||||
def find(id: String) = coll.one[UserCache]($id(id))
|
||||
|
||||
def save(u: UserCache) = coll.update(selectId(u.id), u, upsert = true).void
|
||||
def save(u: UserCache) = coll.update($id(u.id), u, upsert = true).void
|
||||
|
||||
def remove(id: String) = coll.remove(selectId(id)).void
|
||||
|
||||
private def selectId(id: String) = BSONDocument("_id" -> id)
|
||||
def remove(id: String) = coll.remove($id(id)).void
|
||||
}
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
package lila.lobby
|
||||
|
||||
import org.joda.time.DateTime
|
||||
import reactivemongo.bson.{ BSONDocument, BSONInteger, BSONRegex, BSONArray, BSONBoolean }
|
||||
import reactivemongo.core.commands._
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import actorApi.LobbyUser
|
||||
import lila.db.BSON.BSONJodaDateTimeHandler
|
||||
import lila.db.Types.Coll
|
||||
import lila.db.dsl._
|
||||
import lila.memo.AsyncCache
|
||||
import lila.user.{ User, UserRepo }
|
||||
|
||||
|
@ -23,8 +21,8 @@ final class SeekApi(
|
|||
private object ForUser extends CacheKey
|
||||
|
||||
private def allCursor =
|
||||
coll.find(BSONDocument())
|
||||
.sort(BSONDocument("createdAt" -> -1))
|
||||
coll.find($empty)
|
||||
.sort($doc("createdAt" -> -1))
|
||||
.cursor[Seek]()
|
||||
|
||||
private val cache = AsyncCache[CacheKey, List[Seek]](
|
||||
|
@ -58,7 +56,7 @@ final class SeekApi(
|
|||
}._1.reverse
|
||||
|
||||
def find(id: String): Fu[Option[Seek]] =
|
||||
coll.find(BSONDocument("_id" -> id)).one[Seek]
|
||||
coll.find($doc("_id" -> id)).one[Seek]
|
||||
|
||||
def insert(seek: Seek) = coll.insert(seek) >> findByUser(seek.user.id).flatMap {
|
||||
case seeks if seeks.size <= maxPerUser => funit
|
||||
|
@ -67,27 +65,27 @@ final class SeekApi(
|
|||
} >> cache.clear
|
||||
|
||||
def findByUser(userId: String): Fu[List[Seek]] =
|
||||
coll.find(BSONDocument("user.id" -> userId))
|
||||
.sort(BSONDocument("createdAt" -> -1))
|
||||
coll.find($doc("user.id" -> userId))
|
||||
.sort($doc("createdAt" -> -1))
|
||||
.cursor[Seek]().collect[List]()
|
||||
|
||||
def remove(seek: Seek) =
|
||||
coll.remove(BSONDocument("_id" -> seek.id)).void >> cache.clear
|
||||
coll.remove($doc("_id" -> seek.id)).void >> cache.clear
|
||||
|
||||
def archive(seek: Seek, gameId: String) = {
|
||||
val archiveDoc = Seek.seekBSONHandler.write(seek) ++ BSONDocument(
|
||||
val archiveDoc = Seek.seekBSONHandler.write(seek) ++ $doc(
|
||||
"gameId" -> gameId,
|
||||
"archivedAt" -> DateTime.now)
|
||||
coll.remove(BSONDocument("_id" -> seek.id)).void >>
|
||||
coll.remove($doc("_id" -> seek.id)).void >>
|
||||
cache.clear >>
|
||||
archiveColl.insert(archiveDoc)
|
||||
}
|
||||
|
||||
def findArchived(gameId: String): Fu[Option[Seek]] =
|
||||
archiveColl.find(BSONDocument("gameId" -> gameId)).one[Seek]
|
||||
archiveColl.find($doc("gameId" -> gameId)).one[Seek]
|
||||
|
||||
def removeBy(seekId: String, userId: String) =
|
||||
coll.remove(BSONDocument(
|
||||
coll.remove($doc(
|
||||
"_id" -> seekId,
|
||||
"user.id" -> userId
|
||||
)).void >> cache.clear
|
||||
|
|
|
@ -2,9 +2,8 @@ package lila.opening
|
|||
|
||||
import org.goochjs.glicko2._
|
||||
import org.joda.time.DateTime
|
||||
import reactivemongo.bson.{ BSONDocument, BSONInteger, BSONDouble }
|
||||
|
||||
import lila.db.Types.Coll
|
||||
import lila.db.dsl._
|
||||
import lila.rating.{ Glicko, Perf }
|
||||
import lila.user.{ User, UserRepo }
|
||||
|
||||
|
@ -34,13 +33,13 @@ private[opening] final class Finisher(
|
|||
userRatingDiff = userPerf.intRating - user.perfs.opening.intRating)
|
||||
((api.attempt add a) >> {
|
||||
openingColl.update(
|
||||
BSONDocument("_id" -> opening.id),
|
||||
BSONDocument("$inc" -> BSONDocument(
|
||||
Opening.BSONFields.attempts -> BSONInteger(1),
|
||||
Opening.BSONFields.wins -> BSONInteger(win ? 1 | 0)
|
||||
)) ++ BSONDocument("$set" -> BSONDocument(
|
||||
Opening.BSONFields.perf -> Perf.perfBSONHandler.write(openingPerf)
|
||||
))) zip UserRepo.setPerf(user.id, "opening", userPerf)
|
||||
$id(opening.id),
|
||||
$inc(
|
||||
Opening.BSONFields.attempts -> $int(1),
|
||||
Opening.BSONFields.wins -> $int(win ? 1 | 0)
|
||||
) ++ $set(
|
||||
Opening.BSONFields.perf -> Perf.perfBSONHandler.write(openingPerf)
|
||||
)) zip UserRepo.setPerf(user.id, "opening", userPerf)
|
||||
}) recover lila.db.recoverDuplicateKey(_ => ()) inject (a -> none)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,11 +3,10 @@ package lila.opening
|
|||
import scala.util.{ Try, Success, Failure }
|
||||
|
||||
import org.joda.time.DateTime
|
||||
import play.api.libs.json.JsValue
|
||||
import reactivemongo.bson.{ BSONDocument, BSONInteger, BSONRegex, BSONArray, BSONBoolean }
|
||||
import reactivemongo.bson.BSONArray
|
||||
import reactivemongo.core.commands._
|
||||
|
||||
import lila.db.Types.Coll
|
||||
import lila.db.dsl._
|
||||
import lila.user.{ User, UserRepo }
|
||||
|
||||
private[opening] final class OpeningApi(
|
||||
|
@ -21,39 +20,20 @@ private[opening] final class OpeningApi(
|
|||
object opening {
|
||||
|
||||
def find(id: Opening.ID): Fu[Option[Opening]] =
|
||||
openingColl.find(BSONDocument("_id" -> id)).one[Opening]
|
||||
|
||||
def importOne(json: JsValue, token: String): Fu[Opening.ID] =
|
||||
if (token != apiToken) fufail("Invalid API token")
|
||||
else {
|
||||
import Generated.generatedJSONRead
|
||||
Try(json.as[Generated]) match {
|
||||
case Failure(err) => fufail(err.getMessage)
|
||||
case Success(generated) => generated.toOpening.future flatMap insertOpening
|
||||
}
|
||||
}
|
||||
|
||||
def insertOpening(opening: Opening.ID => Opening): Fu[Opening.ID] =
|
||||
lila.db.Util findNextId openingColl flatMap { id =>
|
||||
val o = opening(id)
|
||||
openingColl.count(BSONDocument("fen" -> o.fen).some) flatMap {
|
||||
case 0 => openingColl insert o inject o.id
|
||||
case _ => fufail("Duplicate opening")
|
||||
}
|
||||
}
|
||||
openingColl.byId[Opening](id)
|
||||
}
|
||||
|
||||
object attempt {
|
||||
|
||||
def find(openingId: Opening.ID, userId: String): Fu[Option[Attempt]] =
|
||||
attemptColl.find(BSONDocument(
|
||||
attemptColl.find($doc(
|
||||
Attempt.BSONFields.id -> Attempt.makeId(openingId, userId)
|
||||
)).one[Attempt]
|
||||
|
||||
def add(a: Attempt) = attemptColl insert a void
|
||||
|
||||
def hasPlayed(user: User, opening: Opening): Fu[Boolean] =
|
||||
attemptColl.count(BSONDocument(
|
||||
attemptColl.count($doc(
|
||||
Attempt.BSONFields.id -> Attempt.makeId(opening.id, user.id)
|
||||
).some) map (0!=)
|
||||
|
||||
|
@ -62,9 +42,9 @@ private[opening] final class OpeningApi(
|
|||
import reactivemongo.api.collections.bson.BSONBatchCommands.AggregationFramework.{ Group, Limit, Match, Push }
|
||||
|
||||
val playedIdsGroup =
|
||||
Group(BSONBoolean(true))("ids" -> Push(Attempt.BSONFields.openingId))
|
||||
Group($boolean(true))("ids" -> Push(Attempt.BSONFields.openingId))
|
||||
|
||||
col.aggregate(Match(BSONDocument(Attempt.BSONFields.userId -> user.id)),
|
||||
col.aggregate(Match($doc(Attempt.BSONFields.userId -> user.id)),
|
||||
List(Limit(max), playedIdsGroup)).map(_.documents.headOption.flatMap(
|
||||
_.getAs[BSONArray]("ids")).getOrElse(BSONArray()))
|
||||
}
|
||||
|
@ -72,9 +52,9 @@ private[opening] final class OpeningApi(
|
|||
|
||||
object identify {
|
||||
def apply(fen: String, max: Int): Fu[List[String]] = nameColl.find(
|
||||
BSONDocument("_id" -> fen),
|
||||
BSONDocument("_id" -> false)
|
||||
).one[BSONDocument] map { obj =>
|
||||
$doc("_id" -> fen),
|
||||
$doc("_id" -> false)
|
||||
).one[Bdoc] map { obj =>
|
||||
~obj.??(_.getAs[List[String]]("names"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
package lila.opening
|
||||
|
||||
import reactivemongo.bson.BSONArray
|
||||
import scala.concurrent.duration._
|
||||
import scala.util.Random
|
||||
|
||||
import reactivemongo.api.QueryOpts
|
||||
import reactivemongo.bson.{ BSONDocument, BSONInteger, BSONArray }
|
||||
|
||||
import lila.db.Types.Coll
|
||||
import lila.db.dsl._
|
||||
import lila.user.User
|
||||
|
||||
private[opening] final class Selector(
|
||||
|
@ -20,8 +18,8 @@ private[opening] final class Selector(
|
|||
|
||||
def apply(me: Option[User]): Fu[Opening] = (me match {
|
||||
case None =>
|
||||
openingColl.find(BSONDocument())
|
||||
.options(QueryOpts(skipN = Random nextInt anonSkipMax))
|
||||
openingColl.find($empty)
|
||||
.skip(Random nextInt anonSkipMax)
|
||||
.one[Opening] flatten "Can't find a opening for anon player!"
|
||||
case Some(user) => api.attempt.playedIds(user, modulo) flatMap { ids =>
|
||||
tryRange(user, toleranceStep, ids)
|
||||
|
@ -31,16 +29,15 @@ private[opening] final class Selector(
|
|||
}).mon(_.opening.selector.time) >>- lila.mon.opening.selector.count()
|
||||
|
||||
private def tryRange(user: User, tolerance: Int, ids: BSONArray): Fu[Opening] =
|
||||
openingColl.find(BSONDocument(
|
||||
Opening.BSONFields.id -> BSONDocument("$nin" -> ids),
|
||||
Opening.BSONFields.rating -> BSONDocument(
|
||||
"$gt" -> BSONInteger(user.perfs.opening.intRating - tolerance),
|
||||
"$lt" -> BSONInteger(user.perfs.opening.intRating + tolerance)
|
||||
)
|
||||
)).one[Opening] flatMap {
|
||||
case Some(opening) => fuccess(opening)
|
||||
case None => if ((tolerance + toleranceStep) <= toleranceMax)
|
||||
tryRange(user, tolerance + toleranceStep, ids)
|
||||
else fufail(s"Can't find a opening for user $user!")
|
||||
}
|
||||
openingColl.one[Opening]($doc(
|
||||
Opening.BSONFields.id $nin ids,
|
||||
Opening.BSONFields.rating $gt
|
||||
(user.perfs.opening.intRating - tolerance) $lt
|
||||
(user.perfs.opening.intRating + tolerance)
|
||||
)) flatMap {
|
||||
case Some(opening) => fuccess(opening)
|
||||
case None => if ((tolerance + toleranceStep) <= toleranceMax)
|
||||
tryRange(user, tolerance + toleranceStep, ids)
|
||||
else fufail(s"Can't find a opening for user $user!")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,9 +2,8 @@ package lila.opening
|
|||
|
||||
import play.api.libs.json._
|
||||
import reactivemongo.bson._
|
||||
import reactivemongo.bson.Macros
|
||||
|
||||
import lila.db.Types.Coll
|
||||
import lila.db.dsl._
|
||||
import lila.rating.Glicko
|
||||
import lila.user.User
|
||||
|
||||
|
|
|
@ -4,8 +4,7 @@ import akka.actor.ActorRef
|
|||
import play.api.libs.iteratee._
|
||||
|
||||
import lila.db.dsl._
|
||||
import lila.db.Implicits._
|
||||
import lila.game.{ Game, Pov, Query }
|
||||
import lila.game.{ Game, GameRepo, Pov, Query }
|
||||
import lila.hub.Sequencer
|
||||
import lila.rating.PerfType
|
||||
import lila.user.User
|
||||
|
@ -21,16 +20,12 @@ final class PerfStatIndexer(storage: PerfStatStorage, sequencer: ActorRef) {
|
|||
}
|
||||
|
||||
private def compute(user: User, perfType: PerfType): Funit = {
|
||||
import lila.game.tube.gameTube
|
||||
import lila.game.BSONHandlers.gameBSONHandler
|
||||
pimpQB($query {
|
||||
GameRepo.sortedCursor(
|
||||
Query.user(user.id) ++
|
||||
Query.finished ++
|
||||
Query.turnsMoreThan(2) ++
|
||||
Query.variant(PerfType variantOf perfType)
|
||||
|
||||
}).sort(Query.sortChronological)
|
||||
.cursor[Game]()
|
||||
Query.variant(PerfType variantOf perfType),
|
||||
Query.sortChronological)
|
||||
.enumerate(Int.MaxValue, stopOnError = true) |>>>
|
||||
Iteratee.fold[Game, PerfStat](PerfStat.init(user.id, perfType)) {
|
||||
case (perfStat, game) if game.perfType.contains(perfType) =>
|
||||
|
|
|
@ -2,17 +2,13 @@ package lila.perfStat
|
|||
|
||||
import org.joda.time.DateTime
|
||||
import reactivemongo.bson._
|
||||
import reactivemongo.bson.Macros
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import lila.db.BSON._
|
||||
import lila.db.Types.Coll
|
||||
import lila.db.dsl._
|
||||
import lila.rating.PerfType
|
||||
|
||||
final class PerfStatStorage(coll: Coll) {
|
||||
|
||||
import lila.db.BSON.BSONJodaDateTimeHandler
|
||||
import reactivemongo.bson.Macros
|
||||
implicit val PerfTypeBSONHandler = new BSONHandler[BSONInteger, PerfType] {
|
||||
def read(b: BSONInteger) = PerfType.byId get b.value err s"Invalid perf type id ${b.value}"
|
||||
def write(p: PerfType) = BSONInteger(p.id)
|
||||
|
@ -33,10 +29,10 @@ final class PerfStatStorage(coll: Coll) {
|
|||
private implicit val PerfStatBSONHandler = Macros.handler[PerfStat]
|
||||
|
||||
def find(userId: String, perfType: PerfType): Fu[Option[PerfStat]] =
|
||||
coll.find(BSONDocument("_id" -> PerfStat.makeId(userId, perfType))).one[PerfStat]
|
||||
coll.byId[PerfStat](PerfStat.makeId(userId, perfType))
|
||||
|
||||
def update(perfStat: PerfStat): Funit =
|
||||
coll.update(BSONDocument("_id" -> perfStat.id), perfStat).void
|
||||
coll.update($id(perfStat.id), perfStat).void
|
||||
|
||||
def insert(perfStat: PerfStat): Funit =
|
||||
coll.insert(perfStat).void
|
||||
|
|
|
@ -5,10 +5,8 @@ import scala.concurrent.duration._
|
|||
import akka.actor.{ ActorSelection, Scheduler }
|
||||
import akka.pattern.ask
|
||||
import org.joda.time.DateTime
|
||||
import reactivemongo.bson.BSONDocument
|
||||
|
||||
import lila.db.BSON.BSONJodaDateTimeHandler
|
||||
import lila.db.Types.Coll
|
||||
import lila.db.dsl._
|
||||
|
||||
private[puzzle] final class Daily(
|
||||
coll: Coll,
|
||||
|
@ -45,15 +43,15 @@ private[puzzle] final class Daily(
|
|||
}
|
||||
|
||||
private def findCurrent = coll.find(
|
||||
BSONDocument("day" -> BSONDocument("$gt" -> DateTime.now.minusMinutes(24 * 60 - 15)))
|
||||
$doc("day" -> $doc("$gt" -> DateTime.now.minusMinutes(24 * 60 - 15)))
|
||||
).one[Puzzle]
|
||||
|
||||
private def findNew = coll.find(
|
||||
BSONDocument("day" -> BSONDocument("$exists" -> false))
|
||||
).sort(BSONDocument("vote.sum" -> -1)).one[Puzzle] flatMap {
|
||||
$doc("day" -> $doc("$exists" -> false))
|
||||
).sort($doc("vote.sum" -> -1)).one[Puzzle] flatMap {
|
||||
case Some(puzzle) => coll.update(
|
||||
BSONDocument("_id" -> puzzle.id),
|
||||
BSONDocument("$set" -> BSONDocument("day" -> DateTime.now))
|
||||
$doc("_id" -> puzzle.id),
|
||||
$doc("$set" -> $doc("day" -> DateTime.now))
|
||||
) inject puzzle.some
|
||||
case None => fuccess(none)
|
||||
}
|
||||
|
|
|
@ -2,9 +2,8 @@ package lila.puzzle
|
|||
|
||||
import org.goochjs.glicko2._
|
||||
import org.joda.time.DateTime
|
||||
import reactivemongo.bson.{ BSONDocument, BSONInteger }
|
||||
|
||||
import lila.db.Types.Coll
|
||||
import lila.db.dsl._
|
||||
import lila.rating.{ Glicko, Perf }
|
||||
import lila.user.{ User, UserRepo }
|
||||
|
||||
|
@ -38,13 +37,13 @@ private[puzzle] final class Finisher(
|
|||
userRatingDiff = userPerf.intRating - user.perfs.puzzle.intRating)
|
||||
((api.attempt add a) >> {
|
||||
puzzleColl.update(
|
||||
BSONDocument("_id" -> puzzle.id),
|
||||
BSONDocument("$inc" -> BSONDocument(
|
||||
Puzzle.BSONFields.attempts -> BSONInteger(1),
|
||||
Puzzle.BSONFields.wins -> BSONInteger(data.isWin ? 1 | 0)
|
||||
)) ++ BSONDocument("$set" -> BSONDocument(
|
||||
$id(puzzle.id),
|
||||
$inc(
|
||||
Puzzle.BSONFields.attempts -> $int(1),
|
||||
Puzzle.BSONFields.wins -> $int(data.isWin ? 1 | 0)
|
||||
) ++ $set(
|
||||
Puzzle.BSONFields.perf -> Perf.perfBSONHandler.write(puzzlePerf)
|
||||
))) zip UserRepo.setPerf(user.id, "puzzle", userPerf)
|
||||
)) zip UserRepo.setPerf(user.id, "puzzle", userPerf)
|
||||
}) recover lila.db.recoverDuplicateKey(_ => ()) inject (a -> none)
|
||||
}
|
||||
|
||||
|
|
|
@ -5,9 +5,9 @@ import scala.util.{ Try, Success, Failure }
|
|||
import org.joda.time.DateTime
|
||||
import play.api.libs.json.JsValue
|
||||
import reactivemongo.api.collections.bson.BSONBatchCommands.AggregationFramework._
|
||||
import reactivemongo.bson.{ BSONDocument, BSONInteger, BSONRegex, BSONArray, BSONBoolean }
|
||||
import reactivemongo.bson. BSONArray
|
||||
|
||||
import lila.db.Types.Coll
|
||||
import lila.db.dsl._
|
||||
import lila.user.{ User, UserRepo }
|
||||
|
||||
private[puzzle] final class PuzzleApi(
|
||||
|
@ -20,11 +20,11 @@ private[puzzle] final class PuzzleApi(
|
|||
object puzzle {
|
||||
|
||||
def find(id: PuzzleId): Fu[Option[Puzzle]] =
|
||||
puzzleColl.find(BSONDocument("_id" -> id)).one[Puzzle]
|
||||
puzzleColl.find($doc("_id" -> id)).one[Puzzle]
|
||||
|
||||
def latest(nb: Int): Fu[List[Puzzle]] =
|
||||
puzzleColl.find(BSONDocument())
|
||||
.sort(BSONDocument("date" -> -1))
|
||||
puzzleColl.find($empty)
|
||||
.sort($doc("date" -> -1))
|
||||
.cursor[Puzzle]()
|
||||
.collect[List](nb)
|
||||
|
||||
|
@ -43,8 +43,8 @@ private[puzzle] final class PuzzleApi(
|
|||
case Success(puzzle) :: rest => lila.db.Util findNextId puzzleColl flatMap { id =>
|
||||
val p = puzzle(id)
|
||||
val fenStart = p.fen.split(' ').take(2).mkString(" ")
|
||||
puzzleColl.count(BSONDocument(
|
||||
"fen" -> BSONRegex(fenStart.replace("/", "\\/"), "")
|
||||
puzzleColl.count($doc(
|
||||
"fen".$regex(fenStart.replace("/", "\\/"), "")
|
||||
).some) flatMap {
|
||||
case 0 => (puzzleColl insert p) >> {
|
||||
insertPuzzles(rest) map (Success(id) :: _)
|
||||
|
@ -55,22 +55,22 @@ private[puzzle] final class PuzzleApi(
|
|||
}
|
||||
|
||||
def export(nb: Int): Fu[List[Puzzle]] = List(true, false).map { mate =>
|
||||
puzzleColl.find(BSONDocument("mate" -> mate))
|
||||
.sort(BSONDocument(Puzzle.BSONFields.voteSum -> -1))
|
||||
puzzleColl.find($doc("mate" -> mate))
|
||||
.sort($doc(Puzzle.BSONFields.voteSum -> -1))
|
||||
.cursor[Puzzle]().collect[List](nb / 2)
|
||||
}.sequenceFu.map(_.flatten)
|
||||
|
||||
def disable(id: PuzzleId): Funit =
|
||||
puzzleColl.update(
|
||||
BSONDocument("_id" -> id),
|
||||
BSONDocument("$set" -> BSONDocument(Puzzle.BSONFields.vote -> Vote.disable))
|
||||
$doc("_id" -> id),
|
||||
$doc("$set" -> $doc(Puzzle.BSONFields.vote -> Vote.disable))
|
||||
).void
|
||||
}
|
||||
|
||||
object attempt {
|
||||
|
||||
def find(puzzleId: PuzzleId, userId: String): Fu[Option[Attempt]] =
|
||||
attemptColl.find(BSONDocument(
|
||||
attemptColl.find($doc(
|
||||
Attempt.BSONFields.id -> Attempt.makeId(puzzleId, userId)
|
||||
)).one[Attempt]
|
||||
|
||||
|
@ -83,11 +83,11 @@ private[puzzle] final class PuzzleApi(
|
|||
}
|
||||
val a2 = a1.copy(vote = v.some)
|
||||
attemptColl.update(
|
||||
BSONDocument("_id" -> a2.id),
|
||||
BSONDocument("$set" -> BSONDocument(Attempt.BSONFields.vote -> v))) zip
|
||||
$doc("_id" -> a2.id),
|
||||
$doc("$set" -> $doc(Attempt.BSONFields.vote -> v))) zip
|
||||
puzzleColl.update(
|
||||
BSONDocument("_id" -> p2.id),
|
||||
BSONDocument("$set" -> BSONDocument(Puzzle.BSONFields.vote -> p2.vote))) map {
|
||||
$doc("_id" -> p2.id),
|
||||
$doc("$set" -> $doc(Puzzle.BSONFields.vote -> p2.vote))) map {
|
||||
case _ => p2 -> a2
|
||||
}
|
||||
}
|
||||
|
@ -95,22 +95,22 @@ private[puzzle] final class PuzzleApi(
|
|||
def add(a: Attempt) = attemptColl insert a void
|
||||
|
||||
def hasPlayed(user: User, puzzle: Puzzle): Fu[Boolean] =
|
||||
attemptColl.count(BSONDocument(
|
||||
attemptColl.count($doc(
|
||||
Attempt.BSONFields.id -> Attempt.makeId(puzzle.id, user.id)
|
||||
).some) map (0!=)
|
||||
|
||||
def playedIds(user: User, max: Int): Fu[BSONArray] =
|
||||
attemptColl.distinct(Attempt.BSONFields.puzzleId,
|
||||
BSONDocument(Attempt.BSONFields.userId -> user.id).some
|
||||
$doc(Attempt.BSONFields.userId -> user.id).some
|
||||
) map BSONArray.apply
|
||||
|
||||
def hasVoted(user: User): Fu[Boolean] = attemptColl.find(
|
||||
BSONDocument(Attempt.BSONFields.userId -> user.id),
|
||||
BSONDocument(
|
||||
$doc(Attempt.BSONFields.userId -> user.id),
|
||||
$doc(
|
||||
Attempt.BSONFields.vote -> true,
|
||||
Attempt.BSONFields.id -> false
|
||||
)).sort(BSONDocument(Attempt.BSONFields.date -> -1))
|
||||
.cursor[BSONDocument]()
|
||||
)).sort($doc(Attempt.BSONFields.date -> -1))
|
||||
.cursor[Bdoc]()
|
||||
.collect[List](5) map {
|
||||
case attempts if attempts.size < 5 => true
|
||||
case attempts => attempts.foldLeft(false) {
|
||||
|
|
|
@ -3,10 +3,7 @@ package lila.puzzle
|
|||
import scala.concurrent.duration._
|
||||
import scala.util.Random
|
||||
|
||||
import reactivemongo.api.QueryOpts
|
||||
import reactivemongo.bson.{ BSONDocument, BSONInteger, BSONArray }
|
||||
|
||||
import lila.db.Types.Coll
|
||||
import lila.db.dsl._
|
||||
import lila.user.User
|
||||
|
||||
private[puzzle] final class Selector(
|
||||
|
@ -15,10 +12,10 @@ private[puzzle] final class Selector(
|
|||
anonMinRating: Int,
|
||||
maxAttempts: Int) {
|
||||
|
||||
private def popularSelector(mate: Boolean) = BSONDocument(
|
||||
Puzzle.BSONFields.voteSum -> BSONDocument("$gt" -> BSONInteger(mate.fold(anonMinRating, 0))))
|
||||
private def popularSelector(mate: Boolean) = $doc(
|
||||
Puzzle.BSONFields.voteSum $gt mate.fold(anonMinRating, 0))
|
||||
|
||||
private def mateSelector(mate: Boolean) = BSONDocument("mate" -> mate)
|
||||
private def mateSelector(mate: Boolean) = $doc("mate" -> mate)
|
||||
|
||||
private def difficultyDecay(difficulty: Int) = difficulty match {
|
||||
case 1 => -200
|
||||
|
@ -36,7 +33,7 @@ private[puzzle] final class Selector(
|
|||
me match {
|
||||
case None =>
|
||||
puzzleColl.find(popularSelector(isMate) ++ mateSelector(isMate))
|
||||
.options(QueryOpts(skipN = Random nextInt anonSkipMax))
|
||||
.skip(Random nextInt anonSkipMax)
|
||||
.one[Puzzle]
|
||||
case Some(user) if user.perfs.puzzle.nb > maxAttempts => fuccess(none)
|
||||
case Some(user) =>
|
||||
|
@ -55,14 +52,13 @@ private[puzzle] final class Selector(
|
|||
case d => 200
|
||||
}
|
||||
|
||||
private def tryRange(rating: Int, tolerance: Int, step: Int, decay: Int, ids: BSONArray, isMate: Boolean): Fu[Option[Puzzle]] =
|
||||
puzzleColl.find(mateSelector(isMate) ++ BSONDocument(
|
||||
Puzzle.BSONFields.id -> BSONDocument("$nin" -> ids),
|
||||
Puzzle.BSONFields.rating -> BSONDocument(
|
||||
"$gt" -> BSONInteger(rating - tolerance + decay),
|
||||
"$lt" -> BSONInteger(rating + tolerance + decay)
|
||||
)
|
||||
)).sort(BSONDocument(Puzzle.BSONFields.voteSum -> -1))
|
||||
private def tryRange(rating: Int, tolerance: Int, step: Int, decay: Int, ids: Barr, isMate: Boolean): Fu[Option[Puzzle]] =
|
||||
puzzleColl.find(mateSelector(isMate) ++ $doc(
|
||||
Puzzle.BSONFields.id $nin ids,
|
||||
Puzzle.BSONFields.rating $gt
|
||||
(rating - tolerance + decay) $lt
|
||||
(rating + tolerance + decay)
|
||||
)).sort($sort desc Puzzle.BSONFields.voteSum)
|
||||
.one[Puzzle] flatMap {
|
||||
case None if (tolerance + step) <= toleranceMax =>
|
||||
tryRange(rating, tolerance + step, step, decay, ids, isMate)
|
||||
|
|
|
@ -3,7 +3,7 @@ package lila.puzzle
|
|||
import play.api.libs.json._
|
||||
import reactivemongo.bson._
|
||||
|
||||
import lila.db.Types.Coll
|
||||
import lila.db.dsl._
|
||||
import lila.rating.Glicko
|
||||
import lila.user.User
|
||||
|
||||
|
|
|
@ -6,18 +6,19 @@ import reactivemongo.bson._
|
|||
|
||||
import lila.common.{ LilaCookie, LilaException }
|
||||
import lila.db.dsl._
|
||||
import lila.db.Implicits._
|
||||
import lila.game.Game
|
||||
import lila.user.User
|
||||
import tube.anonConfigTube
|
||||
|
||||
private[setup] object AnonConfigRepo {
|
||||
|
||||
// dirty
|
||||
private val coll = Env.current.anonConfigColl
|
||||
|
||||
def update(req: RequestHeader)(f: UserConfig => UserConfig): Funit =
|
||||
configOption(req) flatMap {
|
||||
_ ?? { config =>
|
||||
anonConfigTube.coll.update(
|
||||
BSONDocument("_id" -> config.id),
|
||||
coll.update(
|
||||
$doc("_id" -> config.id),
|
||||
f(config),
|
||||
upsert = true).void
|
||||
}
|
||||
|
@ -27,8 +28,8 @@ private[setup] object AnonConfigRepo {
|
|||
configOption(req) map (_ | UserConfig.default("nocookie"))
|
||||
|
||||
def config(sid: String): Fu[UserConfig] =
|
||||
$find byId sid recover {
|
||||
case e: LilaException => {
|
||||
coll.byId[UserConfig](sid) recover {
|
||||
case e: Exception => {
|
||||
logger.warn("Can't load config", e)
|
||||
none[UserConfig]
|
||||
}
|
||||
|
@ -38,12 +39,7 @@ private[setup] object AnonConfigRepo {
|
|||
sessionId(req).??(s => config(s) map (_.some))
|
||||
|
||||
def filter(req: RequestHeader): Fu[FilterConfig] = sessionId(req) ?? { sid =>
|
||||
anonConfigTube.coll.find(
|
||||
BSONDocument("_id" -> sid),
|
||||
BSONDocument("filter" -> true)
|
||||
).one[BSONDocument] map {
|
||||
_ flatMap (_.getAs[FilterConfig]("filter"))
|
||||
}
|
||||
coll.primitiveOne[FilterConfig]($id(sid), "filter")
|
||||
} map (_ | FilterConfig.default)
|
||||
|
||||
private def sessionId(req: RequestHeader): Option[String] =
|
||||
|
|
|
@ -74,6 +74,4 @@ object FilterConfig {
|
|||
"s" -> o.speed.map(_.id),
|
||||
"e" -> o.ratingRange.toString)
|
||||
}
|
||||
|
||||
private[setup] val tube = lila.db.BsTube(filterConfigBSONHandler)
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ import lila.lobby.Color
|
|||
import lila.user.UserContext
|
||||
import play.api.data._
|
||||
import play.api.data.Forms._
|
||||
import tube.{ userConfigTube, anonConfigTube }
|
||||
|
||||
private[setup] final class FormFactory(casualOnly: Boolean) {
|
||||
|
||||
|
|
|
@ -12,7 +12,6 @@ import lila.lobby.actorApi.{ AddHook, AddSeek }
|
|||
import lila.lobby.Hook
|
||||
import lila.user.{ User, UserContext }
|
||||
import makeTimeout.short
|
||||
import tube.{ userConfigTube, anonConfigTube }
|
||||
|
||||
private[setup] final class Processor(
|
||||
lobby: ActorSelection,
|
||||
|
|
|
@ -29,7 +29,8 @@ private[setup] object UserConfig {
|
|||
hook = HookConfig.default,
|
||||
filter = FilterConfig.default)
|
||||
|
||||
import lila.db.{ BsTube, BSON }
|
||||
import lila.db.BSON
|
||||
import lila.db.dsl._
|
||||
import reactivemongo.bson._
|
||||
import AiConfig.aiConfigBSONHandler
|
||||
import FriendConfig.friendConfigBSONHandler
|
||||
|
@ -52,6 +53,4 @@ private[setup] object UserConfig {
|
|||
"hook" -> o.hook,
|
||||
"filter" -> o.filter)
|
||||
}
|
||||
|
||||
private[setup] val tube = BsTube(userConfigBSONHandler)
|
||||
}
|
||||
|
|
|
@ -5,34 +5,30 @@ import reactivemongo.bson._
|
|||
|
||||
import lila.common.LilaException
|
||||
import lila.db.dsl._
|
||||
import lila.db.Implicits._
|
||||
import lila.game.Game
|
||||
import lila.user.User
|
||||
import tube.userConfigTube
|
||||
|
||||
private[setup] object UserConfigRepo {
|
||||
|
||||
// dirty
|
||||
private val coll = Env.current.userConfigColl
|
||||
|
||||
def update(user: User)(f: UserConfig => UserConfig): Funit =
|
||||
config(user) flatMap { config =>
|
||||
userConfigTube.coll.update(
|
||||
BSONDocument("_id" -> config.id),
|
||||
coll.update(
|
||||
$doc("_id" -> config.id),
|
||||
f(config),
|
||||
upsert = true).void
|
||||
}
|
||||
|
||||
def config(user: User): Fu[UserConfig] =
|
||||
$find byId user.id recover {
|
||||
case e: LilaException => {
|
||||
coll.byId[UserConfig](user.id) recover {
|
||||
case e: Exception => {
|
||||
logger.warn("Can't load config", e)
|
||||
none[UserConfig]
|
||||
}
|
||||
} map (_ | UserConfig.default(user.id))
|
||||
|
||||
def filter(user: User): Fu[FilterConfig] =
|
||||
userConfigTube.coll.find(
|
||||
BSONDocument("_id" -> user.id),
|
||||
BSONDocument("filter" -> true)
|
||||
).one[BSONDocument] map {
|
||||
_ flatMap (_.getAs[FilterConfig]("filter")) getOrElse FilterConfig.default
|
||||
}
|
||||
coll.primitiveOne[FilterConfig]($id(user.id), "filter") map (_ | FilterConfig.default)
|
||||
}
|
||||
|
|
|
@ -4,14 +4,5 @@ import lila.socket.WithSocket
|
|||
|
||||
package object setup extends PackageObject with WithPlay with WithSocket {
|
||||
|
||||
object tube {
|
||||
|
||||
private[setup] implicit lazy val userConfigTube =
|
||||
UserConfig.tube inColl Env.current.userConfigColl
|
||||
|
||||
private[setup] implicit lazy val anonConfigTube =
|
||||
UserConfig.tube inColl Env.current.anonConfigColl
|
||||
}
|
||||
|
||||
private[setup] def logger = lila.log("setup")
|
||||
}
|
||||
|
|
|
@ -6,8 +6,8 @@ import reactivemongo.core.commands._
|
|||
import scala.concurrent.duration._
|
||||
|
||||
import lila.common.paginator._
|
||||
import lila.db.paginator.BSONAdapter
|
||||
import lila.db.Types.Coll
|
||||
import lila.db.dsl._
|
||||
import lila.db.paginator.Adapter
|
||||
import lila.memo.AsyncCache
|
||||
import lila.user.{ User, UserRepo }
|
||||
|
||||
|
@ -41,16 +41,16 @@ private[video] final class VideoApi(
|
|||
private val maxPerPage = 18
|
||||
|
||||
def find(id: Video.ID): Fu[Option[Video]] =
|
||||
videoColl.find(BSONDocument("_id" -> id)).one[Video]
|
||||
videoColl.find($doc("_id" -> id)).one[Video]
|
||||
|
||||
def search(user: Option[User], query: String, page: Int): Fu[Paginator[VideoView]] = {
|
||||
val q = query.split(' ').map { word => s""""$word"""" } mkString " "
|
||||
val textScore = BSONDocument("score" -> BSONDocument("$meta" -> "textScore"))
|
||||
val textScore = $doc("score" -> $doc("$meta" -> "textScore"))
|
||||
Paginator(
|
||||
adapter = new BSONAdapter[Video](
|
||||
adapter = new Adapter[Video](
|
||||
collection = videoColl,
|
||||
selector = BSONDocument(
|
||||
"$text" -> BSONDocument("$search" -> q)
|
||||
selector = $doc(
|
||||
"$text" -> $doc("$search" -> q)
|
||||
),
|
||||
projection = textScore,
|
||||
sort = textScore
|
||||
|
@ -61,19 +61,19 @@ private[video] final class VideoApi(
|
|||
|
||||
def save(video: Video): Funit =
|
||||
videoColl.update(
|
||||
BSONDocument("_id" -> video.id),
|
||||
BSONDocument("$set" -> video),
|
||||
$doc("_id" -> video.id),
|
||||
$doc("$set" -> video),
|
||||
upsert = true).void
|
||||
|
||||
def removeNotIn(ids: List[Video.ID]) =
|
||||
videoColl.remove(
|
||||
BSONDocument("_id" -> BSONDocument("$nin" -> ids))
|
||||
$doc("_id" -> $doc("$nin" -> ids))
|
||||
).void
|
||||
|
||||
def setMetadata(id: Video.ID, metadata: Youtube.Metadata) =
|
||||
videoColl.update(
|
||||
BSONDocument("_id" -> id),
|
||||
BSONDocument("$set" -> BSONDocument("metadata" -> metadata)),
|
||||
$doc("_id" -> id),
|
||||
$doc("$set" -> $doc("metadata" -> metadata)),
|
||||
upsert = false
|
||||
).void
|
||||
|
||||
|
@ -81,11 +81,11 @@ private[video] final class VideoApi(
|
|||
videoColl.distinct("_id", none) map lila.db.BSON.asStrings
|
||||
|
||||
def popular(user: Option[User], page: Int): Fu[Paginator[VideoView]] = Paginator(
|
||||
adapter = new BSONAdapter[Video](
|
||||
adapter = new Adapter[Video](
|
||||
collection = videoColl,
|
||||
selector = BSONDocument(),
|
||||
projection = BSONDocument(),
|
||||
sort = BSONDocument("metadata.likes" -> -1)
|
||||
selector = $empty,
|
||||
projection = $empty,
|
||||
sort = $doc("metadata.likes" -> -1)
|
||||
) mapFutureList videoViews(user),
|
||||
currentPage = page,
|
||||
maxPerPage = maxPerPage)
|
||||
|
@ -93,35 +93,31 @@ private[video] final class VideoApi(
|
|||
def byTags(user: Option[User], tags: List[Tag], page: Int): Fu[Paginator[VideoView]] =
|
||||
if (tags.isEmpty) popular(user, page)
|
||||
else Paginator(
|
||||
adapter = new BSONAdapter[Video](
|
||||
adapter = new Adapter[Video](
|
||||
collection = videoColl,
|
||||
selector = BSONDocument(
|
||||
"tags" -> BSONDocument("$all" -> tags)
|
||||
),
|
||||
projection = BSONDocument(),
|
||||
sort = BSONDocument("metadata.likes" -> -1)
|
||||
selector = $doc("tags" $all tags),
|
||||
projection = $empty,
|
||||
sort = $doc("metadata.likes" -> -1)
|
||||
) mapFutureList videoViews(user),
|
||||
currentPage = page,
|
||||
maxPerPage = maxPerPage)
|
||||
|
||||
def byAuthor(user: Option[User], author: String, page: Int): Fu[Paginator[VideoView]] =
|
||||
Paginator(
|
||||
adapter = new BSONAdapter[Video](
|
||||
adapter = new Adapter[Video](
|
||||
collection = videoColl,
|
||||
selector = BSONDocument(
|
||||
"author" -> author
|
||||
),
|
||||
projection = BSONDocument(),
|
||||
sort = BSONDocument("metadata.likes" -> -1)
|
||||
selector = $doc("author" -> author),
|
||||
projection = $empty,
|
||||
sort = $doc("metadata.likes" -> -1)
|
||||
) mapFutureList videoViews(user),
|
||||
currentPage = page,
|
||||
maxPerPage = maxPerPage)
|
||||
|
||||
def similar(user: Option[User], video: Video, max: Int): Fu[Seq[VideoView]] =
|
||||
videoColl.find(BSONDocument(
|
||||
"tags" -> BSONDocument("$in" -> video.tags),
|
||||
"_id" -> BSONDocument("$ne" -> video.id)
|
||||
)).sort(BSONDocument("metadata.likes" -> -1))
|
||||
videoColl.find($doc(
|
||||
"tags" $in video.tags,
|
||||
"_id" $ne video.id
|
||||
)).sort($doc("metadata.likes" -> -1))
|
||||
.cursor[Video]()
|
||||
.collect[List]().map { videos =>
|
||||
videos.sortBy { v => -v.similarity(video) } take max
|
||||
|
@ -142,7 +138,7 @@ private[video] final class VideoApi(
|
|||
object view {
|
||||
|
||||
def find(videoId: Video.ID, userId: String): Fu[Option[View]] =
|
||||
viewColl.find(BSONDocument(
|
||||
viewColl.find($doc(
|
||||
View.BSONFields.id -> View.makeId(videoId, userId)
|
||||
)).one[View]
|
||||
|
||||
|
@ -150,17 +146,15 @@ private[video] final class VideoApi(
|
|||
lila.db.recoverDuplicateKey(_ => ())
|
||||
|
||||
def hasSeen(user: User, video: Video): Fu[Boolean] =
|
||||
viewColl.count(BSONDocument(
|
||||
viewColl.count($doc(
|
||||
View.BSONFields.id -> View.makeId(video.id, user.id)
|
||||
).some) map (0!=)
|
||||
|
||||
def seenVideoIds(user: User, videos: Seq[Video]): Fu[Set[Video.ID]] =
|
||||
viewColl.distinct(View.BSONFields.videoId,
|
||||
BSONDocument(
|
||||
"_id" -> BSONDocument("$in" -> videos.map { v =>
|
||||
View.makeId(v.id, user.id)
|
||||
})
|
||||
).some) map lila.db.BSON.asStringSet
|
||||
$inIds(videos.map { v =>
|
||||
View.makeId(v.id, user.id)
|
||||
}).some) map lila.db.BSON.asStringSet
|
||||
}
|
||||
|
||||
object tag {
|
||||
|
@ -182,8 +176,8 @@ private[video] final class VideoApi(
|
|||
tags.filterNot(_.isNumeric)
|
||||
}
|
||||
else videoColl.aggregate(
|
||||
Match(BSONDocument("tags" -> BSONDocument("$all" -> filterTags))),
|
||||
List(Project(BSONDocument("tags" -> BSONBoolean(true))),
|
||||
Match($doc("tags" $all filterTags)),
|
||||
List(Project($doc("tags" -> true)),
|
||||
Unwind("tags"), GroupField("tags")("nb" -> SumValue(1)))).map(
|
||||
_.documents.flatMap(_.asOpt[TagNb]))
|
||||
|
||||
|
@ -208,7 +202,7 @@ private[video] final class VideoApi(
|
|||
|
||||
private val popularCache = AsyncCache.single[List[TagNb]](
|
||||
f = videoColl.aggregate(
|
||||
Project(BSONDocument("tags" -> BSONBoolean(true))), List(
|
||||
Project($doc("tags" -> true)), List(
|
||||
Unwind("tags"), GroupField("tags")("nb" -> SumValue(1)),
|
||||
Sort(Descending("nb")))).map(
|
||||
_.documents.flatMap(_.asOpt[TagNb])),
|
||||
|
|
Loading…
Reference in New Issue