migration WIP

rm0193-mapreduce
Thibault Duplessis 2019-12-01 10:46:36 -06:00
parent b23e862015
commit 29a411ad38
31 changed files with 246 additions and 313 deletions

View File

@ -33,12 +33,12 @@ final class BookmarkApi(
}
def removeByGameId(gameId: Game.ID): Funit =
coll.remove($doc("g" -> gameId)).void
coll.delete.one($doc("g" -> gameId)).void
def removeByGameIds(gameIds: List[Game.ID]): Funit =
coll.remove($doc("g" $in gameIds)).void
coll.delete.one($doc("g" $in gameIds)).void
def remove(gameId: Game.ID, userId: User.ID): Funit = coll.remove(selectId(gameId, userId)).void
def remove(gameId: Game.ID, userId: User.ID): Funit = coll.delete.one(selectId(gameId, userId)).void
// def remove(selector: Bdoc): Funit = coll.remove(selector).void
def toggle(gameId: Game.ID, userId: User.ID): Funit =
@ -54,7 +54,7 @@ final class BookmarkApi(
paginator.byUser(user, page) map2 { (b: Bookmark) => b.game }
private def add(gameId: Game.ID, userId: User.ID, date: DateTime): Funit =
coll.insert($doc(
coll.insert.one($doc(
"_id" -> makeId(gameId, userId),
"g" -> gameId,
"u" -> userId,

View File

@ -69,7 +69,7 @@ object ChatTimeout {
val all: List[Reason] = List(PublicShaming, Insult, Spam, Other)
def apply(key: String) = all.find(_.key == key)
}
implicit val ReasonBSONHandler: BSONHandler[Reason] = lila.db.BSON.tryHandler[Reason](
implicit val ReasonBSONHandler: BSONHandler[Reason] = tryHandler[Reason](
{ case BSONString(value) => Reason(value) toTry s"Invalid reason ${value}" },
x => BSONString(x.key)
)

View File

@ -42,18 +42,18 @@ object Line {
val textMaxSize = 140
val titleSep = '~'
import reactivemongo.api.bson.{ BSONHandler, BSONString }
import reactivemongo.api.bson._
private val invalidLine = UserLine("", None, "[invalid character]", troll = false, deleted = true)
private[chat] implicit val userLineBSONHandler = lila.db.BSON.quickHandler[UserLine](
{ case BSONString(value) => strToUserLine(value) getOrElse invalidLine },
x => BSONString(userLineToStr(x))
private[chat] implicit val userLineBSONHandler = BSONStringHandler.as[UserLine](
v => strToUserLine(v) getOrElse invalidLine,
userLineToStr
)
private[chat] implicit val lineBSONHandler = lila.db.BSON.quickHandler[Line](
{ case BSONString(value) => strToLine(value) getOrElse invalidLine },
x => BSONString(lineToStr(x))
private[chat] implicit val lineBSONHandler = BSONStringHandler.as[Line](
v => strToLine(v) getOrElse invalidLine,
lineToStr
)
private val UserLineRegex = """(?s)([\w-~]{2,}+)([ !?])(.++)""".r

View File

@ -4,7 +4,6 @@ import org.joda.time.DateTime
import ornicar.scalalib.Zero
import reactivemongo.api.bson._
import reactivemongo.api.bson.compat._
import reactivemongo.api.bson.exceptions.TypeDoesNotMatchException
import scala.util.{ Try, Success, Failure }
import dsl._
@ -42,91 +41,6 @@ abstract class BSONReadOnly[T] extends BSONDocumentReader[T] {
object BSON extends Handlers {
// def toDocHandler[A](implicit handler: BSONHandler[A]): BSONDocumentHandler[A] =
// new BSONDocumentReader[A] with BSONDocumentWriter[A] with BSONHandler[A] {
// def read(doc: BSONDocument) = handler read doc
// def write(o: A) = handler write o
// }
// object MapDocument {
// implicit def MapReader[K, V](implicit kIso: Iso.StringIso[K], vr: BSONDocumentReader[V]): BSONDocumentReader[Map[K, V]] = new BSONDocumentReader[Map[K, V]] {
// def read(bson: Bdoc): Map[K, V] = {
// // mutable optimized implementation
// val b = Map.newBuilder[K, V]
// for (tuple <- bson.elements)
// // assume that all values in the document are Bdocs
// b += (kIso.from(tuple.name) -> vr.read(tuple.value.asInstanceOf[Bdoc]))
// b.result
// }
// }
// implicit def MapWriter[K, V](implicit kIso: Iso.StringIso[K], vw: reactivemongo.api.bson.BSONDocumentWriter[V]): BSONDocumentWriter[Map[K, V]] = new BSONDocumentWriter[Map[K, V]] {
// def write(map: Map[K, V]): Bdoc = BSONDocument {
// map.map { tuple =>
// kIso.to(tuple._1) -> vw.writeTry(tuple._2).get
// }
// }
// }
// implicit def MapHandler[K: Iso.StringIso, V: BSONDocumentHandler]: BSONHandler[Map[K, V]] = new BSONHandler[Map[K, V]] {
// private val reader = MapReader[K, V]
// private val writer = MapWriter[K, V]
// def read(bson: Bdoc): Map[K, V] = reader read bson
// def write(map: Map[K, V]): Bdoc = writer write map
// }
// }
// object MapValue {
// implicit def MapReader[K, V](implicit kIso: Iso.StringIso[K], vr: BSONReader[V]): BSONDocumentReader[Map[K, V]] = new BSONDocumentReader[Map[K, V]] {
// def read(bson: Bdoc): Map[K, V] = {
// val valueReader = vr.asInstanceOf[BSONReader[V]]
// // mutable optimized implementation
// val b = Map.newBuilder[K, V]
// for (tuple <- bson.elements) b += (kIso.from(tuple.name) -> valueReader.read(tuple.value))
// b.result
// }
// }
// implicit def MapWriter[K, V](implicit kIso: Iso.StringIso[K], vw: BSONWriter[V]): BSONDocumentWriter[Map[K, V]] = new BSONDocumentWriter[Map[K, V]] {
// def write(map: Map[K, V]): Bdoc = BSONDocument {
// map.toStream.map { tuple =>
// kIso.to(tuple._1) -> vw.writeTry(tuple._2).get
// }
// }
// }
// implicit def MapHandler[K, V](implicit kIso: Iso.StringIso[K], vr: BSONReader[V], vw: BSONWriter[V]): BSONHandler[Map[K, V]] = new BSONHandler[Map[K, V]] {
// private val reader = MapReader[K, V]
// private val writer = MapWriter[K, V]
// def read(bson: Bdoc): Map[K, V] = reader read bson
// def write(map: Map[K, V]): Bdoc = writer write map
// }
// }
def quickHandler[T](read: PartialFunction[BSONValue, T], write: T => BSONValue): BSONHandler[T] = new BSONHandler[T] {
def readTry(bson: BSONValue) = read.andThen(Success(_)).applyOrElse(
bson,
(b: BSONValue) => handlerBadType(b)
)
def writeTry(t: T) = Success(write(t))
}
def tryHandler[T](read: PartialFunction[BSONValue, Try[T]], write: T => BSONValue): BSONHandler[T] = new BSONHandler[T] {
def readTry(bson: BSONValue) = read.applyOrElse(
bson,
(b: BSONValue) => handlerBadType(b)
)
def writeTry(t: T) = Success(write(t))
}
def handlerBadType[T](b: BSONValue): Try[T] =
Failure(TypeDoesNotMatchException("BSONBinary", b.getClass.getSimpleName))
def handlerBadValue[T](msg: String): Try[T] =
Failure(new IllegalArgumentException(msg))
final class Reader(val doc: Bdoc) {
val map = {

View File

@ -24,7 +24,7 @@ object ByteArray {
def fromHexStr(hexStr: String): Try[ByteArray] =
Try(ByteArray(hex str2Hex hexStr))
implicit val ByteArrayBSONHandler = lila.db.BSON.quickHandler[ByteArray](
implicit val ByteArrayBSONHandler = dsl.quickHandler[ByteArray](
{ case v: BSONBinary => ByteArray(v.byteArray) },
v => BSONBinary(v.value, subtype)
)

View File

@ -13,9 +13,9 @@ import dsl.Coll
import lila.common.Chronometer
import lila.common.config._
case class DbConfig(
uri: String,
@ConfigName("image.collection") imageCollName: Option[CollName] = None
class DbConfig(
val uri: String,
@ConfigName("image.collection") val imageCollName: Option[CollName] = None
)
final class Env(name: String, config: DbConfig) {

View File

@ -2,6 +2,7 @@ package lila.db
import org.joda.time.DateTime
import reactivemongo.api.bson._
import reactivemongo.api.bson.exceptions.TypeDoesNotMatchException
import scala.util.{ Try, Success, Failure }
import scalaz.NonEmptyList
@ -10,7 +11,7 @@ import lila.common.{ Iso, IpAddress, EmailAddress, NormalizedEmailAddress }
trait Handlers {
implicit val BSONJodaDateTimeHandler = lila.db.BSON.quickHandler[DateTime](
implicit val BSONJodaDateTimeHandler = dsl.quickHandler[DateTime](
{ case v: BSONDateTime => new DateTime(v.value) },
v => BSONDateTime(v.getMillis)
)
@ -36,11 +37,33 @@ trait Handlers {
def dateIsoHandler[A](implicit iso: Iso[DateTime, A]): BSONHandler[A] = isoHandler[A, DateTime](iso)
implicit def bsonArrayToListHandler[T](implicit handler: BSONHandler[T]): BSONHandler[List[T]] =
lila.db.BSON.quickHandler[List[T]](
{ case BSONArray(values) => values.view.flatMap(handler.readOpt).to(List) },
repr => BSONArray(repr.flatMap(handler.writeOpt))
def quickHandler[T](read: PartialFunction[BSONValue, T], write: T => BSONValue): BSONHandler[T] = new BSONHandler[T] {
def readTry(bson: BSONValue) = read.andThen(Success(_)).applyOrElse(
bson,
(b: BSONValue) => handlerBadType(b)
)
def writeTry(t: T) = Success(write(t))
}
def tryHandler[T](read: PartialFunction[BSONValue, Try[T]], write: T => BSONValue): BSONHandler[T] = new BSONHandler[T] {
def readTry(bson: BSONValue) = read.applyOrElse(
bson,
(b: BSONValue) => handlerBadType(b)
)
def writeTry(t: T) = Success(write(t))
}
def handlerBadType[T](b: BSONValue): Try[T] =
Failure(TypeDoesNotMatchException("BSONBinary", b.getClass.getSimpleName))
def handlerBadValue[T](msg: String): Try[T] =
Failure(new IllegalArgumentException(msg))
// implicit def bsonArrayToListHandler[T](implicit handler: BSONHandler[T]): BSONHandler[List[T]] =
// dsl.quickHandler[List[T]](
// { case BSONArray(values) => values.view.flatMap(handler.readOpt).to(List) },
// repr => BSONArray(repr.flatMap(handler.writeOpt))
// )
// implicit def bsonArrayToVectorHandler[T](implicit handler: BSONHandler[T]): BSONHandler[Vector[T]] = new BSONHandler[Vector[T]] {
// def read(array: BSONArray) = array.values.view.flatMap(handler.readOpt).to(Vector)

View File

@ -49,7 +49,7 @@ private object BSONHandlers {
})
}
implicit val EntryIdHandler = lila.db.BSON.tryHandler[Id](
implicit val EntryIdHandler = tryHandler[Id](
{
case BSONString(value) =>
value split ':' match {

View File

@ -1,5 +1,7 @@
package lila.evalCache
import scala.math.Ordering.Float.TotalOrdering
import EvalCacheEntry._
/**

View File

@ -20,12 +20,12 @@ object BSONHandlers {
def writeTry(cc: CheckCount) = Success(BSONArray(cc.white, cc.black))
}
implicit val StatusBSONHandler = lila.db.BSON.tryHandler[Status](
implicit val StatusBSONHandler = tryHandler[Status](
{ case BSONInteger(v) => Status(v) toTry s"No such status: $v" },
x => BSONInteger(x.id)
)
private[game] implicit val unmovedRooksHandler = lila.db.BSON.tryHandler[UnmovedRooks](
private[game] implicit val unmovedRooksHandler = tryHandler[UnmovedRooks](
{ case bin: BSONBinary => ByteArrayBSONHandler.readTry(bin) map BinaryFormat.unmovedRooks.read },
x => ByteArrayBSONHandler.writeTry(BinaryFormat.unmovedRooks write x).get
)

View File

@ -44,7 +44,7 @@ object Blurs {
import reactivemongo.api.bson._
private[game] implicit val BlursBitsBSONHandler = lila.db.BSON.tryHandler[Bits](
private[game] implicit val BlursBitsBSONHandler = lila.db.dsl.tryHandler[Bits](
{
case BSONInteger(bits) => Success(Bits(bits & 0xffffffffL))
case BSONLong(bits) => Success(Bits(bits))

View File

@ -1,5 +1,6 @@
package lila.notify
import chess.Color
import lila.db.BSON.{ Reader, Writer }
import lila.db.dsl._
import lila.db.{ dsl, BSON }
@ -52,10 +53,7 @@ private object BSONHandlers {
implicit val IrwinDoneHandler = Macros.handler[IrwinDone]
implicit val GenericLinkHandler = Macros.handler[GenericLink]
implicit val ColorBSONHandler = lila.db.BSON.quickHandler[chess.Color](
{ case BSONBoolean(v) => chess.Color(v) },
c => BSONBoolean(c.white)
)
implicit val ColorBSONHandler = BSONBooleanHandler.as[Color](Color.apply, _.white)
implicit val NotificationContentHandler = new BSON[NotificationContent] {

View File

@ -58,7 +58,7 @@ object OAuthScope {
import reactivemongo.api.bson._
import lila.db.dsl._
private[oauth] implicit val scopeHandler = lila.db.BSON.tryHandler[OAuthScope](
private[oauth] implicit val scopeHandler = tryHandler[OAuthScope](
{ case b: BSONString => OAuthScope.byKey.get(b.value) toTry s"No such scope: ${b.value}" },
s => BSONString(s.key)
)

View File

@ -47,14 +47,14 @@ private[puzzle] final class Daily(
none
}
private def findCurrent = coll.find(
private def findCurrent = coll.ext.find(
$doc(F.day $gt DateTime.now.minusMinutes(24 * 60 - 15))
).uno[Puzzle]
private def findNew = coll.find(
private def findNew = coll.ext.find(
$doc(F.day $exists false, F.voteNb $gte 200)
).sort($doc(F.voteRatio -> -1)).uno[Puzzle] flatMap {
case Some(puzzle) => coll.update(
case Some(puzzle) => coll.update.one(
$id(puzzle.id),
$set(F.day -> DateTime.now)
) inject puzzle.some

View File

@ -1,50 +1,57 @@
package lila.puzzle
import akka.actor.{ ActorSelection, ActorSystem }
import com.typesafe.config.Config
import com.softwaremill.macwire._
import io.methvin.play.autoconfig._
import scala.concurrent.duration.FiniteDuration
import play.api.Configuration
import lila.common.config._
import lila.db.Env.configLoader
@Module
private class CoachConfig(
val mongodb: lila.db.DbConfig,
@ConfigName("collection.puzzle") val puzzleColl: CollName,
@ConfigName("collection.round") val roundColl: CollName,
@ConfigName("collection.vote") val voteColl: CollName,
@ConfigName("collection.head") val headColl: CollName,
@ConfigName("api.token") val apiToken: Secret,
@ConfigName("animation.duration") val animationDuration: FiniteDuration,
@ConfigName("selector.puzzle_id_min") val puzzleIdMin: Int
)
final class Env(
config: Config,
appConfig: Configuration,
renderer: ActorSelection,
historyApi: lila.history.HistoryApi,
lightUserApi: lila.user.LightUserApi,
asyncCache: lila.memo.AsyncCache.Builder,
system: ActorSystem,
lifecycle: play.api.inject.ApplicationLifecycle
) {
gameRepo: lila.game.GameRepo,
userRepo: lila.user.UserRepo
)(implicit system: ActorSystem) {
private val settings = new {
val CollectionPuzzle = config getString "collection.puzzle"
val CollectionRound = config getString "collection.round"
val CollectionVote = config getString "collection.vote"
val CollectionHead = config getString "collection.head"
val ApiToken = config getString "api.token"
val AnimationDuration = config duration "animation.duration"
val PuzzleIdMin = config getInt "selector.puzzle_id_min"
}
import settings._
private val config = appConfig.get[CoachConfig]("coach")(AutoConfig.loader)
private val db = new lila.db.Env("puzzle", config getConfig "mongodb", lifecycle)
private lazy val db = new lila.db.Env("puzzle", config.mongodb)
private lazy val gameJson = new GameJson(asyncCache, lightUserApi)
private lazy val gameJson = wire[GameJson]
lazy val jsonView = new JsonView(
gameJson,
animationDuration = AnimationDuration
)
lazy val jsonView = wire[JsonView]
lazy val api = new PuzzleApi(
puzzleColl = puzzleColl,
roundColl = roundColl,
voteColl = voteColl,
headColl = headColl,
puzzleIdMin = PuzzleIdMin,
puzzleIdMin = config.puzzleIdMin,
asyncCache = asyncCache,
apiToken = ApiToken
apiToken = config.apiToken
)
lazy val finisher = new Finisher(
historyApi = historyApi,
userRepo = userRepo,
api = api,
puzzleColl = puzzleColl
)
@ -52,14 +59,14 @@ final class Env(
lazy val selector = new Selector(
puzzleColl = puzzleColl,
api = api,
puzzleIdMin = PuzzleIdMin
puzzleIdMin = config.puzzleIdMin
)
lazy val batch = new PuzzleBatch(
puzzleColl = puzzleColl,
api = api,
finisher = finisher,
puzzleIdMin = PuzzleIdMin
puzzleIdMin = config.puzzleIdMin
)
lazy val userInfos = new UserInfosApi(
@ -89,21 +96,8 @@ final class Env(
}
}
private[puzzle] lazy val puzzleColl = db(CollectionPuzzle)
private[puzzle] lazy val roundColl = db(CollectionRound)
private[puzzle] lazy val voteColl = db(CollectionVote)
private[puzzle] lazy val headColl = db(CollectionHead)
}
object Env {
lazy val current: Env = "puzzle" boot new Env(
config = lila.common.PlayApp loadConfig "puzzle",
renderer = lila.hub.Env.current.renderer,
historyApi = lila.history.Env.current.api,
lightUserApi = lila.user.Env.current.lightUserApi,
asyncCache = lila.memo.Env.current.asyncCache,
system = lila.common.PlayApp.system,
lifecycle = lila.common.PlayApp.lifecycle
)
private lazy val puzzleColl = db(config.puzzleColl)
private lazy val roundColl = db(config.roundColl)
private lazy val voteColl = db(config.voteColl)
private lazy val headColl = db(config.headColl)
}

View File

@ -1,16 +1,17 @@
package lila.puzzle
import org.goochjs.glicko2._
import org.goochjs.glicko2.{ Rating, RatingCalculator, RatingPeriodResults }
import org.joda.time.DateTime
import chess.Mode
import lila.common.Bus
import lila.db.dsl._
import lila.rating.{ Glicko, PerfType }
import lila.common.Bus
import lila.user.{ User, UserRepo }
private[puzzle] final class Finisher(
api: PuzzleApi,
userRepo: UserRepo,
historyApi: lila.history.HistoryApi,
puzzleColl: Coll
) {
@ -35,11 +36,11 @@ private[puzzle] final class Finisher(
)
historyApi.addPuzzle(user = user, completedAt = date, perf = userPerf)
(api.round upsert round) >> {
puzzleColl.update(
puzzleColl.update.one(
$id(puzzle.id),
$inc(Puzzle.BSONFields.attempts -> $int(1)) ++
$set(Puzzle.BSONFields.perf -> PuzzlePerf.puzzlePerfBSONHandler.write(puzzlePerf))
) zip UserRepo.setPerf(user.id, PerfType.Puzzle, userPerf)
) zip userRepo.setPerf(user.id, PerfType.Puzzle, userPerf)
} inject {
Bus.publish(Puzzle.UserResult(puzzle.id, user.id, result, formerUserRating -> userPerf.intRating), "finishPuzzle")
round -> Mode.Rated
@ -77,7 +78,7 @@ private[puzzle] final class Finisher(
ratingDiff = userPerf.intRating - formerUserRating
)
(api.round add a) >>
UserRepo.setPerf(user.id, PerfType.Puzzle, userPerf) >>-
userRepo.setPerf(user.id, PerfType.Puzzle, userPerf) >>-
Bus.publish(
Puzzle.UserResult(puzzle.id, user.id, result, formerUserRating -> userPerf.intRating),
"finishPuzzle"

View File

@ -7,6 +7,7 @@ import lila.game.{ Game, GameRepo, PerfPicker }
import lila.tree.Node.{ partitionTreeJsonWriter, minimalNodeJsonWriter }
private final class GameJson(
gameRepo: GameRepo,
asyncCache: lila.memo.AsyncCache.Builder,
lightUserApi: lila.user.LightUserApi
) {
@ -28,7 +29,7 @@ private final class GameJson(
private def generate(ck: CacheKey): Fu[JsObject] = ck match {
case CacheKey(gameId, plies, onlyLast) =>
(GameRepo game gameId).flatten(s"Missing puzzle game $gameId!") flatMap {
gameRepo game gameId orFail s"Missing puzzle game $gameId!" flatMap {
generate(_, plies, onlyLast)
}
}

View File

@ -4,9 +4,11 @@ import play.api.libs.json._
import lila.game.GameRepo
import lila.tree
import lila.tree.Node.defaultNodeJsonWriter
final class JsonView(
gameJson: GameJson,
gameRepo: GameRepo,
animationDuration: scala.concurrent.duration.Duration
) {
@ -66,7 +68,7 @@ final class JsonView(
)
def batch(puzzles: List[Puzzle], userInfos: UserInfos): Fu[JsObject] = for {
games <- GameRepo.gameOptionsFromSecondary(puzzles.map(_.gameId))
games <- gameRepo.gameOptionsFromSecondary(puzzles.map(_.gameId))
jsons <- (puzzles zip games).collect {
case (puzzle, Some(game)) =>
gameJson.noCache(game, puzzle.initialPly, true) map { gameJson =>
@ -92,7 +94,7 @@ final class JsonView(
"lines" -> lila.puzzle.Line.toJson(puzzle.lines),
"vote" -> puzzle.vote.sum
).add("initialMove" -> isOldMobile.option(puzzle.initialMove.uci))
.add("branch" -> (!isOldMobile).option(makeBranch(puzzle)))
.add("branch" -> (!isOldMobile).??(makeBranch(puzzle)).map(defaultNodeJsonWriter.writes))
.add("enabled" -> puzzle.enabled)
private def makeBranch(puzzle: Puzzle): Option[tree.Branch] = {

View File

@ -1,10 +1,9 @@
package lila.puzzle
import scala.collection.breakOut
import chess.Color
import chess.format.{ Uci, Forsyth }
import org.joda.time.DateTime
import scala.util.{ Try, Success, Failure }
case class Puzzle(
id: PuzzleId,
@ -77,31 +76,29 @@ object Puzzle {
import reactivemongo.api.bson._
import lila.db.BSON
import BSON.BSONJodaDateTimeHandler
private implicit val lineBSONHandler = new BSONHandler[BSONDocument, Lines] {
private implicit val linesBSONHandler = new BSONDocumentReader[Lines] with BSONDocumentWriter[Lines] with BSONHandler[Lines] {
private def readMove(move: String) = chess.Pos.doublePiotrToKey(move take 2) match {
case Some(m) => s"$m${move drop 2}"
case _ => sys error s"Invalid piotr move notation: $move"
}
def read(doc: BSONDocument): Lines = doc.elements.map {
case BSONElement(move, BSONBoolean(true)) => Win(readMove(move))
case BSONElement(move, BSONBoolean(false)) => Retry(readMove(move))
case BSONElement(move, more: BSONDocument) =>
Node(readMove(move), read(more))
case BSONElement(move, value) =>
throw new Exception(s"Can't read value of $move: $value")
}(breakOut)
def readDocument(doc: BSONDocument): Try[Lines] = Try {
doc.elements.map {
case BSONElement(move, BSONBoolean(true)) => Win(readMove(move))
case BSONElement(move, BSONBoolean(false)) => Retry(readMove(move))
case BSONElement(move, more: BSONDocument) => Node(readMove(move), readDocument(more).get)
case BSONElement(move, value) =>
throw new Exception(s"Can't read value of $move: $value")
} to List
}
private def writeMove(move: String) = chess.Pos.doubleKeyToPiotr(move take 4) match {
case Some(m) => s"$m${move drop 4}"
case _ => sys error s"Invalid move notation: $move"
}
def write(lines: Lines): BSONDocument = BSONDocument(lines map {
def writeTry(lines: Lines): Try[BSONDocument] = Success(BSONDocument(lines map {
case Win(move) => writeMove(move) -> BSONBoolean(true)
case Retry(move) => writeMove(move) -> BSONBoolean(false)
case Node(move, lines) => writeMove(move) -> write(lines)
})
case Node(move, lines) => writeMove(move) -> writeTry(lines).get
}))
}
object BSONFields {

View File

@ -1,13 +1,11 @@
package lila.puzzle
import org.joda.time.DateTime
import play.api.libs.iteratee._
import play.api.libs.json._
import reactivemongo.api.ReadPreference
import reactivemongo.play.iteratees.cursorProducer
import scala.concurrent.duration._
import lila.common.MaxPerSecond
import lila.common.config.MaxPerSecond
import lila.db.dsl._
import lila.user.User
@ -16,66 +14,66 @@ final class PuzzleActivity(
roundColl: Coll
)(implicit system: akka.actor.ActorSystem) {
import PuzzleActivity._
import Round.RoundBSONHandler
// import PuzzleActivity._
// import Round.RoundBSONHandler
def stream(config: Config): Enumerator[String] = {
// def stream(config: Config): Enumerator[String] = {
val selector = $doc("_id" $startsWith config.user.id)
// val selector = $doc("_id" $startsWith config.user.id)
val query = roundColl.find(selector).sort($sort desc "_id")
// val query = roundColl.find(selector).sort($sort desc "_id")
val infinite = query.copy(options = query.options.batchSize(config.perSecond.value))
.cursor[Round](ReadPreference.secondaryPreferred)
.bulkEnumerator() &>
Enumeratee.mapM[Iterator[Round]].apply[Seq[JsObject]] { rounds =>
enrich(rounds.toSeq)
} &>
lila.common.Iteratee.delay(1 second) &>
Enumeratee.mapConcat(_.toSeq)
// val infinite = query.copy(options = query.options.batchSize(config.perSecond.value))
// .cursor[Round](ReadPreference.secondaryPreferred)
// .bulkEnumerator() &>
// Enumeratee.mapM[Iterator[Round]].apply[Seq[JsObject]] { rounds =>
// enrich(rounds.toSeq)
// } &>
// lila.common.Iteratee.delay(1 second) &>
// Enumeratee.mapConcat(_.toSeq)
val stream = config.max.fold(infinite) { max =>
// I couldn't figure out how to do it properly :( :( :(
// the nb can't be set as bulkEnumerator(nb)
// because games are further filtered after being fetched
var nb = 0
infinite &> Enumeratee.mapInput { in =>
nb = nb + 1
if (nb <= max) in
else Input.EOF
}
}
// val stream = config.max.fold(infinite) { max =>
// // I couldn't figure out how to do it properly :( :( :(
// // the nb can't be set as bulkEnumerator(nb)
// // because games are further filtered after being fetched
// var nb = 0
// infinite &> Enumeratee.mapInput { in =>
// nb = nb + 1
// if (nb <= max) in
// else Input.EOF
// }
// }
stream &> formatter
}
// stream &> formatter
// }
private def enrich(rounds: Seq[Round]): Fu[Seq[JsObject]] =
puzzleColl.primitiveMap[Int, Double](
ids = rounds.map(_.id.puzzleId).toSeq,
field = "perf.gl.r",
fieldExtractor = obj => for {
perf <- obj.getAs[Bdoc]("perf")
gl <- perf.getAs[Bdoc]("gl")
rating <- gl.getAs[Double]("r")
} yield rating
) map { ratings =>
rounds.toSeq flatMap { round =>
ratings get round.id.puzzleId map { puzzleRating =>
Json.obj(
"id" -> round.id.puzzleId,
"date" -> round.date,
"rating" -> round.rating,
"ratingDiff" -> round.ratingDiff,
"puzzleRating" -> puzzleRating.toInt
)
}
}
}
// private def enrich(rounds: Seq[Round]): Fu[Seq[JsObject]] =
// puzzleColl.primitiveMap[Int, Double](
// ids = rounds.map(_.id.puzzleId).toSeq,
// field = "perf.gl.r",
// fieldExtractor = obj => for {
// perf <- obj.getAs[Bdoc]("perf")
// gl <- perf.getAs[Bdoc]("gl")
// rating <- gl.getAs[Double]("r")
// } yield rating
// ) map { ratings =>
// rounds.toSeq flatMap { round =>
// ratings get round.id.puzzleId map { puzzleRating =>
// Json.obj(
// "id" -> round.id.puzzleId,
// "date" -> round.date,
// "rating" -> round.rating,
// "ratingDiff" -> round.ratingDiff,
// "puzzleRating" -> puzzleRating.toInt
// )
// }
// }
// }
private def formatter =
Enumeratee.map[JsObject].apply[String] { json =>
s"${Json.stringify(json)}\n"
}
// private def formatter =
// Enumeratee.map[JsObject].apply[String] { json =>
// s"${Json.stringify(json)}\n"
// }
}
object PuzzleActivity {

View File

@ -4,6 +4,7 @@ import scala.concurrent.duration._
import play.api.libs.json.JsValue
import lila.common.config.Secret
import lila.db.dsl._
import lila.user.User
import Puzzle.{ BSONFields => F }
@ -15,7 +16,7 @@ private[puzzle] final class PuzzleApi(
headColl: Coll,
puzzleIdMin: PuzzleId,
asyncCache: lila.memo.AsyncCache.Builder,
apiToken: String
apiToken: Secret
) {
import Puzzle.puzzleBSONHandler
@ -23,13 +24,13 @@ private[puzzle] final class PuzzleApi(
object puzzle {
def find(id: PuzzleId): Fu[Option[Puzzle]] =
puzzleColl.find($doc(F.id -> id)).uno[Puzzle]
puzzleColl.ext.find($doc(F.id -> id)).uno[Puzzle]
def findMany(ids: List[PuzzleId]): Fu[List[Option[Puzzle]]] =
puzzleColl.optionsByOrderedIds[Puzzle, PuzzleId](ids)(_.id)
def latest(nb: Int): Fu[List[Puzzle]] =
puzzleColl.find($empty)
puzzleColl.ext.find($empty)
.sort($doc(F.date -> -1))
.cursor[Puzzle]()
.gather[List](nb)
@ -41,13 +42,13 @@ private[puzzle] final class PuzzleApi(
)
def export(nb: Int): Fu[List[Puzzle]] = List(true, false).map { mate =>
puzzleColl.find($doc(F.mate -> mate))
puzzleColl.ext.find($doc(F.mate -> mate))
.sort($doc(F.voteRatio -> -1))
.cursor[Puzzle]().gather[List](nb / 2)
}.sequenceFu.map(_.flatten)
def disable(id: PuzzleId): Funit =
puzzleColl.update(
puzzleColl.update.one(
$id(id),
$doc("$set" -> $doc(F.vote -> AggregateVote.disable))
).void
@ -55,11 +56,11 @@ private[puzzle] final class PuzzleApi(
object round {
def add(a: Round) = roundColl insert a
def add(a: Round) = roundColl.insert.one(a)
def upsert(a: Round) = roundColl.update($id(a.id), a, upsert = true)
def upsert(a: Round) = roundColl.update.one($id(a.id), a, upsert = true)
def reset(user: User) = roundColl.remove($doc(
def reset(user: User) = roundColl.delete.one($doc(
Round.BSONFields.id $startsWith s"${user.id}:"
))
}
@ -84,12 +85,12 @@ private[puzzle] final class PuzzleApi(
Vote(Vote.makeId(id, user.id), v)
)
}
voteColl.update(
voteColl.update.one(
$id(v2.id),
$set("v" -> v),
upsert = true
) zip
puzzleColl.update(
puzzleColl.update.one(
$id(p2.id),
$set(F.vote -> p2.vote)
) map {
@ -102,7 +103,7 @@ private[puzzle] final class PuzzleApi(
def find(user: User): Fu[Option[PuzzleHead]] = headColl.byId[PuzzleHead](user.id)
def set(h: PuzzleHead) = headColl.update($id(h.id), h, upsert = true) void
def set(h: PuzzleHead) = headColl.update.one($id(h.id), h, upsert = true) void
def addNew(user: User, puzzleId: PuzzleId) = set(PuzzleHead(user.id, puzzleId.some, puzzleId))

View File

@ -28,6 +28,7 @@ object Round {
import reactivemongo.api.bson._
import lila.db.BSON
import lila.db.dsl._
import scala.util.{ Try, Success, Failure }
import BSON.BSONJodaDateTimeHandler
private implicit val ResultBSONHandler = booleanAnyValHandler[Result](_.win, Result.apply)
@ -41,17 +42,19 @@ object Round {
def encode(puzzleId: PuzzleId) = puzzleId + shiftValue
def decode(puzzleId: PuzzleId) = puzzleId - shiftValue
implicit val roundIdHandler: BSONHandler[BSONString, Id] = new BSONHandler[BSONString, Id] {
private val sep = ':'
def read(bs: BSONString) = bs.value split sep match {
case Array(userId, puzzleId) => Id(userId, decode(Integer parseInt puzzleId))
case _ => sys error s"Invalid puzzle round id ${bs.value}"
}
def write(id: Id) = {
private val idSep = ':'
implicit val roundIdHandler = tryHandler[Id](
{
case BSONString(v) => v split idSep match {
case Array(userId, puzzleId) => Success(Id(userId, decode(Integer parseInt puzzleId)))
case _ => handlerBadValue(s"Invalid puzzle round id $v")
}
},
id => {
val puzzleId = "%05d".format(encode(id.puzzleId))
BSONString(s"${id.userId}$sep$puzzleId")
BSONString(s"${id.userId}$idSep$puzzleId")
}
}
)
implicit val RoundBSONHandler = new BSON[Round] {

View File

@ -46,7 +46,7 @@ private[puzzle] final class Selector(
}
}
}
}.mon(_.puzzle.selector.time) flattenWith NoPuzzlesAvailableException addEffect { puzzle =>
}.mon(_.puzzle.selector.time) orFailWith NoPuzzlesAvailableException addEffect { puzzle =>
if (puzzle.vote.sum < -1000)
logger.info(s"Select #${puzzle.id} vote.sum: ${puzzle.vote.sum} for ${me.fold("Anon")(_.username)} (${me.fold("?")(_.perfs.puzzle.intRating.toString)})")
else

View File

@ -40,6 +40,6 @@ object TreeBuilder {
}
}
private val logChessError = (id: String) => (err: String) =>
logger.warn(s"TreeBuilder https://lichess.org/$id ${err.lines.toList.headOption}")
private val logChessError = (id: Game.ID) => (err: String) =>
logger.warn(s"TreeBuilder https://lichess.org/$id ${err.linesIterator.toList.headOption}")
}

View File

@ -29,17 +29,16 @@ final class Env(
)(implicit system: ActorSystem, scheduler: Scheduler) {
private val config = appConfig.get[SecurityConfig]("security")(SecurityConfig.loader)
import config._
import net.baseUrl
import config.net.baseUrl
// val recaptchaPublicConfig = recaptcha.public
lazy val firewall = new Firewall(
coll = db(collection.firewall),
coll = db(config.collection.firewall),
scheduler = scheduler
)
lazy val flood = new Flood(config.floodDuration)
lazy val flood = wire[Flood]
lazy val recaptcha: Recaptcha =
if (config.recaptchaC.enabled) wire[RecaptchaGoogle]
@ -51,11 +50,11 @@ final class Env(
lazy val userSpyApi = wire[UserSpyApi]
lazy val store = new Store(db(collection.security))
lazy val store = new Store(db(config.collection.security))
lazy val ipIntel = {
def mk = (email: EmailAddress) => wire[IpIntel]
mk(ipIntelEmail)
mk(config.ipIntelEmail)
}
lazy val ugcArmedSetting = settingStore[Boolean](
@ -64,7 +63,7 @@ final class Env(
text = "Enable the user garbage collector".some
)
lazy val printBan = new PrintBan(db(collection.printBan))
lazy val printBan = new PrintBan(db(config.collection.printBan))
lazy val garbageCollector = {
def mk: (() => Boolean) => GarbageCollector = isArmed => wire[GarbageCollector]
@ -84,20 +83,20 @@ final class Env(
lazy val passwordReset = {
def mk = (s: Secret) => wire[PasswordReset]
mk(passwordResetSecret)
mk(config.passwordResetSecret)
}
lazy val magicLink = {
def mk = (s: Secret) => wire[MagicLink]
mk(passwordResetSecret)
mk(config.passwordResetSecret)
}
lazy val emailChange = {
def mk = (s: Secret) => wire[EmailChange]
mk(emailChangeSecret)
mk(config.emailChangeSecret)
}
lazy val loginToken = new LoginToken(loginTokenSecret, userRepo)
lazy val loginToken = new LoginToken(config.loginTokenSecret, userRepo)
lazy val automaticEmail = wire[AutomaticEmail]
@ -109,7 +108,7 @@ final class Env(
private lazy val disposableEmailDomain = new DisposableEmailDomain(
ws = ws,
providerUrl = disposableEmail.providerUrl,
providerUrl = config.disposableEmail.providerUrl,
checkMailBlocked = () => checkMail.fetchAllBlocked
)
@ -124,7 +123,7 @@ final class Env(
lazy val spam = new Spam(spamKeywordsSetting.get)
scheduler.scheduleOnce(30 seconds)(disposableEmailDomain.refresh)
scheduler.scheduleWithFixedDelay(disposableEmail.refreshDelay, disposableEmail.refreshDelay) {
scheduler.scheduleWithFixedDelay(config.disposableEmail.refreshDelay, config.disposableEmail.refreshDelay) {
() => disposableEmailDomain.refresh
}
@ -138,7 +137,7 @@ final class Env(
lazy val api = wire[SecurityApi]
lazy val csrfRequestHandler = new CSRFRequestHandler(net)
lazy val csrfRequestHandler = wire[CSRFRequestHandler]
def cli = wire[Cli]

View File

@ -10,22 +10,23 @@ import lila.common.EmailAddress
import SecurityConfig._
private case class SecurityConfig(
collection: Collection,
@ConfigName("flood.duration") floodDuration: FiniteDuration,
@ConfigName("geoip") geoIP: GeoIP.Config,
@ConfigName("password_reset.secret") passwordResetSecret: Secret,
@ConfigName("email_config") emailConfirm: EmailConfirm,
@ConfigName("email_change.secret") emailChangeSecret: Secret,
@ConfigName("login_token.secret") loginTokenSecret: Secret,
tor: Tor,
@ConfigName("disposable_email") disposableEmail: DisposableEmail,
@ConfigName("dns_api") dnsApi: DnsApi,
@ConfigName("check_mail_api") checkMail: CheckMail,
recaptchaC: Recaptcha.Config,
mailgun: Mailgun.Config,
net: NetConfig,
@ConfigName("ipintel.email") ipIntelEmail: EmailAddress
@Module
private class SecurityConfig(
val collection: Collection,
@ConfigName("flood.duration") val floodDuration: FiniteDuration,
@ConfigName("geoip") val geoIP: GeoIP.Config,
@ConfigName("password_reset.secret") val passwordResetSecret: Secret,
@ConfigName("email_config") val emailConfirm: EmailConfirm,
@ConfigName("email_change.secret") val emailChangeSecret: Secret,
@ConfigName("login_token.secret") val loginTokenSecret: Secret,
val tor: Tor,
@ConfigName("disposable_email") val disposableEmail: DisposableEmail,
@ConfigName("dns_api") val dnsApi: DnsApi,
@ConfigName("check_mail_api") val checkMail: CheckMail,
val recaptchaC: Recaptcha.Config,
val mailgun: Mailgun.Config,
val net: NetConfig,
@ConfigName("ipintel.email") val ipIntelEmail: EmailAddress
)
private object SecurityConfig {

View File

@ -18,7 +18,7 @@ object PublicLine {
import reactivemongo.api.bson._
import lila.db.dsl._
private implicit val SourceHandler = lila.db.BSON.tryHandler[Source](
private implicit val SourceHandler = lila.db.dsl.tryHandler[Source](
{
case BSONString(v) => v split ':' match {
case Array("t", id) => Success(Source.Tournament(id))
@ -38,7 +38,7 @@ object PublicLine {
private val objectHandler = Macros.handler[PublicLine]
implicit val PublicLineBSONHandler = lila.db.BSON.tryHandler[PublicLine](
implicit val PublicLineBSONHandler = lila.db.dsl.tryHandler[PublicLine](
{
case doc: BSONDocument => objectHandler readTry doc
case BSONString(text) => Success(PublicLine(text, none, none))

View File

@ -32,7 +32,7 @@ final class StreamerApi(
def findOrInit(user: User): Fu[Option[Streamer.WithUser]] =
find(user) orElse {
val s = Streamer.WithUser(Streamer make user, user)
coll insert s.streamer inject s.some
coll.insert.one(s.streamer) inject s.some
}
def withUser(s: Stream): Fu[Option[Streamer.WithUserAndStream]] =
@ -48,14 +48,14 @@ final class StreamerApi(
def setSeenAt(user: User): Funit =
listedIdsCache.get flatMap { ids =>
ids.contains(Streamer.Id(user.id)) ??
coll.update($id(user.id), $set("seenAt" -> DateTime.now)).void
coll.update.one($id(user.id), $set("seenAt" -> DateTime.now)).void
}
def setLiveNow(ids: List[Streamer.Id]): Funit =
coll.update($doc("_id" $in ids), $set("liveAt" -> DateTime.now), multi = true).void
coll.update.one($doc("_id" $in ids), $set("liveAt" -> DateTime.now), multi = true).void
private[streamer] def mostRecentlySeenIds(ids: List[Streamer.Id], max: Int): Fu[Set[Streamer.Id]] =
coll.find($inIds(ids))
coll.ext.find($inIds(ids))
.sort($doc("seenAt" -> -1))
.list[Bdoc](max) map {
_ flatMap {
@ -65,7 +65,7 @@ final class StreamerApi(
def update(prev: Streamer, data: StreamerForm.UserData, asMod: Boolean): Fu[Streamer.ModChange] = {
val streamer = data(prev, asMod)
coll.update($id(streamer.id), streamer) >>-
coll.update.one($id(streamer.id), streamer) >>-
listedIdsCache.refresh inject {
val modChange = Streamer.ModChange(
list = prev.approval.granted != streamer.approval.granted option streamer.approval.granted,
@ -88,7 +88,7 @@ final class StreamerApi(
}
def demote(userId: User.ID): Funit =
coll.update(
coll.update.one(
$id(userId),
$set(
"approval.requested" -> false,
@ -99,22 +99,22 @@ final class StreamerApi(
def create(u: User): Funit =
isStreamer(u) flatMap { exists =>
!exists ?? coll.insert(Streamer make u).void
!exists ?? coll.insert.one(Streamer make u).void
}
def isStreamer(user: User): Fu[Boolean] = listedIdsCache.get.dmap(_ contains Streamer.Id(user.id))
def uploadPicture(s: Streamer, picture: Photographer.Uploaded): Funit =
photographer(s.id.value, picture).flatMap { pic =>
coll.update($id(s.id), $set("picturePath" -> pic.path)).void
coll.update.one($id(s.id), $set("picturePath" -> pic.path)).void
}
def deletePicture(s: Streamer): Funit =
coll.update($id(s.id), $unset("picturePath")).void
coll.update.one($id(s.id), $unset("picturePath")).void
// unapprove after a week if you never streamed
def autoDemoteFakes: Funit =
coll.update(
coll.update.one(
$doc(
"liveAt" $exists false,
"approval.granted" -> true,

View File

@ -78,7 +78,7 @@ object Authenticator {
F.sha512 -> true
)
private[user] implicit val HashedPasswordBsonHandler = lila.db.BSON.quickHandler[HashedPassword](
private[user] implicit val HashedPasswordBsonHandler = quickHandler[HashedPassword](
{ case v: BSONBinary => HashedPassword(v.byteArray) },
v => BSONBinary(v.bytes, Subtype.GenericBinarySubtype)
)

View File

@ -56,7 +56,7 @@ object TotpSecret {
TotpSecret(secret)
}
private[user] val totpSecretBSONHandler = lila.db.BSON.quickHandler[TotpSecret](
private[user] val totpSecretBSONHandler = lila.db.dsl.quickHandler[TotpSecret](
{ case v: BSONBinary => TotpSecret(v.byteArray) },
v => BSONBinary(v.secret, Subtype.GenericBinarySubtype)
)

View File

@ -8,7 +8,8 @@ import reactivemongo.api.bson._
import scala.concurrent.duration._
final class TrophyApi(
coll: Coll, kindColl: Coll
coll: Coll,
kindColl: Coll
)(implicit system: akka.actor.ActorSystem) {
private val trophyKindObjectBSONHandler = Macros.handler[TrophyKind]
@ -22,10 +23,8 @@ final class TrophyApi(
logger = logger
)
private implicit val trophyKindStringBSONHandler = lila.db.BSON.quickHandler[TrophyKind](
{ case BSONString(str) => kindCache sync str },
x => BSONString(x._id)
)
private implicit val trophyKindStringBSONHandler =
BSONStringHandler.as[TrophyKind](kindCache.sync, _._id)
private implicit val trophyBSONHandler = Macros.handler[Trophy]