diff --git a/app/Env.scala b/app/Env.scala index 1ef48733d1..778f42c34a 100644 --- a/app/Env.scala +++ b/app/Env.scala @@ -44,7 +44,8 @@ final class Env( isDonor = Env.donation.isDonor, isHostingSimul = Env.simul.isHosting, isStreamer = Env.tv.isStreamer.apply, - insightShare = Env.insight.share) _ + insightShare = Env.insight.share, + getPlayTime = Env.game.playTime.apply) _ system.actorOf(Props(new actor.Renderer), name = RendererName) diff --git a/app/Global.scala b/app/Global.scala index 86a6c23ac8..80c94828c8 100644 --- a/app/Global.scala +++ b/app/Global.scala @@ -1,51 +1,10 @@ package lila.app -import lila.common.HTTPRequest -import play.api.mvc._ -import play.api.mvc.Results._ -import play.api.{ Application, GlobalSettings, Mode } +import play.api.{ Application, GlobalSettings } object Global extends GlobalSettings { override def onStart(app: Application) { - kamon.Kamon.start() - lila.app.Env.current + lila.app.Env.current // preload modules } - - override def onStop(app: Application) { - kamon.Kamon.shutdown() - } - - override def onRouteRequest(req: RequestHeader): Option[Handler] = { - lila.mon.http.request.all() - Env.i18n.requestHandler(req) orElse super.onRouteRequest(req) - } - - private def niceError(req: RequestHeader): Boolean = - req.method == "GET" && - HTTPRequest.isSynchronousHttp(req) && - !HTTPRequest.hasFileExtension(req) - - override def onHandlerNotFound(req: RequestHeader) = - if (niceError(req)) controllers.Main.notFound(req) - else fuccess(NotFound("404 - Resource not found")) - - override def onBadRequest(req: RequestHeader, error: String) = - if (error startsWith "Illegal character in path") fuccess(Redirect("/")) - else if (error startsWith "Cannot parse parameter") onHandlerNotFound(req) - else if (niceError(req)) { - lila.mon.http.response.code400() - controllers.Lobby.handleStatus(req, Results.BadRequest) - } - else fuccess(BadRequest(error)) - - override def onError(req: RequestHeader, ex: Throwable) = - if (niceError(req)) { - if (lila.common.PlayApp.isProd) { - lila.mon.http.response.code500() - fuccess(InternalServerError(views.html.base.errorPage(ex)(lila.api.Context(req)))) - } - else super.onError(req, ex) - } - else fuccess(InternalServerError(ex.getMessage)) } diff --git a/app/LilaPlayStuff.scala b/app/LilaPlayStuff.scala new file mode 100644 index 0000000000..8d96cca9c8 --- /dev/null +++ b/app/LilaPlayStuff.scala @@ -0,0 +1,76 @@ +package lila.app + +import com.google.inject.AbstractModule +import com.google.inject.name.Names +import javax.inject._ +import lila.common.HTTPRequest +import play.api._ +import play.api.http._ +import play.api.inject.ApplicationLifecycle +import play.api.mvc._ +import play.api.mvc.RequestHeader +import play.api.mvc.Results._ +import play.api.routing.Router +import scala.concurrent._ + +class LilaHttpRequestHandler @Inject() (errorHandler: HttpErrorHandler, + configuration: HttpConfiguration, filters: HttpFilters, + router: Router) extends DefaultHttpRequestHandler(router, errorHandler, configuration, filters) { + + override def routeRequest(req: RequestHeader) = { + lila.mon.http.request.all() + Env.i18n.requestHandler(req) orElse super.routeRequest(req) + } +} + +class LilaHttpErrorHandler @Inject() ( + env: Environment, + config: Configuration, + sourceMapper: OptionalSourceMapper, + router: Provider[Router]) extends DefaultHttpErrorHandler(env, config, sourceMapper, router) { + + private def niceError(req: RequestHeader): Boolean = + req.method == "GET" && + HTTPRequest.isSynchronousHttp(req) && + !HTTPRequest.hasFileExtension(req) + + def onHandlerNotFound(req: RequestHeader) = + if (niceError(req)) controllers.Main.notFound(req) + else fuccess(NotFound("404 - Resource not found")) + + override def onBadRequest(req: RequestHeader, error: String) = + if (error startsWith "Illegal character in path") fuccess(Redirect("/")) + else if (error startsWith "Cannot parse parameter") onHandlerNotFound(req) + else if (niceError(req)) { + lila.mon.http.response.code400() + controllers.Lobby.handleStatus(req, Results.BadRequest) + } + else fuccess(BadRequest(error)) + + override def onProdServerError(req: RequestHeader, ex: UsefulException): Future[Result] = { + lila.mon.http.response.code500() + fuccess(InternalServerError(views.html.base.errorPage(ex)(lila.api.Context(req)))) + } +} + +@Singleton +final class LilaLifecycle @Inject() (lifecycle: ApplicationLifecycle) { + + play.api.Logger("boot").info("Lifecycle bindings") + + lifecycle.addStopHook { () => + play.api.Logger("play").info("LilaLifecycle shutdown") + kamon.Kamon.shutdown() + funit + } +} + +final class LilaModule extends AbstractModule { + + play.api.Logger("boot").info("Kamon start") + kamon.Kamon.start() + + def configure() = { + bind(classOf[LilaLifecycle]).asEagerSingleton + } +} diff --git a/app/controllers/Account.scala b/app/controllers/Account.scala index 32c62f14ea..f88635e6f1 100644 --- a/app/controllers/Account.scala +++ b/app/controllers/Account.scala @@ -5,9 +5,7 @@ import play.api.mvc._, Results._ import lila.api.Context import lila.app._ import lila.common.LilaCookie -import lila.db.api.$find import lila.security.Permission -import lila.user.tube.userTube import lila.user.{ User => UserModel, UserRepo } import views._ diff --git a/app/controllers/Challenge.scala b/app/controllers/Challenge.scala index 83870d0274..4cd5664b53 100644 --- a/app/controllers/Challenge.scala +++ b/app/controllers/Challenge.scala @@ -137,7 +137,7 @@ object Challenge extends LilaController { } } - def websocket(id: String, apiVersion: Int) = SocketOption[JsValue] { implicit ctx => + def websocket(id: String, apiVersion: Int) = SocketOption { implicit ctx => env.api byId id flatMap { _ ?? { c => get("sri") ?? { uid => diff --git a/app/controllers/LilaController.scala b/app/controllers/LilaController.scala index 59e4fc300c..22f318dbf4 100644 --- a/app/controllers/LilaController.scala +++ b/app/controllers/LilaController.scala @@ -1,14 +1,12 @@ package controllers +import akka.stream.scaladsl._ import ornicar.scalalib.Zero import play.api.data.Form import play.api.http._ -import play.api.libs.iteratee.{ Iteratee, Enumerator } import play.api.libs.json.{ Json, JsValue, JsObject, JsArray, Writes } import play.api.mvc._, Results._ -import play.api.mvc.WebSocket.FrameFormatter import play.twirl.api.Html -import scalaz.Monoid import lila.api.{ PageData, Context, HeaderContext, BodyContext, TokenBucket } import lila.app._ @@ -48,23 +46,19 @@ private[controllers] trait LilaController CACHE_CONTROL -> "no-cache, no-store, must-revalidate", EXPIRES -> "0" ) - protected def Socket[A: FrameFormatter](f: Context => Fu[(Iteratee[A, _], Enumerator[A])]) = - WebSocket.tryAccept[A] { req => reqToCtx(req) flatMap f map scala.util.Right.apply } - - protected def SocketEither[A: FrameFormatter](f: Context => Fu[Either[Result, (Iteratee[A, _], Enumerator[A])]]) = - WebSocket.tryAccept[A] { req => reqToCtx(req) flatMap f } + protected def Socket(f: Context => Fu[JsFlow]) = + WebSocket.acceptOrResult[JsObject, JsObject] { req => + reqToCtx(req) flatMap f map Right.apply + } - protected def SocketOption[A: FrameFormatter](f: Context => Fu[Option[(Iteratee[A, _], Enumerator[A])]]) = - WebSocket.tryAccept[A] { req => - reqToCtx(req) flatMap f map { - case None => Left(NotFound(jsonError("socket resource not found"))) - case Some(pair) => Right(pair) - } + protected def SocketEither(f: Context => Fu[Either[Result, JsFlow]]) = + WebSocket.acceptOrResult[JsObject, JsObject] { req => + reqToCtx(req) flatMap f } - protected def SocketOptionLimited[A: FrameFormatter](consumer: TokenBucket.Consumer, name: String)(f: Context => Fu[Option[(Iteratee[A, _], Enumerator[A])]]) = - rateLimitedSocket[A](consumer, name) { ctx => - f(ctx) map { + protected def SocketOption(f: Context => Fu[Option[JsFlow]]): WebSocket = + WebSocket.acceptOrResult[JsObject, JsObject] { req => + reqToCtx(req) flatMap f map { case None => Left(NotFound(jsonError("socket resource not found"))) case Some(pair) => Right(pair) } diff --git a/app/controllers/LilaSocket.scala b/app/controllers/LilaSocket.scala index 10fe5b20d4..5d4fce0f0d 100644 --- a/app/controllers/LilaSocket.scala +++ b/app/controllers/LilaSocket.scala @@ -1,10 +1,11 @@ package controllers +import akka.stream.scaladsl._ import play.api.http._ -import play.api.libs.iteratee._ import play.api.libs.json._ import play.api.mvc._, Results._ -import play.api.mvc.WebSocket.FrameFormatter +import play.api.mvc.WebSocket.MessageFlowTransformer +import scala.concurrent.duration._ import lila.api.{ Context, TokenBucket } import lila.app._ @@ -12,36 +13,27 @@ import lila.common.HTTPRequest trait LilaSocket { self: LilaController => - private type AcceptType[A] = Context => Fu[Either[Result, (Iteratee[A, _], Enumerator[A])]] - - private val logger = lila.log("ratelimit") + protected implicit val jsonMessageFlowTransformer: MessageFlowTransformer[JsObject, JsObject] = { + import scala.util.control.NonFatal + import play.api.libs.streams.AkkaStreams + import websocket._ + def closeOnException[T](block: => Option[T]) = try { + Left(block getOrElse { + sys error "Not a JsObject" + }) + } + catch { + case NonFatal(e) => Right(CloseMessage(Some(CloseCodes.Unacceptable), + "Unable to parse json message")) + } - def rateLimitedSocket[A: FrameFormatter](consumer: TokenBucket.Consumer, name: String)(f: AcceptType[A]): WebSocket[A, A] = - WebSocket[A, A] { req => - reqToCtx(req) flatMap { ctx => - val ip = HTTPRequest lastRemoteAddress req - def userInfo = { - val sri = get("sri", req) | "none" - val username = ctx.usernameOrAnon - s"user:$username sri:$sri" - } - // logger.debug(s"socket:$name socket connect $ip $userInfo") - f(ctx).map { resultOrSocket => - resultOrSocket.right.map { - case (readIn, writeOut) => (e, i) => { - writeOut |>> i - e &> Enumeratee.mapInputM { in => - consumer(ip).map { credit => - if (credit >= 0) in - else { - logger.info(s"socket:$name socket close $ip $userInfo $in") - Input.EOF - } - } - } |>> readIn - } - } - } + new MessageFlowTransformer[JsObject, JsObject] { + def transform(flow: Flow[JsObject, JsObject, _]) = { + AkkaStreams.bypassWith[Message, JsObject, Message](Flow[Message].collect { + case BinaryMessage(data) => closeOnException(Json.parse(data.iterator.asInputStream).asOpt[JsObject]) + case TextMessage(text) => closeOnException(Json.parse(text).asOpt[JsObject]) + })(flow map { json => TextMessage(Json.stringify(json)) }) } } + } } diff --git a/app/controllers/Lobby.scala b/app/controllers/Lobby.scala index 24b2b1d4d3..fdcad1c7e9 100644 --- a/app/controllers/Lobby.scala +++ b/app/controllers/Lobby.scala @@ -48,12 +48,22 @@ object Lobby extends LilaController { size = 10, rate = 6) - def socket(apiVersion: Int) = SocketOptionLimited[JsValue](socketConsumer, "lobby") { implicit ctx => + import akka.stream.scaladsl.Flow + import scala.concurrent.duration._ + private val wsThrottler = Flow[JsObject].throttle( + elements = 10, + per = 5.second, + maximumBurst = 10, + mode = akka.stream.ThrottleMode.Enforcing) + + def socket(apiVersion: Int) = SocketOption { implicit ctx => get("sri") ?? { uid => Env.lobby.socketHandler( uid = uid, user = ctx.me, - mobile = getBool("mobile")) map some + mobile = getBool("mobile")) map { flow => + wsThrottler.via(flow).some + } } } diff --git a/app/controllers/Main.scala b/app/controllers/Main.scala index 266981cfbd..a702d58377 100644 --- a/app/controllers/Main.scala +++ b/app/controllers/Main.scala @@ -37,7 +37,7 @@ object Main extends LilaController { def websocket = SocketOption { implicit ctx => get("sri") ?? { uid => - Env.site.socketHandler(uid, ctx.userId, get("flag")) map some + fuccess(Env.site.socketHandler(uid, ctx.userId, get("flag")).some) } } diff --git a/app/controllers/Prismic.scala b/app/controllers/Prismic.scala index b256c06854..9c18532d96 100644 --- a/app/controllers/Prismic.scala +++ b/app/controllers/Prismic.scala @@ -17,8 +17,12 @@ object Prismic { case _ => logger info message } + import play.api.libs.ws._ + import play.api.Play.current + private val httpClient: WSClient = WS.client + private val fetchPrismicApi = AsyncCache.single[PrismicApi]( - f = PrismicApi.get(Env.api.PrismicApiUrl, logger = prismicLogger), + f = PrismicApi.get(httpClient)(Env.api.PrismicApiUrl, logger = prismicLogger), timeToLive = 1 minute) def prismicApi = fetchPrismicApi(true) @@ -33,7 +37,7 @@ object Prismic { api.forms("everything") .query(s"""[[:d = at(document.id, "$id")]]""") .ref(api.master.ref) - .submit() map { + .submit(httpClient) map { _.results.headOption } } @@ -52,7 +56,7 @@ object Prismic { api.forms("variant") .query(s"""[[:d = at(my.variant.key, "${variant.key}")]]""") .ref(api.master.ref) - .submit() map { + .submit(httpClient) map { _.results.headOption map (_ -> makeLinkResolver(api)) } } diff --git a/app/controllers/Round.scala b/app/controllers/Round.scala index 3e69d0b1e1..97b0937676 100644 --- a/app/controllers/Round.scala +++ b/app/controllers/Round.scala @@ -23,7 +23,7 @@ object Round extends LilaController with TheftPrevention { private def bookmarkApi = Env.bookmark.api private def analyser = Env.analyse.analyser - def websocketWatcher(gameId: String, color: String) = SocketOption[JsValue] { implicit ctx => + def websocketWatcher(gameId: String, color: String) = SocketOption { implicit ctx => get("sri") ?? { uid => env.socketHandler.watcher( gameId = gameId, @@ -35,7 +35,7 @@ object Round extends LilaController with TheftPrevention { } } - def websocketPlayer(fullId: String, apiVersion: Int) = SocketEither[JsValue] { implicit ctx => + def websocketPlayer(fullId: String, apiVersion: Int) = SocketEither { implicit ctx => GameRepo pov fullId flatMap { case Some(pov) => if (isTheft(pov)) fuccess(Left(theftResponse)) diff --git a/app/controllers/Simul.scala b/app/controllers/Simul.scala index b18f11c7d2..18e40a5905 100644 --- a/app/controllers/Simul.scala +++ b/app/controllers/Simul.scala @@ -114,7 +114,7 @@ object Simul extends LilaController { } } - def websocket(id: String, apiVersion: Int) = SocketOption[JsValue] { implicit ctx => + def websocket(id: String, apiVersion: Int) = SocketOption { implicit ctx => get("sri") ?? { uid => env.socketHandler.join(id, uid, ctx.me) } diff --git a/app/controllers/Tournament.scala b/app/controllers/Tournament.scala index 38cf412970..34b622cb22 100644 --- a/app/controllers/Tournament.scala +++ b/app/controllers/Tournament.scala @@ -172,7 +172,7 @@ object Tournament extends LilaController { } } - def websocket(id: String, apiVersion: Int) = SocketOption[JsValue] { implicit ctx => + def websocket(id: String, apiVersion: Int) = SocketOption { implicit ctx => get("sri") ?? { uid => env.socketHandler.join(id, uid, ctx.me) } diff --git a/app/controllers/Tv.scala b/app/controllers/Tv.scala index 4cdf6e03a5..4b99160209 100644 --- a/app/controllers/Tv.scala +++ b/app/controllers/Tv.scala @@ -79,10 +79,11 @@ object Tv extends LilaController { import akka.pattern.ask import lila.round.TvBroadcast import play.api.libs.EventSource - implicit val encoder = play.api.libs.Comet.CometMessage.jsonMessages - Env.round.tvBroadcast ? TvBroadcast.GetEnumerator mapTo - manifest[TvBroadcast.EnumeratorType] map { enum => - Ok.chunked(enum &> EventSource()).as("text/event-stream") + import akka.stream.scaladsl.Source + Env.round.tvBroadcast ? TvBroadcast.GetPublisher mapTo + manifest[TvBroadcast.PublisherType] map { publisher => + val source = Source fromPublisher publisher + Ok.chunked(source via EventSource.flow).as("text/event-stream") } } diff --git a/app/controllers/User.scala b/app/controllers/User.scala index 5fbe6ba03a..a711e02886 100644 --- a/app/controllers/User.scala +++ b/app/controllers/User.scala @@ -7,12 +7,10 @@ import lila.api.{ Context, BodyContext } import lila.app._ import lila.app.mashup.GameFilterMenu import lila.common.LilaCookie -import lila.db.api.$find import lila.evaluation.{ PlayerAggregateAssessment } import lila.game.{ GameRepo, Pov } import lila.rating.PerfType import lila.security.Permission -import lila.user.tube.userTube import lila.user.{ User => UserModel, UserRepo } import views._ diff --git a/app/controllers/WorldMap.scala b/app/controllers/WorldMap.scala index 9ffbde72e3..3e233cd162 100644 --- a/app/controllers/WorldMap.scala +++ b/app/controllers/WorldMap.scala @@ -14,10 +14,8 @@ object WorldMap extends LilaController { } def stream = Action.async { - Env.worldMap.getStream map { stream => - Ok.chunked( - stream &> EventSource() - ) as "text/event-stream" + Env.worldMap.getSource map { source => + Ok.chunked(source via EventSource.flow).as("text/event-stream") } } } diff --git a/app/mashup/GameFilter.scala b/app/mashup/GameFilter.scala index a7789c4458..329e59b8db 100644 --- a/app/mashup/GameFilter.scala +++ b/app/mashup/GameFilter.scala @@ -2,11 +2,10 @@ package lila.app package mashup import lila.common.paginator.Paginator -import lila.db.api.SortOrder +import lila.db.dsl._ import lila.game.{ Game, Query, GameRepo } import lila.user.User -import play.api.libs.json._ import play.api.mvc.Request import scalaz.NonEmptyList @@ -35,7 +34,6 @@ case class GameFilterMenu( object GameFilterMenu { import GameFilter._ - import lila.db.Implicits.docId val all = NonEmptyList.nel(All, List(Me, Rated, Win, Loss, Draw, Playing, Bookmark, Imported, Search)) @@ -96,22 +94,22 @@ object GameFilterMenu { me: Option[User], page: Int)(implicit req: Request[_]): Fu[Paginator[Game]] = { val nb = cachedNbOf(user, info, filter) - def std(query: JsObject) = pag.recentlyCreated(query, nb)(page) + def std(query: Bdoc) = pag.recentlyCreated(query, nb)(page) filter match { case Bookmark => Env.bookmark.api.gamePaginatorByUser(user, page) case Imported => pag.apply( selector = Query imported user.id, - sort = Seq("pgni.ca" -> SortOrder.Descending), + sort = $sort desc "pgni.ca", nb = nb)(page) - case All => std(Query started user) + case All => std(Query started user.id) case Me => std(Query.opponents(user, me | user)) - case Rated => std(Query rated user) - case Win => std(Query win user) - case Loss => std(Query loss user) - case Draw => std(Query draw user) + case Rated => std(Query rated user.id) + case Win => std(Query win user.id) + case Loss => std(Query loss user.id) + case Draw => std(Query draw user.id) case Playing => pag( selector = Query nowPlaying user.id, - sort = Seq(), + sort = $empty, nb = nb)(page) addEffect { p => p.currentPageResults.filter(_.finishedOrAborted) foreach GameRepo.unsetPlayingUids } diff --git a/app/mashup/TeamInfo.scala b/app/mashup/TeamInfo.scala index 9956a60742..624d458df4 100644 --- a/app/mashup/TeamInfo.scala +++ b/app/mashup/TeamInfo.scala @@ -3,10 +3,9 @@ package mashup import scala.concurrent.duration._ -import lila.db.api._ +import lila.db.dsl._ import lila.forum.MiniForumPost import lila.game.{ GameRepo, Game } -import lila.team.tube._ import lila.team.{ Team, Request, RequestRepo, MemberRepo, RequestWithUser, TeamApi } import lila.user.{ User, UserRepo } diff --git a/app/mashup/UserInfo.scala b/app/mashup/UserInfo.scala index 092bc53287..25a3870c1d 100644 --- a/app/mashup/UserInfo.scala +++ b/app/mashup/UserInfo.scala @@ -67,7 +67,8 @@ object UserInfo { isDonor: String => Fu[Boolean], isHostingSimul: String => Fu[Boolean], isStreamer: String => Boolean, - insightShare: lila.insight.Share)(user: User, ctx: Context): Fu[UserInfo] = + insightShare: lila.insight.Share, + getPlayTime: User => Fu[User.PlayTime])(user: User, ctx: Context): Fu[UserInfo] = countUsers() zip getRanks(user.id) zip (gameCached nbPlaying user.id) zip @@ -81,7 +82,7 @@ object UserInfo { isDonor(user.id) zip trophyApi.findByUser(user) zip (user.count.rated >= 10).??(insightShare.grant(user, ctx.me)) zip - PlayTime(user) flatMap { + getPlayTime(user) flatMap { case (((((((((((((nbUsers, ranks), nbPlaying), nbImported), crosstable), ratingChart), nbFollowing), nbFollowers), nbBlockers), nbPosts), isDonor), trophies), insightVisible), playTime) => (nbPlaying > 0) ?? isHostingSimul(user.id) map { hasSimul => new UserInfo( diff --git a/app/views/base/errorPage.scala.html b/app/views/base/errorPage.scala.html index 348fa41b6a..335a63a41a 100644 --- a/app/views/base/errorPage.scala.html +++ b/app/views/base/errorPage.scala.html @@ -1,16 +1,27 @@ @(ex: Throwable)(implicit ctx: Context) @site.layout( -title = "Internal server error") { +title = "Internal server error", +moreCss = cssTag("notFound.css")) {
-

Something went wrong on this page.

-
-
-

If the problem persists, please report it in the forum.

-

Or send me an email at lichess.contact@gmail.com

-
-
- @ex.getMessage +
+

500

+ Server error +

Sorry! We're working on it! Maybe try this mini-game?

+
+
+ +

+ ChessPursuit + courtesy of + Saturnyn +

+
} diff --git a/app/views/site/worldMap.scala.html b/app/views/site/worldMap.scala.html index 6c6df523f8..77ccb077f6 100644 --- a/app/views/site/worldMap.scala.html +++ b/app/views/site/worldMap.scala.html @@ -1,7 +1,6 @@ @() - Lichess World Map diff --git a/bin/build-deps.sh b/bin/build-deps.sh index b00d5e44d8..19135ad3a2 100755 --- a/bin/build-deps.sh +++ b/bin/build-deps.sh @@ -22,8 +22,8 @@ cd .. git clone https://github.com/ornicar/scala-kit --branch lichess-fork cd scala-kit -git checkout b019b3a2522d3f1697c39ec0c79e88c18ea49a91 -sbt -Dversion=1.2.11-THIB publish-local +git checkout 75fb61c8306a7a3db4c5dae6f110972130e7c6bc +sbt -Dversion=1.2.11-THIB2 publish-local cd .. git clone https://github.com/ornicar/maxmind-geoip2-scala --branch customBuild diff --git a/conf/base.conf b/conf/base.conf index 0639a23eb4..a99ddb3997 100644 --- a/conf/base.conf +++ b/conf/base.conf @@ -14,6 +14,9 @@ net { } forcedev = false play { + modules.enabled += "lila.app.LilaModule" + http.requestHandler = "lila.app.LilaHttpRequestHandler" + http.errorHandler = "lila.app.LilaHttpErrorHandler" server { netty { # The maximum length of the initial line. This effectively restricts the maximum length of a URL that the server will @@ -24,6 +27,10 @@ play { # The maximum length of the HTTP headers. The most common effect of this is a restriction in cookie length, including # number of cookies and size of cookie values. maxHeaderSize = 4096 # 8192 + + # "native is faster but only available on linux + # "native" is enabled in production + transport = "jdk" } } i18n { @@ -38,16 +45,26 @@ play { ws { useragent = ${net.base_url} compressionEnabled = true + followRedirects = true timeout { - connection = 5 seconds - idle = 5 minutes - request = 5 minutes + connection = 10 seconds + idle = 30 seconds + request = 1 minute + } + + # Configuration specific to the Ahc implementation of the WS client + ahc { + keepAlive = true + maxNumberOfRedirects = 3 + maxRequestRetry = 3 + # If non null, the maximum time that a connection should live for in the pool. + maxConnectionLifetime = null + # If non null, the time after which a connection that has been idle in the pool should be closed. + idleConnectionInPoolTimeout = 1 minute } - } - crypto { - secret="CiebwjgIM9cHQ;I?Xk:sfqDJ;BhIe:jsL?r=?IPF[saf>s^r0]?0grUq4>q?5mP^" } akka.actor-system = "lila" + crypto.secret="CiebwjgIM9cHQ;I?Xk:sfqDJ;BhIe:jsL?r=?IPF[saf>s^r0]?0grUq4>q?5mP^" } app { scheduler { diff --git a/conf/logback.xml b/conf/logback.xml index 4c9e1dd4f5..ae988ca169 100644 --- a/conf/logback.xml +++ b/conf/logback.xml @@ -1,6 +1,6 @@ - + diff --git a/modules/analyse/src/main/Analysis.scala b/modules/analyse/src/main/Analysis.scala index a29c1add6d..2929642834 100644 --- a/modules/analyse/src/main/Analysis.scala +++ b/modules/analyse/src/main/Analysis.scala @@ -74,6 +74,4 @@ object Analysis { "by" -> o.by, "date" -> w.date(o.date)) } - - private[analyse] lazy val tube = lila.db.BsTube(analysisBSONHandler) } diff --git a/modules/analyse/src/main/AnalysisRepo.scala b/modules/analyse/src/main/AnalysisRepo.scala index 5c27466eaf..5f148119e0 100644 --- a/modules/analyse/src/main/AnalysisRepo.scala +++ b/modules/analyse/src/main/AnalysisRepo.scala @@ -2,25 +2,25 @@ package lila.analyse import org.joda.time.DateTime import play.api.libs.json.Json -import play.modules.reactivemongo.json.ImplicitBSONHandlers.JsObjectWriter -import lila.db.api._ -import lila.db.Implicits._ +import lila.db.dsl._ import lila.game.Game -import tube.analysisTube object AnalysisRepo { import Analysis.analysisBSONHandler + // dirty + private val coll = Env.current.analysisColl + type ID = String - def save(analysis: Analysis) = analysisTube.coll insert analysis void + def save(analysis: Analysis) = coll insert analysis void - def byId(id: ID): Fu[Option[Analysis]] = $find byId id + def byId(id: ID): Fu[Option[Analysis]] = coll.byId[Analysis](id) def byIds(ids: Seq[ID]): Fu[Seq[Option[Analysis]]] = - $find optionsByOrderedIds ids + coll.optionsByOrderedIds[Analysis](ids)(_.id) def associateToGames(games: List[Game]): Fu[List[(Game, Analysis)]] = byIds(games.map(_.id)) map { as => @@ -29,7 +29,7 @@ object AnalysisRepo { } } - def remove(id: String) = $remove byId id + def remove(id: String) = coll remove $id(id) - def exists(id: String) = $count exists id + def exists(id: String) = coll exists $id(id) } diff --git a/modules/analyse/src/main/package.scala b/modules/analyse/src/main/package.scala index 52e0eb1278..08d552a8df 100644 --- a/modules/analyse/src/main/package.scala +++ b/modules/analyse/src/main/package.scala @@ -5,10 +5,4 @@ package object analyse extends PackageObject with WithPlay { type InfoAdvices = List[(Info, Option[Advice])] type PgnMove = String - - object tube { - - private[analyse] implicit lazy val analysisTube = - Analysis.tube inColl Env.current.analysisColl - } } diff --git a/modules/api/src/main/Cli.scala b/modules/api/src/main/Cli.scala index 856606c2f7..74a89894c2 100644 --- a/modules/api/src/main/Cli.scala +++ b/modules/api/src/main/Cli.scala @@ -20,11 +20,6 @@ private[api] final class Cli(bus: lila.common.Bus, renderer: ActorSelection) ext def process = { case "deploy" :: "pre" :: Nil => remindDeploy(lila.hub.actorApi.RemindDeployPre) case "deploy" :: "post" :: Nil => remindDeploy(lila.hub.actorApi.RemindDeployPost) - case "rating" :: "fest" :: Nil => RatingFest( - lila.db.Env.current, - lila.round.Env.current.perfsUpdater, - lila.game.Env.current, - lila.user.Env.current) inject "done" } private def remindDeploy(event: RemindDeploy): Fu[String] = { @@ -48,10 +43,8 @@ private[api] final class Cli(bus: lila.common.Bus, renderer: ActorSelection) ext lila.game.Env.current.cli.process orElse lila.gameSearch.Env.current.cli.process orElse lila.teamSearch.Env.current.cli.process orElse - lila.forum.Env.current.cli.process orElse lila.forumSearch.Env.current.cli.process orElse lila.team.Env.current.cli.process orElse - lila.round.Env.current.cli.process orElse lila.puzzle.Env.current.cli.process orElse lila.tournament.Env.current.cli.process orElse lila.explorer.Env.current.cli.process orElse diff --git a/modules/api/src/main/Env.scala b/modules/api/src/main/Env.scala index bbe3877204..70ea0b2b48 100644 --- a/modules/api/src/main/Env.scala +++ b/modules/api/src/main/Env.scala @@ -49,11 +49,11 @@ final class Env( object assetVersion { import reactivemongo.bson._ + import lila.db.dsl._ private val coll = db("flag") private val cache = lila.memo.MixedCache.single[Int]( - f = coll.find(BSONDocument("_id" -> "asset")).one[BSONDocument].map { - _.flatMap(_.getAs[BSONNumberLike]("version")) - .fold(Net.AssetVersion)(_.toInt max Net.AssetVersion) + f = coll.primitiveOne[BSONNumberLike]($id("asset"), "version").map { + _.fold(Net.AssetVersion)(_.toInt max Net.AssetVersion) }, timeToLive = 30.seconds, default = Net.AssetVersion, diff --git a/modules/api/src/main/GameApi.scala b/modules/api/src/main/GameApi.scala index 43bb97605c..662ba85f2e 100644 --- a/modules/api/src/main/GameApi.scala +++ b/modules/api/src/main/GameApi.scala @@ -7,12 +7,10 @@ import chess.format.pgn.Pgn import lila.analyse.{ AnalysisRepo, Analysis } import lila.common.paginator.{ Paginator, PaginatorJson } import lila.common.PimpedJson._ -import lila.db.api._ -import lila.db.Implicits._ -import lila.db.paginator.{ BSONAdapter, CachedAdapter } +import lila.db.dsl._ +import lila.db.paginator.{ Adapter, CachedAdapter } import lila.game.BSONHandlers._ import lila.game.Game.{ BSONFields => G } -import lila.game.tube.gameTube import lila.game.{ Game, GameRepo, PerfPicker } import lila.hub.actorApi.{ router => R } import lila.user.User @@ -38,16 +36,16 @@ private[api] final class GameApi( nb: Option[Int], page: Option[Int]): Fu[JsObject] = Paginator( adapter = new CachedAdapter( - adapter = new BSONAdapter[Game]( - collection = gameTube.coll, - selector = BSONDocument( + adapter = new Adapter[Game]( + collection = GameRepo.coll, + selector = $doc( G.playerUids -> user.id, - G.status -> BSONDocument("$gte" -> chess.Status.Mate.id), - G.rated -> rated.map(_.fold[BSONValue](BSONBoolean(true), BSONDocument("$exists" -> false))), - G.analysed -> analysed.map(_.fold[BSONValue](BSONBoolean(true), BSONDocument("$exists" -> false))) + G.status -> $doc("$gte" -> chess.Status.Mate.id), + G.rated -> rated.map(_.fold[BSONValue](BSONBoolean(true), $doc("$exists" -> false))), + G.analysed -> analysed.map(_.fold[BSONValue](BSONBoolean(true), $doc("$exists" -> false))) ), - projection = BSONDocument(), - sort = BSONDocument(G.createdAt -> -1) + projection = $empty, + sort = $doc(G.createdAt -> -1) ), nbResults = fuccess { rated.fold(user.count.game)(_.fold(user.count.rated, user.count.casual)) @@ -74,7 +72,7 @@ private[api] final class GameApi( withFens: Boolean, withMoveTimes: Boolean, token: Option[String]): Fu[Option[JsObject]] = - $find byId id flatMap { + GameRepo game id flatMap { _ ?? { g => gamesJson( withAnalysis = withAnalysis, diff --git a/modules/api/src/main/PgnDump.scala b/modules/api/src/main/PgnDump.scala index c34e03bcaf..142be09f56 100644 --- a/modules/api/src/main/PgnDump.scala +++ b/modules/api/src/main/PgnDump.scala @@ -1,8 +1,7 @@ package lila.api import chess.format.pgn.{ Pgn, Parser } -import lila.db.api.$query -import lila.db.Implicits._ +import lila.db.dsl._ import lila.game.Game import lila.game.{ GameRepo, Query } import play.api.libs.iteratee._ @@ -22,15 +21,11 @@ final class PgnDump( def filename(game: Game) = dumper filename game def exportUserGames(userId: String): Enumerator[String] = PgnStream { - import lila.game.tube.gameTube - import lila.game.BSONHandlers.gameBSONHandler - pimpQB($query(Query user userId)).sort(Query.sortCreated).cursor[Game]() + GameRepo.sortedCursor(Query user userId, Query.sortCreated) } def exportGamesFromIds(ids: List[String]): Enumerator[String] = PgnStream { - import lila.game.tube.gameTube - import lila.game.BSONHandlers.gameBSONHandler - pimpQB($query byIds ids).sort(Query.sortCreated).cursor[Game]() + GameRepo.sortedCursor($inIds(ids), Query.sortCreated) } private def PgnStream(cursor: reactivemongo.api.Cursor[Game]): Enumerator[String] = { diff --git a/modules/api/src/main/RatingFest.scala b/modules/api/src/main/RatingFest.scala deleted file mode 100644 index e8ffcdf0bb..0000000000 --- a/modules/api/src/main/RatingFest.scala +++ /dev/null @@ -1,86 +0,0 @@ -package lila.api - -import play.api.libs.iteratee._ -import play.api.libs.json.Json -import reactivemongo.bson._ - -import lila.db.api._ -import lila.db.Implicits._ -import lila.game.BSONHandlers.gameBSONHandler -import lila.game.Game.{ BSONFields => G } -import lila.game.{ Game, GameRepo, PerfPicker } -import lila.round.PerfsUpdater -import lila.user.{ User, UserRepo } - -object RatingFest { - - def apply( - db: lila.db.Env, - perfsUpdater: PerfsUpdater, - gameEnv: lila.game.Env, - userEnv: lila.user.Env) = { - - // val limit = Int.MaxValue - // val limit = 100000 - val limit = Int.MaxValue - val bulkSize = 4 - - def rerate(g: Game) = UserRepo.pair(g.whitePlayer.userId, g.blackPlayer.userId).flatMap { - case (Some(white), Some(black)) => - perfsUpdater.save(g, white, black, resetGameRatings = true) void - case _ => funit - } - - def unrate(game: Game) = - (game.whitePlayer.ratingDiff.isDefined || game.blackPlayer.ratingDiff.isDefined) ?? GameRepo.unrate(game.id).void - - def log(x: Any) = lila.log("ratingFest") info x.toString - - var nb = 0 - for { - _ <- fuccess(log("Removing history")) - _ <- db("history3").remove(BSONDocument()) - _ = log("Reseting perfs") - _ <- lila.user.tube.userTube.coll.update( - BSONDocument(), - BSONDocument("$unset" -> BSONDocument( - List( - "global", "white", "black", - "standard", "chess960", "kingOfTheHill", "threeCheck", - "bullet", "blitz", "classical", "correspondence" - ).map { name => s"perfs.$name" -> BSONBoolean(true) } - )), - multi = true) - _ = log("Gathering cheater IDs") - engineIds <- UserRepo.engineIds - _ = log(s"Found ${engineIds.size} cheaters") - _ = log("Starting the party") - _ <- lila.game.tube.gameTube |> { implicit gameTube => - val query = $query(lila.game.Query.rated) - // val query = $query.all - // .batch(100) - .sort($sort asc G.createdAt) - var started = nowMillis - $enumerate.bulk[Game](query, bulkSize, limit) { games => - nb = nb + bulkSize - if (nb % 1000 == 0) { - val perS = 1000 * 1000 / math.max(1, nowMillis - started) - started = nowMillis - log("Processed %d games at %d/s".format(nb, perS)) - } - games.map { game => - game.userIds match { - case _ if !game.rated => funit - case _ if !game.finished => funit - case _ if game.fromPosition => funit - case List(uidW, uidB) if (uidW == uidB) => funit - case List(uidW, uidB) if engineIds(uidW) || engineIds(uidB) => unrate(game) - case List(uidW, uidB) => rerate(game) - case _ => funit - } - }.sequenceFu.void - } andThen { case _ => log(nb) } - } - } yield () - } -} diff --git a/modules/api/src/main/UserApi.scala b/modules/api/src/main/UserApi.scala index 381a6865df..892d488a94 100644 --- a/modules/api/src/main/UserApi.scala +++ b/modules/api/src/main/UserApi.scala @@ -3,12 +3,10 @@ package lila.api import play.api.libs.json._ import lila.common.PimpedJson._ -import lila.db.api._ -import lila.db.Implicits._ +import lila.db.dsl._ import lila.game.GameRepo import lila.hub.actorApi.{ router => R } import lila.rating.Perf -import lila.user.tube.userTube import lila.user.{ UserRepo, User, Perfs, Profile } import makeTimeout.short diff --git a/modules/blog/src/main/BlogApi.scala b/modules/blog/src/main/BlogApi.scala index f4970e5f85..330a96fb36 100644 --- a/modules/blog/src/main/BlogApi.scala +++ b/modules/blog/src/main/BlogApi.scala @@ -6,15 +6,19 @@ import scala.concurrent.duration._ final class BlogApi(prismicUrl: String, collection: String) { + import play.api.libs.ws._ + import play.api.Play.current + private val httpClient: WSClient = WS.client + def recent(api: Api, ref: Option[String], nb: Int): Fu[Option[Response]] = api.forms(collection).ref(resolveRef(api)(ref) | api.master.ref) .orderings(s"[my.$collection.date desc]") - .pageSize(nb).page(1).submit().fold(_ => none, some _) + .pageSize(nb).page(1).submit(httpClient).fold(_ => none, some _) def one(api: Api, ref: Option[String], id: String) = api.forms(collection) .query(s"""[[:d = at(document.id, "$id")]]""") - .ref(resolveRef(api)(ref) | api.master.ref).submit() map (_.results.headOption) + .ref(resolveRef(api)(ref) | api.master.ref).submit(httpClient) map (_.results.headOption) // -- Build a Prismic context def context(refName: Option[String])(implicit linkResolver: (Api, Option[String]) => DocumentLinkResolver) = @@ -39,7 +43,7 @@ final class BlogApi(prismicUrl: String, collection: String) { } private val fetchPrismicApi = AsyncCache.single[Api]( - f = Api.get(prismicUrl, cache = cache, logger = prismicLogger), + f = Api.get(httpClient)(prismicUrl, cache = cache, logger = prismicLogger), timeToLive = 10 seconds) def prismicApi = fetchPrismicApi(true) diff --git a/modules/bookmark/src/main/BookmarkApi.scala b/modules/bookmark/src/main/BookmarkApi.scala index 9bc731e236..f5a38e07e5 100644 --- a/modules/bookmark/src/main/BookmarkApi.scala +++ b/modules/bookmark/src/main/BookmarkApi.scala @@ -1,17 +1,18 @@ package lila.bookmark -import lila.db.api._ -import lila.game.tube.gameTube +import lila.db.dsl._ import lila.game.{ Game, GameRepo } import lila.user.User -import tube.bookmarkTube final class BookmarkApi( + coll: Coll, cached: Cached, paginator: PaginatorBuilder) { + import lila.game.BSONHandlers.gameBSONHandler + def toggle(gameId: String, userId: String): Funit = - $find.byId[Game](gameId) flatMap { + coll.byId[Game](gameId) flatMap { _ ?? { game => BookmarkRepo.toggle(gameId, userId) flatMap { bookmarked => GameRepo.incBookmarks(gameId, bookmarked.fold(1, -1)) >>- diff --git a/modules/bookmark/src/main/BookmarkRepo.scala b/modules/bookmark/src/main/BookmarkRepo.scala index 8154a64fc3..b0a440ef26 100644 --- a/modules/bookmark/src/main/BookmarkRepo.scala +++ b/modules/bookmark/src/main/BookmarkRepo.scala @@ -1,19 +1,19 @@ package lila.bookmark import org.joda.time.DateTime -import play.api.libs.json._ import reactivemongo.bson._ -import lila.db.api._ -import lila.db.Implicits._ -import tube.bookmarkTube +import lila.db.dsl._ case class Bookmark(game: lila.game.Game, user: lila.user.User) private[bookmark] object BookmarkRepo { + // dirty + private val coll = Env.current.bookmarkColl + def toggle(gameId: String, userId: String): Fu[Boolean] = - $count exists selectId(gameId, userId) flatMap { e => + coll exists selectId(gameId, userId) flatMap { e => e.fold( remove(gameId, userId), add(gameId, userId, DateTime.now) @@ -21,25 +21,25 @@ private[bookmark] object BookmarkRepo { } def gameIdsByUserId(userId: String): Fu[Set[String]] = - bookmarkTube.coll.distinct("g", BSONDocument("u" -> userId).some) map lila.db.BSON.asStringSet + coll.distinct("g", $doc("u" -> userId).some) map lila.db.BSON.asStringSet def removeByGameId(gameId: String): Funit = - $remove(Json.obj("g" -> gameId)) + coll.remove($doc("g" -> gameId)).void def removeByGameIds(gameIds: List[String]): Funit = - $remove(Json.obj("g" -> $in(gameIds))) + coll.remove($doc("g" -> $in(gameIds: _*))).void private def add(gameId: String, userId: String, date: DateTime): Funit = - $insert(Json.obj( + coll.insert($doc( "_id" -> makeId(gameId, userId), "g" -> gameId, "u" -> userId, - "d" -> $date(date))) + "d" -> date)).void - def userIdQuery(userId: String) = Json.obj("u" -> userId) - def makeId(gameId: String, userId: String) = gameId + userId - def selectId(gameId: String, userId: String) = $select(makeId(gameId, userId)) + def userIdQuery(userId: String) = $doc("u" -> userId) + def makeId(gameId: String, userId: String) = s"$gameId$userId" + def selectId(gameId: String, userId: String) = $id(makeId(gameId, userId)) - def remove(gameId: String, userId: String): Funit = $remove(selectId(gameId, userId)) - def remove(selector: JsObject): Funit = $remove(selector) + def remove(gameId: String, userId: String): Funit = coll.remove(selectId(gameId, userId)).void + def remove(selector: Bdoc): Funit = coll.remove(selector).void } diff --git a/modules/bookmark/src/main/Env.scala b/modules/bookmark/src/main/Env.scala index 4f5a6b783f..7b2f65b264 100644 --- a/modules/bookmark/src/main/Env.scala +++ b/modules/bookmark/src/main/Env.scala @@ -20,9 +20,11 @@ final class Env( private lazy val cached = new Cached lazy val paginator = new PaginatorBuilder( + coll = bookmarkColl, maxPerPage = PaginatorMaxPerPage) lazy val api = new BookmarkApi( + coll = bookmarkColl, cached = cached, paginator = paginator) diff --git a/modules/bookmark/src/main/PaginatorBuilder.scala b/modules/bookmark/src/main/PaginatorBuilder.scala index e661ac4bfd..9b0008002b 100644 --- a/modules/bookmark/src/main/PaginatorBuilder.scala +++ b/modules/bookmark/src/main/PaginatorBuilder.scala @@ -1,18 +1,14 @@ package lila.bookmark -import play.api.libs.json._ -import play.modules.reactivemongo.json.ImplicitBSONHandlers._ - import lila.common.paginator._ -import lila.common.PimpedJson._ -import lila.db.api._ -import lila.db.Implicits._ +import lila.db.dsl._ import lila.db.paginator._ import lila.game.{ Game, GameRepo } import lila.user.User -import tube.bookmarkTube -private[bookmark] final class PaginatorBuilder(maxPerPage: Int) { +private[bookmark] final class PaginatorBuilder( + coll: Coll, + maxPerPage: Int) { def byUser(user: User, page: Int): Fu[Paginator[Bookmark]] = paginator(new UserAdapter(user), page) @@ -26,17 +22,15 @@ private[bookmark] final class PaginatorBuilder(maxPerPage: Int) { final class UserAdapter(user: User) extends AdapterLike[Bookmark] { - def nbResults: Fu[Int] = $count(selector) + def nbResults: Fu[Int] = coll countSel selector def slice(offset: Int, length: Int): Fu[Seq[Bookmark]] = for { - gameIds ← $primitive( - selector, - "g", - _ sort sorting skip offset, - length.some)(_.asOpt[String]) - games ← lila.game.tube.gameTube |> { implicit t => - $find.byOrderedIds[Game](gameIds) - } + gameIds ← coll.find(selector, $doc("g" -> true)) + .sort(sorting) + .skip(offset) + .cursor[Bdoc]() + .gather[List](length) map { _ flatMap { _.getAs[String]("g") } } + games ← GameRepo games gameIds } yield games map { g => Bookmark(g, user) } private def selector = BookmarkRepo userIdQuery user.id diff --git a/modules/bookmark/src/main/package.scala b/modules/bookmark/src/main/package.scala index 545c2376d3..c2b938bf74 100644 --- a/modules/bookmark/src/main/package.scala +++ b/modules/bookmark/src/main/package.scala @@ -1,12 +1,3 @@ package lila -import lila.db.JsTube - -package object bookmark extends PackageObject with WithPlay { - - object tube { - - private[bookmark] implicit lazy val bookmarkTube = - JsTube.json inColl Env.current.bookmarkColl - } -} +package object bookmark extends PackageObject with WithPlay diff --git a/modules/challenge/src/main/BSONHandlers.scala b/modules/challenge/src/main/BSONHandlers.scala index 7e78144f59..d42d43c9d2 100644 --- a/modules/challenge/src/main/BSONHandlers.scala +++ b/modules/challenge/src/main/BSONHandlers.scala @@ -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] { diff --git a/modules/challenge/src/main/ChallengeRepo.scala b/modules/challenge/src/main/ChallengeRepo.scala index 49e439f8d6..ffa5e08ef4 100644 --- a/modules/challenge/src/main/ChallengeRepo.scala +++ b/modules/challenge/src/main/ChallengeRepo.scala @@ -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)).uno[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)) - .cursor[Challenge]().collect[List]() + coll.find(selectCreated ++ $doc("challenger.id" -> userId)) + .sort($doc("createdAt" -> 1)) + .cursor[Challenge]().gather[List]() def createdByDestId(userId: String): Fu[List[Challenge]] = - coll.find(selectCreated ++ BSONDocument("destUser.id" -> userId)) - .sort(BSONDocument("createdAt" -> 1)) - .cursor[Challenge]().collect[List]() + coll.find(selectCreated ++ $doc("destUser.id" -> userId)) + .sort($doc("createdAt" -> 1)) + .cursor[Challenge]().gather[List]() def removeByUserId(userId: String): Funit = - coll.remove(BSONDocument("$or" -> BSONArray( - BSONDocument("challenger.id" -> userId), - BSONDocument("destUser.id" -> userId) - ))).void + coll.remove($or( + $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]) + "destUser.id" -> destUserId)).uno[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) - )).cursor[Challenge]().collect[List](max) + coll.find(selectCreated ++ selectClock ++ $doc( + "seenAt" -> $doc("$lt" -> date) + )).cursor[Challenge]().gather[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) + ).uno[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) } diff --git a/modules/challenge/src/main/Env.scala b/modules/challenge/src/main/Env.scala index 6b063aee01..c9f5880820 100644 --- a/modules/challenge/src/main/Env.scala +++ b/modules/challenge/src/main/Env.scala @@ -45,7 +45,7 @@ final class Env( lazy val socketHandler = new SocketHandler( hub = hub, socketHub = socketHub, - pingChallenge = api.ping) + pingChallenge = api.ping)(system) lazy val api = new ChallengeApi( repo = repo, diff --git a/modules/challenge/src/main/Socket.scala b/modules/challenge/src/main/Socket.scala index 24cf3ca3db..2a52fa1d1d 100644 --- a/modules/challenge/src/main/Socket.scala +++ b/modules/challenge/src/main/Socket.scala @@ -1,12 +1,11 @@ package lila.challenge import akka.actor._ -import play.api.libs.iteratee.Concurrent import play.api.libs.json._ import scala.concurrent.duration.Duration import lila.hub.TimeBomb -import lila.socket.actorApi.{ Connected => _, _ } +import lila.socket.actorApi._ import lila.socket.{ SocketActor, History, Historical } private final class Socket( @@ -40,15 +39,11 @@ private final class Socket( if (timeBomb.boom) self ! PoisonPill } - case GetVersion => sender ! history.version + case GetVersion => sender ! history.version - case Socket.Join(uid, userId, owner) => - val (enumerator, channel) = Concurrent.broadcast[JsValue] - val member = Socket.Member(channel, userId, owner) - addMember(uid, member) - sender ! Socket.Connected(enumerator, member) + case Socket.AddMember(uid, member) => addMember(uid, member) - case Quit(uid) => quit(uid) + case Quit(uid) => quit(uid) } protected def shouldSkipMessageFor(message: Message, member: Socket.Member) = false @@ -57,14 +52,13 @@ private final class Socket( private object Socket { case class Member( - channel: JsChannel, + out: ActorRef, userId: Option[String], owner: Boolean) extends lila.socket.SocketMember { val troll = false } - case class Join(uid: String, userId: Option[String], owner: Boolean) - case class Connected(enumerator: JsEnumerator, member: Member) + case class AddMember(uid: String, member: Member) case object Reload } diff --git a/modules/challenge/src/main/SocketHandler.scala b/modules/challenge/src/main/SocketHandler.scala index 2d05025ff0..2e20fc17c2 100644 --- a/modules/challenge/src/main/SocketHandler.scala +++ b/modules/challenge/src/main/SocketHandler.scala @@ -1,14 +1,11 @@ package lila.challenge -import scala.concurrent.duration._ - import akka.actor._ import akka.pattern.ask -import akka.actor.ActorSelection import lila.common.PimpedJson._ import lila.hub.actorApi.map._ -import lila.socket.actorApi.{ Connected => _, _ } +import lila.socket.actorApi._ import lila.socket.Handler import lila.user.User import makeTimeout.short @@ -16,20 +13,25 @@ import makeTimeout.short private[challenge] final class SocketHandler( hub: lila.hub.Env, socketHub: ActorRef, - pingChallenge: Challenge.ID => Funit) { + pingChallenge: Challenge.ID => Funit)(implicit system: ActorSystem) { def join( challengeId: Challenge.ID, uid: String, userId: Option[User.ID], - owner: Boolean): Fu[Option[JsSocketHandler]] = for { - socket ← socketHub ? Get(challengeId) mapTo manifest[ActorRef] - join = Socket.Join(uid = uid, userId = userId, owner = owner) - handler ← Handler(hub, socket, uid, join, userId) { - case Socket.Connected(enum, member) => - (controller(socket, challengeId, uid, member), enum, member) + owner: Boolean): Fu[Option[JsFlow]] = + socketHub ? Get(challengeId) mapTo manifest[ActorRef] map { socket => + Handler.actorRef { out => + val member = Socket.Member(out, userId, owner) + socket ! Socket.AddMember(uid, member) + Handler.props(hub, socket, member, uid, userId) { + case ("p", o) => o int "v" foreach { v => + socket ! PingVersion(uid, v) + } + case ("ping", _) if member.owner => pingChallenge(challengeId) + } + }.some } - } yield handler.some private def controller( socket: ActorRef, diff --git a/modules/chat/src/main/ChatApi.scala b/modules/chat/src/main/ChatApi.scala index 1e9d318326..8156f52511 100644 --- a/modules/chat/src/main/ChatApi.scala +++ b/modules/chat/src/main/ChatApi.scala @@ -3,7 +3,7 @@ package lila.chat import chess.Color import reactivemongo.bson.BSONDocument -import lila.db.Types.Coll +import lila.db.dsl._ import lila.user.{ User, UserRepo } final class ChatApi( @@ -18,7 +18,7 @@ final class ChatApi( object userChat { def findOption(chatId: ChatId): Fu[Option[UserChat]] = - coll.find(BSONDocument("_id" -> chatId)).one[UserChat] + coll.find(BSONDocument("_id" -> chatId)).uno[UserChat] def find(chatId: ChatId): Fu[UserChat] = findOption(chatId) map (_ | Chat.makeUser(chatId)) @@ -52,7 +52,7 @@ final class ChatApi( object playerChat { def findOption(chatId: ChatId): Fu[Option[MixedChat]] = - coll.find(BSONDocument("_id" -> chatId)).one[MixedChat] + coll.find(BSONDocument("_id" -> chatId)).uno[MixedChat] def find(chatId: ChatId): Fu[MixedChat] = findOption(chatId) map (_ | Chat.makeMixed(chatId)) diff --git a/modules/common/src/main/AkkaStream.scala b/modules/common/src/main/AkkaStream.scala new file mode 100644 index 0000000000..1dd89bdcd0 --- /dev/null +++ b/modules/common/src/main/AkkaStream.scala @@ -0,0 +1,23 @@ +package lila.common + +import akka.actor._ +import akka.stream._ +import akka.stream.scaladsl._ +import org.reactivestreams.Publisher + +object AkkaStream { + + def actorPublisher[T]( + bufferSize: Int, + overflowStrategy: OverflowStrategy = OverflowStrategy.dropHead)(implicit materializer: Materializer): (ActorRef, Publisher[T]) = { + Source.actorRef[T](20, OverflowStrategy.dropHead) + .toMat(Sink asPublisher false)(Keep.both).run() + } + + def actorSource[T]( + bufferSize: Int, + overflowStrategy: OverflowStrategy)(implicit materializer: Materializer): (ActorRef, Source[T, _]) = { + val (out, publisher) = actorPublisher(bufferSize, overflowStrategy) + (out, Source fromPublisher publisher) + } +} diff --git a/modules/common/src/main/PackageObject.scala b/modules/common/src/main/PackageObject.scala index 204ef2a21e..75089730ea 100644 --- a/modules/common/src/main/PackageObject.scala +++ b/modules/common/src/main/PackageObject.scala @@ -97,6 +97,9 @@ trait WithPlay { self: PackageObject => implicit def execontext = play.api.libs.concurrent.Execution.defaultContext + implicit def materializer(implicit system: akka.actor.ActorSystem): akka.stream.Materializer = + akka.stream.ActorMaterializer() + implicit val LilaFutureMonad = new Monad[Fu] { override def map[A, B](fa: Fu[A])(f: A => B) = fa map f def point[A](a: => A) = fuccess(a) @@ -123,95 +126,8 @@ trait WithPlay { self: PackageObject => Future sequence t } - implicit final class LilaPimpedFuture[A](fua: Fu[A]) { - - def >>-(sideEffect: => Unit): Fu[A] = fua andThen { - case _ => sideEffect - } - - def >>[B](fub: => Fu[B]): Fu[B] = fua flatMap (_ => fub) - - def void: Funit = fua map (_ => Unit) - - def inject[B](b: => B): Fu[B] = fua map (_ => b) - - def injectAnyway[B](b: => B): Fu[B] = fua.fold(_ => b, _ => b) - - def effectFold(fail: Exception => Unit, succ: A => Unit) { - fua onComplete { - case scala.util.Failure(e: Exception) => fail(e) - case scala.util.Failure(e) => throw e // Throwables - case scala.util.Success(e) => succ(e) - } - } - - def andThenAnyway(sideEffect: => Unit): Fu[A] = { - fua onComplete { - case scala.util.Failure(_) => sideEffect - case scala.util.Success(_) => sideEffect - } - fua - } - - def fold[B](fail: Exception => B, succ: A => B): Fu[B] = - fua map succ recover { case e: Exception => fail(e) } - - def flatFold[B](fail: Exception => Fu[B], succ: A => Fu[B]): Fu[B] = - fua flatMap succ recoverWith { case e: Exception => fail(e) } - - def logFailure(logger: => lila.log.Logger, msg: Exception => String): Fu[A] = - addFailureEffect { e => logger.warn(msg(e), e) } - def logFailure(logger: => lila.log.Logger): Fu[A] = logFailure(logger, _.toString) - - def addEffect(effect: A => Unit) = fua ~ (_ foreach effect) - - def addFailureEffect(effect: Exception => Unit) = fua ~ (_ onFailure { - case e: Exception => effect(e) - }) - - def addEffects(fail: Exception => Unit, succ: A => Unit): Fu[A] = - fua andThen { - case scala.util.Failure(e: Exception) => fail(e) - case scala.util.Failure(e) => throw e // Throwables - case scala.util.Success(e) => succ(e) - } - - def mapFailure(f: Exception => Exception) = fua recover { - case cause: Exception => throw f(cause) - } - - def prefixFailure(p: => String) = fua mapFailure { e => - common.LilaException(s"$p ${e.getMessage}") - } - - def thenPp: Fu[A] = fua ~ { - _.effectFold( - e => println("[failure] " + e), - a => println("[success] " + a) - ) - } - - def thenPp(msg: String): Fu[A] = fua ~ { - _.effectFold( - e => println(s"[$msg] [failure] $e"), - a => println(s"[$msg] [success] $a") - ) - } - - def awaitSeconds(seconds: Int): A = { - import scala.concurrent.duration._ - scala.concurrent.Await.result(fua, seconds.seconds) - } - - def withTimeout(duration: FiniteDuration, error: => Throwable)(implicit system: akka.actor.ActorSystem): Fu[A] = { - Future firstCompletedOf Seq(fua, - akka.pattern.after(duration, system.scheduler)(fufail(error))) - } - - def chronometer = lila.common.Chronometer(fua) - - def mon(path: lila.mon.RecPath) = chronometer.mon(path).result - } + implicit def LilaPimpedFuture[A](fua: Fu[A]): PimpedFuture.LilaPimpedFuture[A] = + new PimpedFuture.LilaPimpedFuture(fua) implicit final class LilaPimpedFutureZero[A: Zero](fua: Fu[A]) { @@ -258,6 +174,8 @@ trait WithPlay { self: PackageObject => implicit final class LilaPimpedActorSystem(self: akka.actor.ActorSystem) { def lilaBus = lila.common.Bus(self) + + def lilaMat = materializer(self) } object makeTimeout { diff --git a/modules/common/src/main/PimpedConfig.scala b/modules/common/src/main/PimpedConfig.scala index 975ae81072..4674f0cf16 100644 --- a/modules/common/src/main/PimpedConfig.scala +++ b/modules/common/src/main/PimpedConfig.scala @@ -7,7 +7,7 @@ import com.typesafe.config.Config object PimpedConfig { - implicit final class LilaPimpedConfig(config: Config) { + implicit final class LilaPimpedConfig(val config: Config) extends AnyVal { def millis(name: String): Int = config.getDuration(name, TimeUnit.MILLISECONDS).toInt def seconds(name: String): Int = config.getDuration(name, TimeUnit.SECONDS).toInt diff --git a/modules/common/src/main/PimpedFuture.scala b/modules/common/src/main/PimpedFuture.scala new file mode 100644 index 0000000000..94713a8f97 --- /dev/null +++ b/modules/common/src/main/PimpedFuture.scala @@ -0,0 +1,109 @@ +package lila + +import play.api.libs.concurrent.Execution.Implicits._ +import scala.concurrent.Future +import scala.concurrent.duration._ + +object PimpedFuture { + + private type Fu[A] = Future[A] + private type Funit = Fu[Unit] + + final class LilaPimpedFuture[A](val fua: Fu[A]) extends AnyVal { + + def >>-(sideEffect: => Unit): Fu[A] = fua andThen { + case _ => sideEffect + } + + def >>[B](fub: => Fu[B]): Fu[B] = fua flatMap (_ => fub) + + def void: Funit = fua map (_ => Unit) + + def inject[B](b: => B): Fu[B] = fua map (_ => b) + + def injectAnyway[B](b: => B): Fu[B] = fold(_ => b, _ => b) + + def effectFold(fail: Exception => Unit, succ: A => Unit) { + fua onComplete { + case scala.util.Failure(e: Exception) => fail(e) + case scala.util.Failure(e) => throw e // Throwables + case scala.util.Success(e) => succ(e) + } + } + + def andThenAnyway(sideEffect: => Unit): Fu[A] = { + fua onComplete { + case scala.util.Failure(_) => sideEffect + case scala.util.Success(_) => sideEffect + } + fua + } + + def fold[B](fail: Exception => B, succ: A => B): Fu[B] = + fua map succ recover { case e: Exception => fail(e) } + + def flatFold[B](fail: Exception => Fu[B], succ: A => Fu[B]): Fu[B] = + fua flatMap succ recoverWith { case e: Exception => fail(e) } + + def logFailure(logger: => lila.log.Logger, msg: Exception => String): Fu[A] = + addFailureEffect { e => logger.warn(msg(e), e) } + def logFailure(logger: => lila.log.Logger): Fu[A] = logFailure(logger, _.toString) + + def addEffect(effect: A => Unit) = { + fua foreach effect + fua + } + + def addFailureEffect(effect: Exception => Unit) = { + fua onFailure { + case e: Exception => effect(e) + } + fua + } + + def addEffects(fail: Exception => Unit, succ: A => Unit): Fu[A] = + fua andThen { + case scala.util.Failure(e: Exception) => fail(e) + case scala.util.Failure(e) => throw e // Throwables + case scala.util.Success(e) => succ(e) + } + + def mapFailure(f: Exception => Exception) = fua recover { + case cause: Exception => throw f(cause) + } + + def prefixFailure(p: => String) = mapFailure { e => + common.LilaException(s"$p ${e.getMessage}") + } + + def thenPp: Fu[A] = { + effectFold( + e => println("[failure] " + e), + a => println("[success] " + a) + ) + fua + } + + def thenPp(msg: String): Fu[A] = { + effectFold( + e => println(s"[$msg] [failure] $e"), + a => println(s"[$msg] [success] $a") + ) + fua + } + + def awaitSeconds(seconds: Int): A = { + import scala.concurrent.duration._ + scala.concurrent.Await.result(fua, seconds.seconds) + } + + def withTimeout(duration: FiniteDuration, error: => Throwable)(implicit system: akka.actor.ActorSystem): Fu[A] = { + Future firstCompletedOf Seq(fua, + akka.pattern.after(duration, system.scheduler)(Future failed error)) + } + + def chronometer = lila.common.Chronometer(fua) + + def mon(path: lila.mon.RecPath) = chronometer.mon(path).result + } +} diff --git a/modules/common/src/main/PimpedJson.scala b/modules/common/src/main/PimpedJson.scala index c5663a2f5b..79af9c7a32 100644 --- a/modules/common/src/main/PimpedJson.scala +++ b/modules/common/src/main/PimpedJson.scala @@ -2,11 +2,9 @@ package lila.common import play.api.libs.json._ -object PimpedJson extends PimpedJson +object PimpedJson { -trait PimpedJson { - - implicit final class LilaPimpedJsObject(js: JsObject) { + implicit final class LilaPimpedJsObject(val js: JsObject) extends AnyVal { def str(key: String): Option[String] = (js \ key).asOpt[String] @@ -41,7 +39,7 @@ trait PimpedJson { } } - implicit final class LilaPimpedJsValue(js: JsValue) { + implicit final class LilaPimpedJsValue(val js: JsValue) extends AnyVal { def str(key: String): Option[String] = js.asOpt[JsObject] flatMap { obj => diff --git a/modules/coordinate/src/main/CoordinateApi.scala b/modules/coordinate/src/main/CoordinateApi.scala index 7730b2b58b..9d0207b7bb 100644 --- a/modules/coordinate/src/main/CoordinateApi.scala +++ b/modules/coordinate/src/main/CoordinateApi.scala @@ -1,6 +1,6 @@ package lila.coordinate -import lila.db.Types.Coll +import lila.db.dsl._ import reactivemongo.bson._ final class CoordinateApi(scoreColl: Coll) { @@ -8,7 +8,7 @@ final class CoordinateApi(scoreColl: Coll) { private implicit val scoreBSONHandler = Macros.handler[Score] def getScore(userId: String): Fu[Score] = - scoreColl.find(BSONDocument("_id" -> userId)).one[Score] map (_ | Score(userId)) + scoreColl.byId[Score](userId) map (_ | Score(userId)) def addScore(userId: String, white: Boolean, hits: Int): Funit = scoreColl.update( diff --git a/modules/db/src/main/BSON.scala b/modules/db/src/main/BSON.scala index 4b4f48e5ef..2049689b84 100644 --- a/modules/db/src/main/BSON.scala +++ b/modules/db/src/main/BSON.scala @@ -15,21 +15,32 @@ abstract class BSON[T] def reads(reader: Reader): T def writes(writer: Writer, obj: T): BSONDocument - def read(doc: BSONDocument): T = reads(new Reader(doc)) + def read(doc: BSONDocument): T = try { + reads(new Reader(doc)) + } + catch { + case e: Exception => + logger.error(s"Can't read malformed doc ${debug(doc)}", e) + throw e + } + def write(obj: T): BSONDocument = writes(writer, obj) } -object BSON { - - implicit object BSONJodaDateTimeHandler extends BSONHandler[BSONDateTime, DateTime] { - def read(x: BSONDateTime) = new DateTime(x.value) - def write(x: DateTime) = BSONDateTime(x.getMillis) - } +object BSON extends Handlers { - def stringAnyValHandler[A](from: A => String, to: String => A) = new BSONHandler[BSONString, A] { - def read(x: BSONString) = to(x.value) - def write(x: A) = BSONString(from(x)) - } + def LoggingHandler[T](logger: lila.log.Logger)(handler: BSONHandler[BSONDocument, T]) = + new BSONHandler[BSONDocument, T] with BSONDocumentReader[T] with BSONDocumentWriter[T] { + def read(doc: BSONDocument): T = try { + handler read doc + } + catch { + case e: Exception => + logger.error(s"Can't read malformed doc ${debug(doc)}", e) + throw e + } + def write(obj: T): BSONDocument = handler write obj + } object MapDocument { @@ -88,24 +99,6 @@ object BSON { } } - private def readStream[T](array: BSONArray, reader: BSONReader[BSONValue, T]): Stream[T] = { - array.stream.filter(_.isSuccess).map { v => - reader.read(v.get) - } - } - - implicit def bsonArrayToListHandler[T](implicit reader: BSONReader[_ <: BSONValue, T], writer: BSONWriter[T, _ <: BSONValue]): BSONHandler[BSONArray, List[T]] = new BSONHandler[BSONArray, List[T]] { - def read(array: BSONArray) = readStream(array, reader.asInstanceOf[BSONReader[BSONValue, T]]).toList - def write(repr: List[T]) = - new BSONArray(repr.map(s => scala.util.Try(writer.write(s))).to[Stream]) - } - - implicit def bsonArrayToVectorHandler[T](implicit reader: BSONReader[_ <: BSONValue, T], writer: BSONWriter[T, _ <: BSONValue]): BSONHandler[BSONArray, Vector[T]] = new BSONHandler[BSONArray, Vector[T]] { - def read(array: BSONArray) = readStream(array, reader.asInstanceOf[BSONReader[BSONValue, T]]).toVector - def write(repr: Vector[T]) = - new BSONArray(repr.map(s => scala.util.Try(writer.write(s))).to[Stream]) - } - final class Reader(val doc: BSONDocument) { val map = { diff --git a/modules/db/src/main/ByteArray.scala b/modules/db/src/main/ByteArray.scala index d2d5361177..dc5e9b5dc0 100644 --- a/modules/db/src/main/ByteArray.scala +++ b/modules/db/src/main/ByteArray.scala @@ -2,13 +2,9 @@ package lila.db import scala.util.{ Try, Success, Failure } -import play.api.data.validation.ValidationError -import play.api.libs.json._ import reactivemongo.bson._ import reactivemongo.bson.utils.Converters -import lila.common.PimpedJson._ - case class ByteArray(value: Array[Byte]) { def isEmpty = value.isEmpty @@ -36,21 +32,6 @@ object ByteArray { def write(ba: ByteArray) = BSONBinary(ba.value, subtype) } - implicit object JsByteArrayFormat extends OFormat[ByteArray] { - - def reads(json: JsValue) = (for { - hexStr ← json str "$binary" - bytes ← fromHexStr(hexStr).toOption - } yield bytes) match { - case None => JsError(s"error reading ByteArray from $json") - case Some(ba) => JsSuccess(ba) - } - - def writes(byteArray: ByteArray) = Json.obj( - "$binary" -> byteArray.toHexStr, - "$type" -> binarySubType) - } - def parseByte(s: String): Byte = { var i = s.length - 1 var sum = 0 diff --git a/modules/db/src/main/CollExt.scala b/modules/db/src/main/CollExt.scala new file mode 100644 index 0000000000..23b42ef51f --- /dev/null +++ b/modules/db/src/main/CollExt.scala @@ -0,0 +1,85 @@ +package lila.db + +import reactivemongo.api._ +import reactivemongo.bson._ + +trait CollExt { self: dsl with QueryBuilderExt => + + final implicit class ExtendColl(coll: Coll) { + + def uno[D: BSONDocumentReader](selector: BSONDocument): Fu[Option[D]] = + coll.find(selector).uno[D] + + def list[D: BSONDocumentReader](selector: BSONDocument): Fu[List[D]] = + coll.find(selector).list[D]() + + def list[D: BSONDocumentReader](selector: BSONDocument, max: Int): Fu[List[D]] = + coll.find(selector).list[D](max) + + def byId[D: BSONDocumentReader, I: BSONValueWriter](id: I): Fu[Option[D]] = + uno[D]($id(id)) + + def byId[D: BSONDocumentReader](id: String): Fu[Option[D]] = uno[D]($id(id)) + + def byId[D: BSONDocumentReader](id: Int): Fu[Option[D]] = uno[D]($id(id)) + + def byIds[D: BSONDocumentReader, I: BSONValueWriter](ids: Iterable[I]): Fu[List[D]] = + list[D]($inIds(ids)) + + def byIds[D: BSONDocumentReader](ids: Iterable[String]): Fu[List[D]] = + byIds[D, String](ids) + + def countSel(selector: BSONDocument): Fu[Int] = coll count selector.some + + def exists(selector: BSONDocument): Fu[Boolean] = countSel(selector).map(0!=) + + def byOrderedIds[D: BSONDocumentReader](ids: Iterable[String])(docId: D => String): Fu[List[D]] = + byIds[D](ids) map { docs => + val docsMap = docs.map(u => docId(u) -> u).toMap + ids.flatMap(docsMap.get).toList + } + // def byOrderedIds[A <: Identified[String]: TubeInColl](ids: Iterable[String]): Fu[List[A]] = + // byOrderedIds[String, A](ids) + + def optionsByOrderedIds[D: BSONDocumentReader](ids: Iterable[String])(docId: D => String): Fu[List[Option[D]]] = + byIds[D](ids) map { docs => + val docsMap = docs.map(u => docId(u) -> u).toMap + ids.map(docsMap.get).toList + } + + def primitive[V: BSONValueReader](selector: BSONDocument, field: String): Fu[List[V]] = + coll.find(selector, $doc(field -> true)) + .list[BSONDocument]() + .map { + _ flatMap { _.getAs[V](field) } + } + + def primitiveOne[V: BSONValueReader](selector: BSONDocument, field: String): Fu[Option[V]] = + coll.find(selector, $doc(field -> true)) + .uno[BSONDocument] + .map { + _ flatMap { _.getAs[V](field) } + } + + def primitiveOne[V: BSONValueReader](selector: BSONDocument, sort: BSONDocument, field: String): Fu[Option[V]] = + coll.find(selector, $doc(field -> true)) + .sort(sort) + .uno[BSONDocument] + .map { + _ flatMap { _.getAs[V](field) } + } + + def updateField[V: BSONValueWriter](selector: BSONDocument, field: String, value: V) = + coll.update(selector, $set(field -> value)) + + def updateFieldUnchecked[V: BSONValueWriter](selector: BSONDocument, field: String, value: V) = + coll.uncheckedUpdate(selector, $set(field -> value)) + + def fetchUpdate[D: BSONDocumentHandler](selector: BSONDocument)(update: D => BSONDocument): Funit = + uno[D](selector) flatMap { + _ ?? { doc => + coll.update(selector, update(doc)).void + } + } + } +} diff --git a/modules/db/src/main/CursorExt.scala b/modules/db/src/main/CursorExt.scala new file mode 100644 index 0000000000..53a47c0c1c --- /dev/null +++ b/modules/db/src/main/CursorExt.scala @@ -0,0 +1,26 @@ +package lila.db + +import scala.collection.generic.CanBuildFrom + +import reactivemongo.api._ +import reactivemongo.bson._ + +trait CursorExt { self: dsl => + + final implicit class ExtendCursor[A: BSONDocumentReader](val c: Cursor[A]) { + + // like collect, but with stopOnError defaulting to false + def gather[M[_]](upTo: Int = Int.MaxValue)(implicit cbf: CanBuildFrom[M[_], A, M[A]]): Fu[M[A]] = + c.collect[M](upTo, stopOnError = false) + + def list(limit: Option[Int]): Fu[List[A]] = gather[List](limit | Int.MaxValue) + + def list(limit: Int): Fu[List[A]] = list(limit.some) + + def list(): Fu[List[A]] = list(none) + + // like headOption, but with stopOnError defaulting to false + def uno: Fu[Option[A]] = + c.collect[Iterable](1, stopOnError = false).map(_.headOption) + } +} diff --git a/modules/db/src/main/Env.scala b/modules/db/src/main/Env.scala index 4f46e42cef..c0b66b57df 100644 --- a/modules/db/src/main/Env.scala +++ b/modules/db/src/main/Env.scala @@ -5,7 +5,7 @@ import reactivemongo.api._ import scala.concurrent.duration._ import scala.concurrent.Future import scala.util.{ Success, Failure } -import Types._ +import dsl._ final class Env( config: Config, diff --git a/modules/db/src/main/Handlers.scala b/modules/db/src/main/Handlers.scala new file mode 100644 index 0000000000..f0e25bada2 --- /dev/null +++ b/modules/db/src/main/Handlers.scala @@ -0,0 +1,35 @@ +package lila.db + +import org.joda.time.DateTime +import reactivemongo.bson._ + +trait Handlers { + + implicit object BSONJodaDateTimeHandler extends BSONHandler[BSONDateTime, DateTime] { + def read(x: BSONDateTime) = new DateTime(x.value) + def write(x: DateTime) = BSONDateTime(x.getMillis) + } + + def stringAnyValHandler[A](from: A => String, to: String => A) = new BSONHandler[BSONString, A] { + def read(x: BSONString) = to(x.value) + def write(x: A) = BSONString(from(x)) + } + + implicit def bsonArrayToListHandler[T](implicit reader: BSONReader[_ <: BSONValue, T], writer: BSONWriter[T, _ <: BSONValue]): BSONHandler[BSONArray, List[T]] = new BSONHandler[BSONArray, List[T]] { + def read(array: BSONArray) = readStream(array, reader.asInstanceOf[BSONReader[BSONValue, T]]).toList + def write(repr: List[T]) = + new BSONArray(repr.map(s => scala.util.Try(writer.write(s))).to[Stream]) + } + + implicit def bsonArrayToVectorHandler[T](implicit reader: BSONReader[_ <: BSONValue, T], writer: BSONWriter[T, _ <: BSONValue]): BSONHandler[BSONArray, Vector[T]] = new BSONHandler[BSONArray, Vector[T]] { + def read(array: BSONArray) = readStream(array, reader.asInstanceOf[BSONReader[BSONValue, T]]).toVector + def write(repr: Vector[T]) = + new BSONArray(repr.map(s => scala.util.Try(writer.write(s))).to[Stream]) + } + + private def readStream[T](array: BSONArray, reader: BSONReader[BSONValue, T]): Stream[T] = { + array.stream.filter(_.isSuccess).map { v => + reader.read(v.get) + } + } +} diff --git a/modules/db/src/main/PaginatorAdapter.scala b/modules/db/src/main/PaginatorAdapter.scala index 4db059ba22..0e9e15ba1c 100644 --- a/modules/db/src/main/PaginatorAdapter.scala +++ b/modules/db/src/main/PaginatorAdapter.scala @@ -1,28 +1,13 @@ package lila.db package paginator -import api._ -import Implicits._ -import play.api.libs.json._ +import dsl._ import reactivemongo.api.collections.bson.BSONCollection import reactivemongo.api._ import reactivemongo.bson._ import lila.common.paginator.AdapterLike -final class Adapter[A: TubeInColl]( - selector: JsObject, - sort: Sort, - readPreference: ReadPreference = ReadPreference.primary) extends AdapterLike[A] { - - def nbResults: Fu[Int] = $count(selector) - - def slice(offset: Int, length: Int): Fu[Seq[A]] = $find( - pimpQB($query(selector)).sort(sort: _*) skip offset, - length, - readPreference = readPreference) -} - final class CachedAdapter[A]( adapter: AdapterLike[A], val nbResults: Fu[Int]) extends AdapterLike[A] { @@ -31,7 +16,7 @@ final class CachedAdapter[A]( adapter.slice(offset, length) } -final class BSONAdapter[A: BSONDocumentReader]( +final class Adapter[A: BSONDocumentReader]( collection: BSONCollection, selector: BSONDocument, projection: BSONDocument, @@ -43,7 +28,7 @@ final class BSONAdapter[A: BSONDocumentReader]( def slice(offset: Int, length: Int): Fu[Seq[A]] = collection.find(selector, projection) .sort(sort) - .copy(options = QueryOpts(skipN = offset)) + .skip(offset) .cursor[A](readPreference = readPreference) - .collect[List](length) + .gather[List](length) } diff --git a/modules/db/src/main/QueryBuilderExt.scala b/modules/db/src/main/QueryBuilderExt.scala new file mode 100644 index 0000000000..487f9462ae --- /dev/null +++ b/modules/db/src/main/QueryBuilderExt.scala @@ -0,0 +1,36 @@ +package lila.db + +import scala.collection.generic.CanBuildFrom + +import reactivemongo.api._ +import reactivemongo.api.collections.GenericQueryBuilder +import reactivemongo.bson._ + +trait QueryBuilderExt { self: dsl => + + final implicit class ExtendQueryBuilder[A](val b: dsl.QueryBuilder) { + + def skip(nb: Int) = b.options(b.options skip nb) + + def batch(nb: Int) = b.options(b.options batchSize nb) + + // like collect, but with stopOnError defaulting to false + def gather[A, M[_]](upTo: Int = Int.MaxValue)(implicit cbf: CanBuildFrom[M[_], A, M[A]], reader: BSONDocumentReader[A]): Fu[M[A]] = + b.cursor[A]().collect[M](upTo, stopOnError = false) + + def list[A: BSONDocumentReader](limit: Option[Int]): Fu[List[A]] = gather[A, List](limit | Int.MaxValue) + + def list[A: BSONDocumentReader](limit: Int): Fu[List[A]] = list[A](limit.some) + + def list[A: BSONDocumentReader](): Fu[List[A]] = list[A](none) + + // like one, but with stopOnError defaulting to false + def uno[A: BSONDocumentReader]: Fu[Option[A]] = uno[A](ReadPreference.primary) + + def uno[A: BSONDocumentReader](readPreference: ReadPreference): Fu[Option[A]] = + b.copy(options = b.options.batchSize(1)) + .cursor[A](readPreference = readPreference) + .collect[Iterable](1, stopOnError = false) + .map(_.headOption) + } +} diff --git a/modules/db/src/main/Tube.scala b/modules/db/src/main/Tube.scala deleted file mode 100644 index 71bcd5d967..0000000000 --- a/modules/db/src/main/Tube.scala +++ /dev/null @@ -1,139 +0,0 @@ -package lila.db - -import scala.util.{ Try, Success, Failure } - -import play.api.libs.functional.syntax._ -import play.api.libs.json._ -import reactivemongo.bson._ -import Reads.constraints._ -import Types.Coll - -import lila.common.LilaException - -trait InColl[A] { implicit def coll: Types.Coll } - -trait Tube[Doc] extends BSONDocumentReader[Option[Doc]] - -case class BsTube[Doc](handler: BSONHandler[BSONDocument, Doc]) extends Tube[Doc] { - - def read(bson: BSONDocument): Option[Doc] = handler readTry bson match { - case Success(doc) => Some(doc) - case Failure(err) => - logger.error(s"[tube] Cannot read ${lila.db.BSON.debug(bson)}\n$err\n", err) - None - } - - def write(doc: Doc): BSONDocument = handler write doc - - def inColl(c: Coll): BsTubeInColl[Doc] = - new BsTube[Doc](handler) with InColl[Doc] { def coll = c } -} - -case class JsTube[Doc]( - reader: Reads[Doc], - writer: Writes[Doc], - flags: Seq[JsTube.Flag.type => JsTube.Flag] = Seq.empty) - extends Tube[Doc] - with Reads[Doc] - with Writes[Doc] { - - import play.modules.reactivemongo.json._ - - implicit def reads(js: JsValue): JsResult[Doc] = reader reads js - implicit def writes(doc: Doc): JsValue = writer writes doc - - def read(bson: BSONDocument): Option[Doc] = { - val js = JsObjectReader read bson - fromMongo(js) match { - case JsSuccess(v, _) => Some(v) - case e => - logger.error("[tube] Cannot read %s\n%s".format(js, e)) - None - } - } - - def read(js: JsObject): JsResult[Doc] = reads(js) - - def write(doc: Doc): JsResult[JsObject] = writes(doc) match { - case obj: JsObject => JsSuccess(obj) - case something => - logger.error(s"[tube] Cannot write $doc\ngot $something") - JsError() - } - - def toMongo(doc: Doc): JsResult[JsObject] = flag(_.NoId)( - write(doc), - write(doc) flatMap JsTube.toMongoId - ) - - def fromMongo(js: JsObject): JsResult[Doc] = flag(_.NoId)( - read(js), - JsTube.depath(JsTube fromMongoId js) flatMap read - ) - - def inColl(c: Coll): JsTubeInColl[Doc] = - new JsTube[Doc](reader, writer, flags) with InColl[Doc] { def coll = c } - - private lazy val flagSet = flags.map(_(JsTube.Flag)).toSet - - private def flag[A](f: JsTube.Flag.type => JsTube.Flag)(x: => A, y: => A) = - flagSet contains f(JsTube.Flag) fold (x, y) -} - -object JsTube { - - val json = JsTube[JsObject]( - __.read[JsObject], - __.write[JsObject], - Seq(_.NoId) // no need to rename the ID field as we are not mapping - ) - - private val toMongoIdOp = Helpers.rename('id, '_id) - def toMongoId(js: JsValue): JsResult[JsObject] = js transform toMongoIdOp - - private val fromMongoIdOp = Helpers.rename('_id, 'id) - def fromMongoId(js: JsValue): JsResult[JsObject] = js transform fromMongoIdOp - - sealed trait Flag - object Flag { - case object NoId extends Flag - } - - object Helpers { - - // Adds Writes[A].andThen combinator, symmetric to Reads[A].andThen - // Explodes on failure - implicit final class LilaTubePimpedWrites[A](writes: Writes[A]) { - def andThen(transformer: Reads[JsObject]): Writes[A] = - writes.transform(Writes[JsValue] { origin => - origin transform transformer match { - case err: JsError => throw LilaException("[tube] Cannot transform %s\n%s".format(origin, err)) - case JsSuccess(js, _) => js - } - }) - } - - def rename(from: Symbol, to: Symbol) = __.json update ( - (__ \ to).json copyFrom (__ \ from).json.pick - ) andThen (__ \ from).json.prune - - def readDate(field: Symbol) = - (__ \ field).json.update(of[JsObject] map { o => - (o \ "$date").toOption err s"Can't read date of $o" - }) - - def readDateOpt(field: Symbol) = readDate(field) orElse json.reader - - def writeDate(field: Symbol) = (__ \ field).json.update(of[JsNumber] map { - millis => Json.obj("$date" -> millis) - }) - - def writeDateOpt(field: Symbol) = (__ \ field).json.update(of[JsNumber] map { - millis => Json.obj("$date" -> millis) - }) orElse json.reader - - def merge(obj: JsObject) = __.read[JsObject] map (obj ++) - } - - private def depath[A](r: JsResult[A]): JsResult[A] = r.flatMap(JsSuccess(_)) -} diff --git a/modules/db/src/main/Util.scala b/modules/db/src/main/Util.scala index f1d8f2a3ad..794c9349f3 100644 --- a/modules/db/src/main/Util.scala +++ b/modules/db/src/main/Util.scala @@ -2,14 +2,14 @@ package lila.db import reactivemongo.bson._ -import Types.Coll +import dsl._ object Util { def findNextId(coll: Coll): Fu[Int] = coll.find(BSONDocument(), BSONDocument("_id" -> true)) .sort(BSONDocument("_id" -> -1)) - .one[BSONDocument] map { + .uno[BSONDocument] map { _ flatMap { doc => doc.getAs[Int]("_id") map (1+) } getOrElse 1 } } diff --git a/modules/db/src/main/api/count.scala b/modules/db/src/main/api/count.scala deleted file mode 100644 index 3471244c69..0000000000 --- a/modules/db/src/main/api/count.scala +++ /dev/null @@ -1,21 +0,0 @@ -package lila.db -package api - -import play.api.libs.json._ -import play.modules.reactivemongo.json.ImplicitBSONHandlers.JsObjectWriter -import reactivemongo.core.commands.Count -import Types._ - -object $count { - - def apply[A: InColl](q: JsObject): Fu[Int] = - implicitly[InColl[A]].coll |> { _.count(JsObjectWriter.write(q).some) } - - def apply[A: InColl]: Fu[Int] = - implicitly[InColl[A]].coll |> { _.count(none) } - - def exists[A : InColl](q: JsObject): Fu[Boolean] = apply(q) map (0 !=) - - def exists[ID: Writes, A: InColl](id: ID): Fu[Boolean] = exists($select(id)) - def exists[A: InColl](id: String): Fu[Boolean] = exists[String, A](id) -} diff --git a/modules/db/src/main/api/cursor.scala b/modules/db/src/main/api/cursor.scala deleted file mode 100644 index 337794992a..0000000000 --- a/modules/db/src/main/api/cursor.scala +++ /dev/null @@ -1,16 +0,0 @@ -package lila.db -package api - -import Implicits._ -import play.api.libs.json._ -import reactivemongo.api._ -import reactivemongo.bson._ - -object $cursor { - - def apply[A: TubeInColl](q: JsObject): Cursor[Option[A]] = - apply($query(q)) - - def apply[A: TubeInColl](b: QueryBuilder): Cursor[Option[A]] = - b.cursor[Option[A]]() -} diff --git a/modules/db/src/main/api/enumerate.scala b/modules/db/src/main/api/enumerate.scala deleted file mode 100644 index 50499fe0a8..0000000000 --- a/modules/db/src/main/api/enumerate.scala +++ /dev/null @@ -1,40 +0,0 @@ -package lila.db -package api - -import play.api.libs.iteratee._ -import play.api.libs.json._ -import play.modules.reactivemongo.json.ImplicitBSONHandlers._ -import reactivemongo.api.Cursor -import reactivemongo.bson._ -import scalaz.Monoid - -import lila.db.Implicits._ - -object $enumerate { - - def apply[A: BSONDocumentReader](query: QueryBuilder, limit: Int = Int.MaxValue)(op: A => Any): Funit = - query.cursor[A]().enumerate(limit) run { - Iteratee.foreach((obj: A) => op(obj)) - } - - def over[A: TubeInColl](query: QueryBuilder, limit: Int = Int.MaxValue)(op: A => Funit): Funit = - query.cursor[Option[A]]().enumerate(limit, stopOnError = false) run { - Iteratee.foldM(()) { - case (_, Some(obj)) => op(obj) - case _ => funit - } - } - - def bulk[A: BSONDocumentReader](query: QueryBuilder, size: Int, limit: Int = Int.MaxValue)(op: List[A] => Funit): Funit = - query.batch(size).cursor[A]().enumerateBulks(limit) run { - Iteratee.foldM(()) { - case (_, objs) => op(objs.toList) - } - } - - def fold[A: BSONDocumentReader, B](query: QueryBuilder)(zero: B)(f: (B, A) => B): Fu[B] = - query.cursor[A]().enumerate() |>>> Iteratee.fold(zero)(f) - - def foldMonoid[A: BSONDocumentReader, B: Monoid](query: QueryBuilder)(f: A => B): Fu[B] = - fold[A, B](query)(Monoid[B].zero) { case (b, a) => f(a) |+| b } -} diff --git a/modules/db/src/main/api/find.scala b/modules/db/src/main/api/find.scala deleted file mode 100644 index 02523195a2..0000000000 --- a/modules/db/src/main/api/find.scala +++ /dev/null @@ -1,57 +0,0 @@ -package lila.db -package api - -import Implicits._ -import play.api.libs.json._ -import reactivemongo.bson._ -import reactivemongo.api._ - -object $find { - - def one[A: TubeInColl]( - q: JsObject, - modifier: QueryBuilder => QueryBuilder = identity): Fu[Option[A]] = - one(modifier($query(q))) - - def one[A: TubeInColl](q: QueryBuilder): Fu[Option[A]] = - q.one[Option[A]] map (_.flatten) - - def byId[ID: Writes, A: TubeInColl](id: ID): Fu[Option[A]] = one($select byId id) - def byId[A: TubeInColl](id: String): Fu[Option[A]] = byId[String, A](id) - - def byIds[ID: Writes, A: TubeInColl](ids: Iterable[ID]): Fu[List[A]] = apply($select byIds ids) - def byIds[A: TubeInColl](ids: Iterable[String]): Fu[List[A]] = byIds[String, A](ids) - - def byOrderedIds[ID: Writes, A <: Identified[ID]: TubeInColl](ids: Iterable[ID]): Fu[List[A]] = - byIds(ids) map { docs => - val docsMap = docs.map(u => u.id -> u).toMap - ids.flatMap(docsMap.get).toList - } - def byOrderedIds[A <: Identified[String]: TubeInColl](ids: Iterable[String]): Fu[List[A]] = - byOrderedIds[String, A](ids) - - def optionsByOrderedIds[ID: Writes, A <: Identified[ID]: TubeInColl](ids: Iterable[ID]): Fu[List[Option[A]]] = - byIds(ids) map { docs => - val docsMap = docs.map(u => u.id -> u).toMap - ids.map(docsMap.get).toList - } - def opByOrderedIds[A <: Identified[String]: TubeInColl](ids: Iterable[String]): Fu[List[Option[A]]] = - optionsByOrderedIds[String, A](ids) - - def all[A: TubeInColl]: Fu[List[A]] = apply($select.all) - - def apply[A: TubeInColl](q: JsObject): Fu[List[A]] = - $query(q).toList[Option[A]](none) map (_.flatten) - - def apply[A: TubeInColl](q: JsObject, nb: Int): Fu[List[A]] = - $query(q).toList[Option[A]](nb.some) map (_.flatten) - - def apply[A: TubeInColl](b: QueryBuilder): Fu[List[A]] = - b.toList[Option[A]](none) map (_.flatten) - - def apply[A: TubeInColl](b: QueryBuilder, nb: Int): Fu[List[A]] = - b.toList[Option[A]](nb.some) map (_.flatten) - - def apply[A: TubeInColl](b: QueryBuilder, nb: Int, readPreference: ReadPreference): Fu[List[A]] = - b.toList[Option[A]](nb.some, readPreference) map (_.flatten) -} diff --git a/modules/db/src/main/api/insert.scala b/modules/db/src/main/api/insert.scala deleted file mode 100644 index c6fbb04104..0000000000 --- a/modules/db/src/main/api/insert.scala +++ /dev/null @@ -1,23 +0,0 @@ -package lila.db -package api - -import play.api.libs.json._ -import reactivemongo.bson._ -import Types.Coll - -object $insert { - import play.modules.reactivemongo.json._ - - def apply[A: JsTubeInColl](doc: A): Funit = - (implicitly[JsTube[A]] toMongo doc).fold(e => fufail(e.toString), apply(_)) - - def apply[A: InColl](js: JsObject): Funit = - implicitly[InColl[A]].coll insert js void - - def bson[A: BsTubeInColl](doc: A): Funit = bson { - implicitly[BsTube[A]].handler write doc - } - - def bson[A: InColl](bs: BSONDocument): Funit = - implicitly[InColl[A]].coll insert bs void -} diff --git a/modules/db/src/main/api/operator.scala b/modules/db/src/main/api/operator.scala deleted file mode 100644 index 393a01802b..0000000000 --- a/modules/db/src/main/api/operator.scala +++ /dev/null @@ -1,51 +0,0 @@ -package lila.db -package api - -import play.api.libs.json._ -import reactivemongo.bson._ - -object $operator extends $operator -trait $operator { - import play.modules.reactivemongo.json._ - - def $set[A: Writes](pairs: (String, A)*) = Json.obj("$set" -> Json.obj(wrap(pairs): _*)) - def $set(pairs: (String, Json.JsValueWrapper)*) = Json.obj("$set" -> Json.obj(pairs: _*)) - def $set(pairs: JsObject) = Json.obj("$set" -> pairs) - def $setBson(pairs: (String, BSONValue)*) = BSONDocument("$set" -> BSONDocument(pairs)) - def $setBson(pairs: BSONDocument) = BSONDocument("$set" -> pairs) - def $unset(fields: String*) = Json.obj("$unset" -> Json.obj(wrap(fields map (_ -> true)): _*)) - def $inc[A: Writes](pairs: (String, A)*) = Json.obj("$inc" -> Json.obj(wrap(pairs): _*)) - def $incBson(pairs: (String, Int)*) = BSONDocument("$inc" -> BSONDocument(pairs map { - case (k, v) => k -> BSONInteger(v) - })) - def $push[A: Writes](field: String, value: A) = Json.obj("$push" -> Json.obj(field -> value)) - def $pushSlice[A: Writes](field: String, value: A, max: Int) = Json.obj("$push" -> Json.obj( - field -> Json.obj( - "$each" -> List(value), - "$slice" -> max - ) - )) - def $pull[A: Writes](field: String, value: A) = Json.obj("$pull" -> Json.obj(field -> value)) - - def $gt[A: Writes](value: A) = Json.obj("$gt" -> value) - def $gte[A: Writes](value: A) = Json.obj("$gte" -> value) - def $lt[A: Writes](value: A) = Json.obj("$lt" -> value) - def $lte[A: Writes](value: A) = Json.obj("$lte" -> value) - def $ne[A: Writes](value: A) = Json.obj("$ne" -> value) - - def $in[A: Writes](values: Iterable[A]) = Json.obj("$in" -> values) - def $nin[A: Writes](values: Iterable[A]) = Json.obj("$nin" -> values) - def $all[A: Writes](values: Iterable[A]) = Json.obj("$all" -> values) - def $exists(bool: Boolean) = Json.obj("$exists" -> bool) - - def $or[A: Writes](conditions: Iterable[A]): JsObject = Json.obj("$or" -> conditions) - - def $regex(value: String, flags: String = "") = BSONFormats toJSON BSONRegex(value, flags) - - import org.joda.time.DateTime - def $date(value: DateTime) = BSONFormats toJSON BSONDateTime(value.getMillis) - - private def wrap[K, V: Writes](pairs: Seq[(K, V)]): Seq[(K, Json.JsValueWrapper)] = pairs map { - case (k, v) => k -> Json.toJsFieldJsValueWrapper(v) - } -} diff --git a/modules/db/src/main/api/package.scala b/modules/db/src/main/api/package.scala deleted file mode 100644 index 136ddbca72..0000000000 --- a/modules/db/src/main/api/package.scala +++ /dev/null @@ -1,5 +0,0 @@ -package lila.db - -package object api extends api.$operator { - type JSFunction = String -} diff --git a/modules/db/src/main/api/primitive.scala b/modules/db/src/main/api/primitive.scala deleted file mode 100644 index 08a1fd38cd..0000000000 --- a/modules/db/src/main/api/primitive.scala +++ /dev/null @@ -1,39 +0,0 @@ -package lila.db -package api - -import Implicits._ -import play.api.libs.json._ -import reactivemongo.bson._ - -object $primitive { - import play.modules.reactivemongo.json._ - - def apply[A: InColl, B]( - query: JsObject, - field: String, - modifier: QueryBuilder => QueryBuilder = identity, - max: Option[Int] = None, - hint: BSONDocument = BSONDocument())(extract: JsValue => Option[B]): Fu[List[B]] = - modifier { - implicitly[InColl[A]].coll - .genericQueryBuilder - .query(query) - .hint(hint) - .projection(Json.obj(field -> true)) - } toList[BSONDocument] max map2 { (obj: BSONDocument) => - extract(JsObjectReader.read(obj) \ field get) - } map (_.flatten) - - def one[A: InColl, B]( - query: JsObject, - field: String, - modifier: QueryBuilder => QueryBuilder = identity)(extract: JsValue => Option[B]): Fu[Option[B]] = - modifier { - implicitly[InColl[A]].coll - .genericQueryBuilder - .query(query) - .projection(Json.obj(field -> true)) - }.one[BSONDocument] map2 { (obj: BSONDocument) => - (JsObjectReader.read(obj) \ field).toOption flatMap extract - } map (_.flatten) -} diff --git a/modules/db/src/main/api/projection.scala b/modules/db/src/main/api/projection.scala deleted file mode 100644 index b3e6b56412..0000000000 --- a/modules/db/src/main/api/projection.scala +++ /dev/null @@ -1,33 +0,0 @@ -package lila.db -package api - -import Implicits._ -import play.api.libs.json._ -import reactivemongo.bson._ - -object $projection { - import play.modules.reactivemongo.json._ - - def apply[A: InColl, B]( - q: JsObject, - fields: Seq[String], - modifier: QueryBuilder => QueryBuilder = identity, - max: Option[Int] = None)(extract: JsObject => Option[B]): Fu[List[B]] = - modifier { - implicitly[InColl[A]].coll.genericQueryBuilder query q projection projector(fields) - } toList[BSONDocument] max map (list => list map { obj => - extract(JsObjectReader read obj) - } flatten) - - def one[A: InColl, B]( - q: JsObject, - fields: Seq[String], - modifier: QueryBuilder => QueryBuilder = identity)(extract: JsObject => Option[B]): Fu[Option[B]] = - modifier(implicitly[InColl[A]].coll.genericQueryBuilder query q projection projector(fields)).one[BSONDocument] map (opt => opt map { obj => - extract(JsObjectReader read obj) - } flatten) - - private def projector(fields: Seq[String]): JsObject = Json obj { - (fields map (_ -> Json.toJsFieldJsValueWrapper(1))): _* - } -} diff --git a/modules/db/src/main/api/query.scala b/modules/db/src/main/api/query.scala deleted file mode 100644 index f9e1cc498a..0000000000 --- a/modules/db/src/main/api/query.scala +++ /dev/null @@ -1,22 +0,0 @@ -package lila.db -package api - -import play.api.libs.json._ -import reactivemongo.bson._ -import Types._ - -object $query { - import play.modules.reactivemongo.json._ - - def all[A: InColl] = builder - - def apply[A: InColl](q: JsObject) = builder query q - - def apply[A: InColl](q: BSONDocument) = builder query q - - def byId[A: InColl, B: Writes](id: B) = apply($select byId id) - - def byIds[A: InColl, B: Writes](ids: Iterable[B]) = apply($select byIds ids) - - def builder[A: InColl] = implicitly[InColl[A]].coll.genericQueryBuilder -} diff --git a/modules/db/src/main/api/remove.scala b/modules/db/src/main/api/remove.scala deleted file mode 100644 index e172dfdc78..0000000000 --- a/modules/db/src/main/api/remove.scala +++ /dev/null @@ -1,24 +0,0 @@ -package lila.db -package api - -import play.api.libs.json._ -import Types._ - -object $remove { - import play.modules.reactivemongo.json._ - - def apply[A: InColl](selector: JsObject): Funit = - implicitly[InColl[A]].coll remove selector void - - def byId[ID: Writes, A: InColl](id: ID): Funit = - apply($select(id)) - def byId[A: InColl](id: String): Funit = byId[String, A](id) - - def byIds[ID: Writes, A: InColl](ids: Seq[ID]): Funit = - apply($select byIds ids) - def byIds[A: InColl](ids: Seq[String]): Funit = byIds[String, A](ids) - - def apply[ID: Writes, A <: Identified[ID]: TubeInColl](doc: A): Funit = - byId(doc.id) - def apply[A <: Identified[String]: TubeInColl](doc: A): Funit = apply[String, A](doc) -} diff --git a/modules/db/src/main/api/save.scala b/modules/db/src/main/api/save.scala deleted file mode 100644 index 703c832152..0000000000 --- a/modules/db/src/main/api/save.scala +++ /dev/null @@ -1,19 +0,0 @@ -package lila.db -package api - -import play.api.libs.json._ -import Types._ - -object $save { - import play.modules.reactivemongo.json._ - - def apply[ID: Writes, A <: Identified[ID]: JsTubeInColl](doc: A): Funit = - (implicitly[JsTube[A]] toMongo doc).fold(e => fufail(e.toString), - js => $update($select(doc.id), js, upsert = true) - ) - - def apply[A <: Identified[String]: JsTubeInColl](doc: A): Funit = apply[String, A](doc) - - def apply[ID: Writes, A: InColl](id: ID, doc: JsObject): Funit = - $update($select(id), doc + ("_id" -> Json.toJson(id)), upsert = true, multi = false) -} diff --git a/modules/db/src/main/api/select.scala b/modules/db/src/main/api/select.scala deleted file mode 100644 index 4e52324e86..0000000000 --- a/modules/db/src/main/api/select.scala +++ /dev/null @@ -1,15 +0,0 @@ -package lila.db -package api - -import play.api.libs.json._ - -object $select { - - def all = Json.obj() - - def apply[A: Writes](id: A): JsObject = byId(id) - - def byId[A: Writes](id: A) = Json.obj("_id" -> id) - - def byIds[A: Writes](ids: Iterable[A]) = Json.obj("_id" -> $in(ids)) -} diff --git a/modules/db/src/main/api/sort.scala b/modules/db/src/main/api/sort.scala deleted file mode 100644 index 82b2d6c2c0..0000000000 --- a/modules/db/src/main/api/sort.scala +++ /dev/null @@ -1,31 +0,0 @@ -package lila.db -package api - -import play.api.libs.json._ -import reactivemongo.bson._ - -sealed trait SortOrder - -object SortOrder { - object Ascending extends SortOrder - object Descending extends SortOrder -} - -object $sort { - def asc: SortOrder = SortOrder.Ascending - def desc: SortOrder = SortOrder.Descending - - def asc(field: String): (String, SortOrder) = field -> asc - def desc(field: String): (String, SortOrder) = field -> desc - - val ascId = asc("_id") - val descId = desc("_id") - - val naturalAsc = asc("$natural") - val naturalDesc = desc("$natural") - val naturalOrder = naturalDesc - - val createdAsc = asc("createdAt") - val createdDesc = desc("createdAt") - val updatedDesc = desc("updatedAt") -} diff --git a/modules/db/src/main/api/update.scala b/modules/db/src/main/api/update.scala deleted file mode 100644 index 0d40eebf50..0000000000 --- a/modules/db/src/main/api/update.scala +++ /dev/null @@ -1,45 +0,0 @@ -package lila.db -package api - -import play.api.libs.json._ -import reactivemongo.bson._ -import Types._ - -object $update { - import play.modules.reactivemongo.json._ - - def apply[ID: Writes, A <: Identified[ID]: JsTubeInColl](doc: A): Funit = - (implicitly[JsTube[A]] toMongo doc).fold(e => fufail(e.toString), - js => apply($select(doc.id), js) - ) - def apply[A <: Identified[String]: JsTubeInColl](doc: A): Funit = apply[String, A](doc) - - def apply[A: InColl, B: BSONDocumentWriter](selector: JsObject, update: B, upsert: Boolean = false, multi: Boolean = false): Funit = - implicitly[InColl[A]].coll.update(selector, update, upsert = upsert, multi = multi).void - - def doc[ID: Writes, A <: Identified[ID]: TubeInColl](id: ID)(op: A => JsObject): Funit = - $find byId id flatten "[db] cannot update missing doc" flatMap { doc => - apply($select(id), op(doc)) - } - - def docBson[ID: Writes, A <: Identified[ID]: TubeInColl](id: ID)(op: A => BSONDocument): Funit = - $find byId id flatten "[db] cannot update missing doc" flatMap { doc => - apply($select(id), op(doc)) - } - - def field[ID: Writes, A: InColl, B: Writes](id: ID, name: String, value: B, upsert: Boolean = false): Funit = - apply($select(id), $set(name -> value), upsert = upsert) - - def bsonField[ID: Writes, A: InColl](id: ID, name: String, value: BSONValue, upsert: Boolean = false): Funit = - apply($select(id), BSONDocument("$set" -> BSONDocument(name -> value)), upsert = upsert) - - // UNCHECKED - - def unchecked[A: InColl, B: BSONDocumentWriter](selector: JsObject, update: B, upsert: Boolean = false, multi: Boolean = false) { - implicitly[InColl[A]].coll.uncheckedUpdate(selector, update, upsert = upsert, multi = multi) - } - - def fieldUnchecked[ID: Writes, A: InColl, B: Writes](id: ID, name: String, value: B, upsert: Boolean = false) { - unchecked($select(id), $set(name -> value), upsert = upsert) - } -} diff --git a/modules/db/src/main/dsl.scala b/modules/db/src/main/dsl.scala new file mode 100644 index 0000000000..b5695b9f37 --- /dev/null +++ b/modules/db/src/main/dsl.scala @@ -0,0 +1,379 @@ +// Copyright (C) 2014 Fehmi Can Saglam (@fehmicans) and contributors. +// See the LICENCE.txt file distributed with this work for additional +// information regarding copyright ownership. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package lila.db + +import ornicar.scalalib.Zero +import reactivemongo.api._ +import reactivemongo.api.collections.GenericQueryBuilder +import reactivemongo.bson._ + +trait dsl { + + type Coll = reactivemongo.api.collections.bson.BSONCollection + type Bdoc = BSONDocument + type Barr = BSONArray + + type QueryBuilder = GenericQueryBuilder[BSONSerializationPack.type] + + type BSONValueReader[A] = BSONReader[_ <: BSONValue, A] + type BSONValueWriter[A] = BSONWriter[A, _ <: BSONValue] + type BSONValueHandler[A] = BSONHandler[_ <: BSONValue, A] + + type BSONDocumentHandler[A] = BSONDocumentReader[A] with BSONDocumentWriter[A] + + implicit val LilaBSONDocumentZero: Zero[BSONDocument] = + Zero.instance($doc()) + + implicit def bsonDocumentToPretty(document: BSONDocument): String = { + BSONDocument.pretty(document) + } + + //**********************************************************************************************// + // Helpers + def $empty: BSONDocument = BSONDocument.empty + + def $doc(elements: Producer[BSONElement]*): BSONDocument = BSONDocument(elements: _*) + + def $doc(elements: Traversable[BSONElement]): BSONDocument = BSONDocument(elements) + + def $arr(elements: Producer[BSONValue]*): BSONArray = { + BSONArray(elements: _*) + } + + def $id[T](id: T)(implicit writer: BSONWriter[T, _ <: BSONValue]): BSONDocument = $doc("_id" -> id) + + def $inIds[T](ids: Iterable[T])(implicit writer: BSONWriter[T, _ <: BSONValue]): BSONDocument = + $id($doc("$in" -> ids)) + + def $boolean(b: Boolean) = BSONBoolean(b) + def $string(s: String) = BSONString(s) + def $int(i: Int) = BSONInteger(i) + + // End of Helpers + //**********************************************************************************************// + + //**********************************************************************************************// + // Top Level Logical Operators + def $or(expressions: BSONDocument*): BSONDocument = { + $doc("$or" -> expressions) + } + + def $and(expressions: BSONDocument*): BSONDocument = { + $doc("$and" -> expressions) + } + + def $nor(expressions: BSONDocument*): BSONDocument = { + $doc("$nor" -> expressions) + } + // End of Top Level Logical Operators + //**********************************************************************************************// + + //**********************************************************************************************// + // Top Level Evaluation Operators + def $text(search: String): BSONDocument = { + $doc("$text" -> $doc("$search" -> search)) + } + + def $text(search: String, language: String): BSONDocument = { + $doc("$text" -> $doc("$search" -> search, "$language" -> language)) + } + + def $where(expression: String): BSONDocument = { + $doc("$where" -> expression) + } + // End of Top Level Evaluation Operators + //**********************************************************************************************// + + //**********************************************************************************************// + // Top Level Field Update Operators + def $inc(item: Producer[BSONElement], items: Producer[BSONElement]*): BSONDocument = { + $doc("$inc" -> $doc((Seq(item) ++ items): _*)) + } + def $inc(items: Iterable[BSONElement]): BSONDocument = { + $doc("$inc" -> $doc(items)) + } + + def $mul(item: Producer[BSONElement]): BSONDocument = { + $doc("$mul" -> $doc(item)) + } + + def $rename(item: (String, String), items: (String, String)*)(implicit writer: BSONWriter[String, _ <: BSONValue]): BSONDocument = { + $doc("$rename" -> $doc((Seq(item) ++ items).map(Producer.nameValue2Producer[String]): _*)) + } + + def $setOnInsert(item: Producer[BSONElement], items: Producer[BSONElement]*): BSONDocument = { + $doc("$setOnInsert" -> $doc((Seq(item) ++ items): _*)) + } + + def $set(item: Producer[BSONElement], items: Producer[BSONElement]*): BSONDocument = { + $doc("$set" -> $doc((Seq(item) ++ items): _*)) + } + + def $unset(field: String, fields: String*): BSONDocument = { + $doc("$unset" -> $doc((Seq(field) ++ fields).map(_ -> BSONString("")))) + } + + def $min(item: Producer[BSONElement]): BSONDocument = { + $doc("$min" -> $doc(item)) + } + + def $max(item: Producer[BSONElement]): BSONDocument = { + $doc("$max" -> $doc(item)) + } + + // Helpers + def $eq[T](value: T)(implicit writer: BSONWriter[T, _ <: BSONValue]) = $doc("$eq" -> value) + + def $gt[T](value: T)(implicit writer: BSONWriter[T, _ <: BSONValue]) = $doc("$gt" -> value) + + /** Matches values that are greater than or equal to the value specified in the query. */ + def $gte[T](value: T)(implicit writer: BSONWriter[T, _ <: BSONValue]) = $doc("$gte" -> value) + + /** Matches any of the values that exist in an array specified in the query.*/ + def $in[T](values: T*)(implicit writer: BSONWriter[T, _ <: BSONValue]) = $doc("$in" -> values) + + /** Matches values that are less than the value specified in the query. */ + def $lt[T](value: T)(implicit writer: BSONWriter[T, _ <: BSONValue]) = $doc("$lt" -> value) + + /** Matches values that are less than or equal to the value specified in the query. */ + def $lte[T](value: T)(implicit writer: BSONWriter[T, _ <: BSONValue]) = $doc("$lte" -> value) + + /** Matches all values that are not equal to the value specified in the query. */ + def $ne[T](value: T)(implicit writer: BSONWriter[T, _ <: BSONValue]) = $doc("$ne" -> value) + + /** Matches values that do not exist in an array specified to the query. */ + def $nin[T](values: T*)(implicit writer: BSONWriter[T, _ <: BSONValue]) = $doc("$nin" -> values) + + def $exists(value: Boolean) = $doc("$exists" -> value) + + trait CurrentDateValueProducer[T] { + def produce: BSONValue + } + + implicit class BooleanCurrentDateValueProducer(value: Boolean) extends CurrentDateValueProducer[Boolean] { + def produce: BSONValue = BSONBoolean(value) + } + + implicit class StringCurrentDateValueProducer(value: String) extends CurrentDateValueProducer[String] { + def isValid: Boolean = Seq("date", "timestamp") contains value + + def produce: BSONValue = { + if (!isValid) + throw new IllegalArgumentException(value) + + $doc("$type" -> value) + } + } + + def $currentDate(items: (String, CurrentDateValueProducer[_])*): BSONDocument = { + $doc("$currentDate" -> $doc(items.map(item => item._1 -> item._2.produce))) + } + // End of Top Level Field Update Operators + //**********************************************************************************************// + + //**********************************************************************************************// + // Top Level Array Update Operators + def $addToSet(item: Producer[BSONElement], items: Producer[BSONElement]*): BSONDocument = { + $doc("$addToSet" -> $doc((Seq(item) ++ items): _*)) + } + + def $pop(item: (String, Int)): BSONDocument = { + if (item._2 != -1 && item._2 != 1) + throw new IllegalArgumentException(s"${item._2} is not equal to: -1 | 1") + + $doc("$pop" -> $doc(item)) + } + + def $push(item: Producer[BSONElement]): BSONDocument = { + $doc("$push" -> $doc(item)) + } + + def $pushEach[T](field: String, values: T*)(implicit writer: BSONWriter[T, _ <: BSONValue]): BSONDocument = { + $doc( + "$push" -> $doc( + field -> $doc( + "$each" -> values + ) + ) + ) + } + + def $pull(item: Producer[BSONElement]): BSONDocument = { + $doc("$pull" -> $doc(item)) + } + // End ofTop Level Array Update Operators + //**********************************************************************************************// + + /** + * Represents the inital state of the expression which has only the name of the field. + * It does not know the value of the expression. + */ + trait ElementBuilder { + def field: String + def append(value: BSONDocument): BSONDocument = value + } + + /** Represents the state of an expression which has a field and a value */ + trait Expression[V <: BSONValue] extends ElementBuilder { + def value: V + def toBdoc(implicit writer: BSONWriter[V, _ <: BSONValue]) = toBSONDocument(this) + } + + /* + * This type of expressions cannot be cascaded. Examples: + * + * {{{ + * "price" $eq 10 + * "price" $ne 1000 + * "size" $in ("S", "M", "L") + * "size" $nin ("S", "XXL") + * }}} + * + */ + case class SimpleExpression[V <: BSONValue](field: String, value: V) + extends Expression[V] + + /** + * Expressions of this type can be cascaded. Examples: + * + * {{{ + * "age" $gt 50 $lt 60 + * "age" $gte 50 $lte 60 + * }}} + * + */ + case class CompositeExpression(field: String, value: BSONDocument) + extends Expression[BSONDocument] + with ComparisonOperators { + override def append(value: BSONDocument): BSONDocument = { + this.value ++ value + } + } + + /** MongoDB comparison operators. */ + trait ComparisonOperators { self: ElementBuilder => + + def $eq[T](value: T)(implicit writer: BSONWriter[T, _ <: BSONValue]): SimpleExpression[BSONValue] = { + SimpleExpression(field, writer.write(value)) + } + + /** Matches values that are greater than the value specified in the query. */ + def $gt[T](value: T)(implicit writer: BSONWriter[T, _ <: BSONValue]): CompositeExpression = { + CompositeExpression(field, append($doc("$gt" -> value))) + } + + /** Matches values that are greater than or equal to the value specified in the query. */ + def $gte[T](value: T)(implicit writer: BSONWriter[T, _ <: BSONValue]): CompositeExpression = { + CompositeExpression(field, append($doc("$gte" -> value))) + } + + /** Matches any of the values that exist in an array specified in the query.*/ + def $in[T](values: T*)(implicit writer: BSONWriter[T, _ <: BSONValue]): SimpleExpression[BSONDocument] = { + SimpleExpression(field, $doc("$in" -> values)) + } + + /** Matches values that are less than the value specified in the query. */ + def $lt[T](value: T)(implicit writer: BSONWriter[T, _ <: BSONValue]): CompositeExpression = { + CompositeExpression(field, append($doc("$lt" -> value))) + } + + /** Matches values that are less than or equal to the value specified in the query. */ + def $lte[T](value: T)(implicit writer: BSONWriter[T, _ <: BSONValue]): CompositeExpression = { + CompositeExpression(field, append($doc("$lte" -> value))) + } + + /** Matches all values that are not equal to the value specified in the query. */ + def $ne[T](value: T)(implicit writer: BSONWriter[T, _ <: BSONValue]): SimpleExpression[BSONDocument] = { + SimpleExpression(field, $doc("$ne" -> value)) + } + + /** Matches values that do not exist in an array specified to the query. */ + def $nin[T](values: T*)(implicit writer: BSONWriter[T, _ <: BSONValue]): SimpleExpression[BSONDocument] = { + SimpleExpression(field, $doc("$nin" -> values)) + } + + } + + trait LogicalOperators { self: ElementBuilder => + def $not(f: (String => Expression[BSONDocument])): SimpleExpression[BSONDocument] = { + val expression = f(field) + SimpleExpression(field, $doc("$not" -> expression.value)) + } + } + + trait ElementOperators { self: ElementBuilder => + def $exists(exists: Boolean): SimpleExpression[BSONDocument] = { + SimpleExpression(field, $doc("$exists" -> exists)) + } + } + + trait EvaluationOperators { self: ElementBuilder => + def $mod(divisor: Int, remainder: Int): SimpleExpression[BSONDocument] = { + SimpleExpression(field, $doc("$mod" -> BSONArray(divisor, remainder))) + } + + def $regex(value: String, options: String): SimpleExpression[BSONRegex] = { + SimpleExpression(field, BSONRegex(value, options)) + } + } + + trait ArrayOperators { self: ElementBuilder => + def $all[T](values: T*)(implicit writer: BSONWriter[T, _ <: BSONValue]): SimpleExpression[BSONDocument] = { + SimpleExpression(field, $doc("$all" -> values)) + } + + def $elemMatch(query: Producer[BSONElement]*): SimpleExpression[BSONDocument] = { + SimpleExpression(field, $doc("$elemMatch" -> $doc(query: _*))) + } + + def $size(size: Int): SimpleExpression[BSONDocument] = { + SimpleExpression(field, $doc("$size" -> size)) + } + } + + object $sort { + + def asc(field: String) = $doc(field -> 1) + def desc(field: String) = $doc(field -> -1) + + val naturalAsc = asc("$natural") + val naturalDesc = desc("$natural") + val naturalOrder = naturalDesc + + val createdAsc = asc("createdAt") + val createdDesc = desc("createdAt") + val updatedDesc = desc("updatedAt") + } + + implicit class ElementBuilderLike(val field: String) + extends ElementBuilder + with ComparisonOperators + with ElementOperators + with EvaluationOperators + with LogicalOperators + with ArrayOperators + + implicit def toBSONElement[V <: BSONValue](expression: Expression[V])(implicit writer: BSONWriter[V, _ <: BSONValue]): Producer[BSONElement] = { + expression.field -> expression.value + } + + implicit def toBSONDocument[V <: BSONValue](expression: Expression[V])(implicit writer: BSONWriter[V, _ <: BSONValue]): BSONDocument = + $doc(expression.field -> expression.value) + +} + +object dsl extends dsl with CollExt with QueryBuilderExt with CursorExt with Handlers diff --git a/modules/db/src/main/package.scala b/modules/db/src/main/package.scala index 1cc573d7cf..d27d95285a 100644 --- a/modules/db/src/main/package.scala +++ b/modules/db/src/main/package.scala @@ -4,9 +4,6 @@ import reactivemongo.api._ import reactivemongo.api.commands.WriteResult package object db extends PackageObject with WithPlay { - type TubeInColl[A] = Tube[A] with InColl[A] - type JsTubeInColl[A] = JsTube[A] with InColl[A] - type BsTubeInColl[A] = BsTube[A] with InColl[A] def recoverDuplicateKey[A](f: WriteResult => A): PartialFunction[Throwable, A] = { case e: WriteResult if e.code.contains(11000) => f(e) diff --git a/modules/db/src/main/typesAndImplicits.scala b/modules/db/src/main/typesAndImplicits.scala deleted file mode 100644 index a312f275fc..0000000000 --- a/modules/db/src/main/typesAndImplicits.scala +++ /dev/null @@ -1,60 +0,0 @@ -package lila.db - -import play.api.libs.json._ -import reactivemongo.api._ -import reactivemongo.api.collections.GenericQueryBuilder -import reactivemongo.bson._ -import ornicar.scalalib.Zero - -object Types extends Types -object Implicits extends Implicits - -trait Types { - type Coll = reactivemongo.api.collections.bson.BSONCollection - - type QueryBuilder = GenericQueryBuilder[BSONSerializationPack.type] - - type Identified[ID] = { def id: ID } - - type Sort = Seq[(String, api.SortOrder)] - - type BSONValueReader[A] = BSONReader[_ <: BSONValue, A] - type BSONValueHandler[A] = BSONHandler[_ <: BSONValue, A] -} - -trait Implicits extends Types { - - implicit val LilaBSONDocumentZero: Zero[BSONDocument] = - Zero.instance(BSONDocument()) - - implicit def docId[ID](doc: Identified[ID]): ID = doc.id - - def pimpQB(b: QueryBuilder) = new LilaPimpedQueryBuilder(b) - - // hack, this should be in reactivemongo - implicit final class LilaPimpedQueryBuilder(b: QueryBuilder) { - - def sort(sorters: (String, api.SortOrder)*): QueryBuilder = - if (sorters.size == 0) b - else b sort { - BSONDocument( - (for (sorter ← sorters) yield sorter._1 -> BSONInteger( - sorter._2 match { - case api.SortOrder.Ascending => 1 - case api.SortOrder.Descending => -1 - })).toStream) - } - - def skip(nb: Int): QueryBuilder = b.options(b.options skip nb) - - def batch(nb: Int): QueryBuilder = b.options(b.options batchSize nb) - - def toList[A: BSONDocumentReader](limit: Option[Int], readPreference: ReadPreference = ReadPreference.primary): Fu[List[A]] = - limit.fold(b.cursor[A](readPreference = readPreference).collect[List]()) { l => - batch(l).cursor[A](readPreference = readPreference).collect[List](l) - } - - def toListFlatten[A: Tube](limit: Option[Int]): Fu[List[A]] = - toList[Option[A]](limit) map (_.flatten) - } -} diff --git a/modules/db/src/test/DateTest.scala b/modules/db/src/test/DateTest.scala deleted file mode 100644 index 4edbd8932a..0000000000 --- a/modules/db/src/test/DateTest.scala +++ /dev/null @@ -1,29 +0,0 @@ -package lila.db - -import api._ -import Implicits._ -import org.joda.time.DateTime -import org.specs2.execute.{ Result, AsResult } -import org.specs2.mutable._ -import org.specs2.specification._ -import play.api.libs.json._ -import play.api.test._ -import reactivemongo.api._ -import reactivemongo.bson._ - -class DateTest extends Specification { - - val date = DateTime.now - import play.modules.reactivemongo.json.ImplicitBSONHandlers._ - - "date conversion" should { - "js to bson" in { - val doc = JsObjectWriter.write(Json.obj( - "ca" -> $gt($date(date)) - )) - doc.getAsTry[BSONDocument]("ca") flatMap { gt => - gt.getAsTry[BSONDateTime]("$gt") - } must_== scala.util.Success(BSONDateTime(date.getMillis)) - } - } -} diff --git a/modules/db/src/test/DbApiTest.scala b/modules/db/src/test/DbApiTest.scala deleted file mode 100644 index 0e67dbb2b5..0000000000 --- a/modules/db/src/test/DbApiTest.scala +++ /dev/null @@ -1,26 +0,0 @@ -package lila.db - -import org.joda.time.DateTime -import org.specs2.mutable._ -import play.api.libs.json._ -import play.api.test._ -import Types._ - -class DbApiTest extends Specification { - - import api._ - - val date = DateTime.now - - "operators" should { - - "$set" in { - $set("foo" -> "bar") must_== Json.obj( - "$set" -> Json.obj("foo" -> "bar")) - } - // "$set DateTime" in new WithApplication { - // $set("foo" -> date) must_== Json.obj( - // "$set" -> Json.obj("foo" -> Json.obj("$date" -> date.getMillis))) - // } - } -} diff --git a/modules/donation/src/main/DonationApi.scala b/modules/donation/src/main/DonationApi.scala index ff6d7bca28..723d0ddaa6 100644 --- a/modules/donation/src/main/DonationApi.scala +++ b/modules/donation/src/main/DonationApi.scala @@ -7,7 +7,7 @@ import scala.concurrent.duration._ import scala.util.Try import lila.db.BSON.BSONJodaDateTimeHandler -import lila.db.Types.Coll +import lila.db.dsl._ final class DonationApi( coll: Coll, @@ -37,7 +37,7 @@ final class DonationApi( def list(nb: Int) = coll.find(decentAmount) .sort(BSONDocument("date" -> -1)) .cursor[Donation]() - .collect[List](nb) + .gather[List](nb) def top(nb: Int): Fu[List[lila.user.User.ID]] = coll.aggregate( Match(BSONDocument("userId" -> BSONDocument("$exists" -> true))), List( @@ -84,7 +84,7 @@ final class DonationApi( "$lt" -> to )), BSONDocument("net" -> true, "_id" -> false) - ).cursor[BSONDocument]().collect[List]() map2 { (obj: BSONDocument) => + ).cursor[BSONDocument]().gather[List]() map2 { (obj: BSONDocument) => ~obj.getAs[Int]("net") } map (_.sum) map { amount => Progress(from, weeklyGoal, amount) diff --git a/modules/explorer/src/main/Env.scala b/modules/explorer/src/main/Env.scala index b21d3a209c..ff46306d52 100644 --- a/modules/explorer/src/main/Env.scala +++ b/modules/explorer/src/main/Env.scala @@ -5,6 +5,7 @@ import com.typesafe.config.Config final class Env( config: Config, + gameColl: lila.db.dsl.Coll, system: ActorSystem) { private val Endpoint = config getString "endpoint" @@ -12,6 +13,7 @@ final class Env( private val IndexFlow = config getBoolean "index_flow" private lazy val indexer = new ExplorerIndexer( + gameColl = gameColl, endpoint = Endpoint, massImportEndpoint = MassImportEndpoint) @@ -41,5 +43,6 @@ object Env { lazy val current = "explorer" boot new Env( config = lila.common.PlayApp loadConfig "explorer", + gameColl = lila.game.Env.current.gameColl, system = lila.common.PlayApp.system) } diff --git a/modules/explorer/src/main/ExplorerIndexer.scala b/modules/explorer/src/main/ExplorerIndexer.scala index 2727ca3448..07b9c04d90 100644 --- a/modules/explorer/src/main/ExplorerIndexer.scala +++ b/modules/explorer/src/main/ExplorerIndexer.scala @@ -10,14 +10,13 @@ import play.api.libs.iteratee._ import play.api.libs.ws.WS import play.api.Play.current -import lila.db.api._ -import lila.db.Implicits._ +import lila.db.dsl._ import lila.game.BSONHandlers.gameBSONHandler -import lila.game.tube.gameTube import lila.game.{ Game, GameRepo, Query, PgnDump, Player } import lila.user.UserRepo private final class ExplorerIndexer( + gameColl: Coll, endpoint: String, massImportEndpoint: String) { @@ -40,16 +39,15 @@ private final class ExplorerIndexer( def apply(sinceStr: String): Funit = parseDate(sinceStr).fold(fufail[Unit](s"Invalid date $sinceStr")) { since => logger.info(s"Start indexing since $since") - val query = $query( + val query = Query.createdSince(since) ++ Query.rated ++ Query.finished ++ Query.turnsMoreThan(8) ++ Query.noProvisional ++ Query.bothRatingsGreaterThan(1501) - ) import reactivemongo.api._ - pimpQB(query) + gameColl.find($empty) .sort(Query.sortChronological) .cursor[Game](ReadPreference.secondaryPreferred) .enumerate(maxGames, stopOnError = true) &> diff --git a/modules/fishnet/src/main/Cleaner.scala b/modules/fishnet/src/main/Cleaner.scala index 6cb7d77176..8da8af3f4c 100644 --- a/modules/fishnet/src/main/Cleaner.scala +++ b/modules/fishnet/src/main/Cleaner.scala @@ -5,7 +5,7 @@ import reactivemongo.bson._ import scala.concurrent.duration._ import lila.db.BSON.BSONJodaDateTimeHandler -import lila.db.Implicits._ +import lila.db.dsl._ private final class Cleaner( repo: FishnetRepo, @@ -32,7 +32,7 @@ private final class Cleaner( private def cleanAnalysis: Funit = analysisColl.find(BSONDocument( "acquired.date" -> BSONDocument("$lt" -> durationAgo(analysisTimeoutBase)) - )).sort(BSONDocument("acquired.date" -> 1)).cursor[Work.Analysis]().collect[List](100).flatMap { + )).sort(BSONDocument("acquired.date" -> 1)).cursor[Work.Analysis]().gather[List](100).flatMap { _.filter { ana => ana.acquiredAt.??(_ isBefore durationAgo(analysisTimeout(ana.nbPly))) }.map { ana => diff --git a/modules/fishnet/src/main/FishnetApi.scala b/modules/fishnet/src/main/FishnetApi.scala index 56c3bff8be..56f99b5358 100644 --- a/modules/fishnet/src/main/FishnetApi.scala +++ b/modules/fishnet/src/main/FishnetApi.scala @@ -7,7 +7,7 @@ import scala.concurrent.Future import scala.util.{ Try, Success, Failure } import Client.Skill -import lila.db.Implicits._ +import lila.db.dsl._ import lila.hub.FutureSequencer final class FishnetApi( @@ -63,7 +63,7 @@ final class FishnetApi( )).sort(BSONDocument( "sender.system" -> 1, // user requests first, then lichess auto analysis "createdAt" -> 1 // oldest requests first - )).one[Work.Analysis].flatMap { + )).uno[Work.Analysis].flatMap { _ ?? { work => repo.updateAnalysis(work assignTo client) inject work.some } diff --git a/modules/fishnet/src/main/FishnetRepo.scala b/modules/fishnet/src/main/FishnetRepo.scala index 812bbb0d0a..3f6c72e981 100644 --- a/modules/fishnet/src/main/FishnetRepo.scala +++ b/modules/fishnet/src/main/FishnetRepo.scala @@ -5,7 +5,7 @@ import reactivemongo.bson._ import scala.concurrent.duration._ import lila.db.BSON.BSONJodaDateTimeHandler -import lila.db.Implicits._ +import lila.db.dsl._ import lila.memo.AsyncCache private final class FishnetRepo( @@ -15,7 +15,7 @@ private final class FishnetRepo( import BSONHandlers._ private val clientCache = AsyncCache[Client.Key, Option[Client]]( - f = key => clientColl.find(selectClient(key)).one[Client], + f = key => clientColl.find(selectClient(key)).uno[Client], timeToLive = 10 seconds) def getClient(key: Client.Key) = clientCache(key) @@ -33,14 +33,14 @@ private final class FishnetRepo( clientColl.update(selectClient(key), BSONDocument("$set" -> BSONDocument("enabled" -> v))).void >> clientCache.remove(key) def allRecentClients = clientColl.find(BSONDocument( "instance.seenAt" -> BSONDocument("$gt" -> Client.Instance.recentSince) - )).cursor[Client]().collect[List]() + )).cursor[Client]().gather[List]() def lichessClients = clientColl.find(BSONDocument( "enabled" -> true, "userId" -> BSONDocument("$regex" -> "^lichess-") - )).cursor[Client]().collect[List]() + )).cursor[Client]().gather[List]() def addAnalysis(ana: Work.Analysis) = analysisColl.insert(ana).void - def getAnalysis(id: Work.Id) = analysisColl.find(selectWork(id)).one[Work.Analysis] + def getAnalysis(id: Work.Id) = analysisColl.find(selectWork(id)).uno[Work.Analysis] def updateAnalysis(ana: Work.Analysis) = analysisColl.update(selectWork(ana.id), ana).void def deleteAnalysis(ana: Work.Analysis) = analysisColl.remove(selectWork(ana.id)).void def giveUpAnalysis(ana: Work.Analysis) = deleteAnalysis(ana) >>- logger.warn(s"Give up on analysis $ana") @@ -50,10 +50,10 @@ private final class FishnetRepo( ).some) def getAnalysisByGameId(gameId: String) = analysisColl.find(BSONDocument( "game.id" -> gameId - )).one[Work.Analysis] + )).uno[Work.Analysis] def getSimilarAnalysis(work: Work.Analysis): Fu[Option[Work.Analysis]] = - analysisColl.find(BSONDocument("game.id" -> work.game.id)).one[Work.Analysis] + analysisColl.find(BSONDocument("game.id" -> work.game.id)).uno[Work.Analysis] def selectWork(id: Work.Id) = BSONDocument("_id" -> id.value) def selectClient(key: Client.Key) = BSONDocument("_id" -> key.value) diff --git a/modules/fishnet/src/main/Limiter.scala b/modules/fishnet/src/main/Limiter.scala index e81a3e3e3a..eb976d2bbe 100644 --- a/modules/fishnet/src/main/Limiter.scala +++ b/modules/fishnet/src/main/Limiter.scala @@ -2,7 +2,7 @@ package lila.fishnet import reactivemongo.bson._ -import lila.db.Implicits.Coll +import lila.db.dsl.Coll private final class Limiter(analysisColl: Coll) { diff --git a/modules/fishnet/src/main/Monitor.scala b/modules/fishnet/src/main/Monitor.scala index 242000e93a..d1716e4d24 100644 --- a/modules/fishnet/src/main/Monitor.scala +++ b/modules/fishnet/src/main/Monitor.scala @@ -4,7 +4,7 @@ import org.joda.time.DateTime import reactivemongo.bson._ import scala.concurrent.duration._ -import lila.db.Implicits._ +import lila.db.dsl._ private final class Monitor( moveDb: MoveDB, diff --git a/modules/forum/src/main/BSONHandlers.scala b/modules/forum/src/main/BSONHandlers.scala new file mode 100644 index 0000000000..62d2230d57 --- /dev/null +++ b/modules/forum/src/main/BSONHandlers.scala @@ -0,0 +1,9 @@ +package lila.forum + +private object BSONHandlers { + + import lila.db.BSON.BSONJodaDateTimeHandler + implicit val CategBSONHandler = reactivemongo.bson.Macros.handler[Categ] + implicit val TopicBSONHandler = reactivemongo.bson.Macros.handler[Topic] + implicit val PostBSONHandler = reactivemongo.bson.Macros.handler[Post] +} diff --git a/modules/forum/src/main/Categ.scala b/modules/forum/src/main/Categ.scala index de0890e936..b73faa8547 100644 --- a/modules/forum/src/main/Categ.scala +++ b/modules/forum/src/main/Categ.scala @@ -1,7 +1,7 @@ package lila.forum case class Categ( - id: String, // slug + _id: String, // slug name: String, desc: String, pos: Int, @@ -13,6 +13,8 @@ case class Categ( nbPostsTroll: Int, lastPostIdTroll: String) { + def id = _id + def nbTopics(troll: Boolean): Int = troll.fold(nbTopicsTroll, nbTopics) def nbPosts(troll: Boolean): Int = troll.fold(nbPostsTroll, nbPosts) def lastPostId(troll: Boolean): String = troll.fold(lastPostIdTroll, lastPostId) @@ -31,19 +33,3 @@ case class Categ( def slug = id } - -object Categ { - - import lila.db.JsTube - import JsTube.Helpers._ - import play.api.libs.json._ - - private implicit def topicTube = Topic.tube - - private def defaults = Json.obj("team" -> none[String]) - - private[forum] lazy val tube = JsTube( - reader = (__.json update merge(defaults)) andThen Json.reads[Categ], - writer = Json.writes[Categ] - ) -} diff --git a/modules/forum/src/main/CategApi.scala b/modules/forum/src/main/CategApi.scala index b980e01b0c..426a571b21 100644 --- a/modules/forum/src/main/CategApi.scala +++ b/modules/forum/src/main/CategApi.scala @@ -1,14 +1,14 @@ package lila.forum import lila.common.paginator._ -import lila.db.api._ -import lila.db.Implicits._ +import lila.db.dsl._ import lila.db.paginator._ import lila.user.{ User, UserContext } -import tube._ private[forum] final class CategApi(env: Env) { + import BSONHandlers._ + def list(teams: Set[String], troll: Boolean): Fu[List[CategView]] = for { categs ← CategRepo withTeams teams views ← (categs map { categ => @@ -27,7 +27,7 @@ private[forum] final class CategApi(env: Env) { def makeTeam(slug: String, name: String): Funit = CategRepo.nextPosition flatMap { position => val categ = Categ( - id = teamSlug(slug), + _id = teamSlug(slug), name = name, desc = "Forum of the team " + name, pos = position, @@ -55,10 +55,10 @@ private[forum] final class CategApi(env: Env) { hidden = topic.hidden, lang = "en".some, categId = categ.id) - $insert(categ) >> - $insert(post) >> - $insert(topic withPost post) >> - $update(categ withTopic post) + env.categColl.insert(categ).void >> + env.postColl.insert(post).void >> + env.topicColl.insert(topic withPost post).void >> + env.categColl.update($id(categ.id), categ withTopic post).void } def show(slug: String, page: Int, troll: Boolean): Fu[Option[(Categ, Paginator[TopicView])]] = @@ -75,17 +75,13 @@ private[forum] final class CategApi(env: Env) { topicIdsTroll = topicsTroll map (_.id) nbPostsTroll ← PostRepoTroll countByTopics topicIdsTroll lastPostTroll ← PostRepoTroll lastByTopics topicIdsTroll - _ ← $update(categ.copy( + _ ← env.categColl.update($id(categ.id), categ.copy( nbTopics = topics.size, nbPosts = nbPosts, lastPostId = lastPost ?? (_.id), nbTopicsTroll = topicsTroll.size, nbPostsTroll = nbPostsTroll, lastPostIdTroll = lastPostTroll ?? (_.id) - )) + )).void } yield () - - def denormalize: Funit = $find.all[Categ] flatMap { categs => - categs.map(denormalize).sequenceFu - } void } diff --git a/modules/forum/src/main/CategRepo.scala b/modules/forum/src/main/CategRepo.scala index 89c8f33c70..e4ef8bce6f 100644 --- a/modules/forum/src/main/CategRepo.scala +++ b/modules/forum/src/main/CategRepo.scala @@ -1,27 +1,25 @@ package lila.forum -import play.api.libs.json.Json - -import lila.db.api._ -import lila.db.Implicits._ -import tube.categTube +import lila.db.dsl._ object CategRepo { - def bySlug(slug: String) = $find byId slug + import BSONHandlers.CategBSONHandler + + // dirty + private val coll = Env.current.categColl + + def bySlug(slug: String) = coll.byId[Categ](slug) def withTeams(teams: Set[String]): Fu[List[Categ]] = - $find($query($or(Seq( - Json.obj("team" -> $exists(false)), - Json.obj("team" -> $in(teams)) - ))) sort $sort.asc("pos")) + coll.find($or( + "team" $exists false, + $doc("team" -> $doc("$in" -> teams)) + )).sort($sort asc "pos").cursor[Categ]().gather[List]() - def nextPosition: Fu[Int] = $primitive.one( - $select.all, - "pos", - _ sort $sort.desc("pos") - )(_.asOpt[Int]) map (~_ + 1) + def nextPosition: Fu[Int] = + coll.primitiveOne[Int]($empty, $sort desc "pos", "pos") map (~_ + 1) def nbPosts(id: String): Fu[Int] = - $primitive.one($select(id), "nbPosts")(_.asOpt[Int]) map (~_) + coll.primitiveOne[Int]($id(id), "nbPosts") map (~_) } diff --git a/modules/forum/src/main/Env.scala b/modules/forum/src/main/Env.scala index 11d3125800..9ae2840591 100644 --- a/modules/forum/src/main/Env.scala +++ b/modules/forum/src/main/Env.scala @@ -54,14 +54,6 @@ final class Env( lazy val forms = new DataForm(hub.actor.captcher) lazy val recent = new Recent(postApi, RecentTtl, RecentNb, PublicCategIds) - def cli = new lila.common.Cli { - import tube._ - def process = { - case "forum" :: "denormalize" :: Nil => - topicApi.denormalize >> categApi.denormalize inject "Forum denormalized" - } - } - system.actorOf(Props(new Actor { def receive = { case MakeTeam(id, name) => categApi.makeTeam(id, name) diff --git a/modules/forum/src/main/Post.scala b/modules/forum/src/main/Post.scala index 57dba1547f..1a2d6614b8 100644 --- a/modules/forum/src/main/Post.scala +++ b/modules/forum/src/main/Post.scala @@ -6,7 +6,7 @@ import ornicar.scalalib.Random import lila.user.User case class Post( - id: String, + _id: String, topicId: String, categId: String, author: Option[String], @@ -19,6 +19,8 @@ case class Post( lang: Option[String], createdAt: DateTime) { + def id = _id + def showAuthor = (author map (_.trim) filter ("" !=)) | User.anonymous def showUserIdOrAuthor = userId | showAuthor @@ -43,7 +45,7 @@ object Post { lang: Option[String], troll: Boolean, hidden: Boolean): Post = Post( - id = Random nextStringUppercase idSize, + _id = Random nextStringUppercase idSize, topicId = topicId, author = author, userId = userId, @@ -55,13 +57,4 @@ object Post { hidden = hidden, createdAt = DateTime.now, categId = categId) - - import lila.db.JsTube - import JsTube.Helpers._ - import play.api.libs.json._ - - private[forum] lazy val tube = JsTube( - (__.json update readDate('createdAt)) andThen Json.reads[Post], - Json.writes[Post] andThen (__.json update writeDate('createdAt)) - ) } diff --git a/modules/forum/src/main/PostApi.scala b/modules/forum/src/main/PostApi.scala index 0f05c6fec4..0cb08bb887 100644 --- a/modules/forum/src/main/PostApi.scala +++ b/modules/forum/src/main/PostApi.scala @@ -3,17 +3,14 @@ package lila.forum import actorApi._ import akka.actor.ActorSelection import org.joda.time.DateTime -import play.api.libs.json._ import lila.common.paginator._ -import lila.db.api._ -import lila.db.Implicits._ +import lila.db.dsl._ import lila.db.paginator._ import lila.hub.actorApi.timeline.{ Propagate, ForumPost } import lila.mod.ModlogApi import lila.security.{ Granter => MasterGranter } import lila.user.{ User, UserContext } -import tube._ final class PostApi( env: Env, @@ -24,6 +21,8 @@ final class PostApi( timeline: ActorSelection, detectLanguage: lila.common.DetectLanguage) { + import BSONHandlers._ + def makePost( categ: Categ, topic: Topic, @@ -44,11 +43,11 @@ final class PostApi( PostRepo findDuplicate post flatMap { case Some(dup) => fuccess(dup) case _ => - $insert(post) >> - $update(topic withPost post) >> { + env.postColl.insert(post) >> + env.topicColl.update($id(topic.id), topic withPost post) >> { shouldHideOnPost(topic) ?? TopicRepo.hide(topic.id, true) } >> - $update(categ withTopic post) >>- + env.categColl.update($id(categ.id), categ withTopic post) >>- (indexer ! InsertPost(post)) >> (env.recent.invalidate inject post) >>- ctx.userId.?? { userId => @@ -89,13 +88,13 @@ final class PostApi( } def get(postId: String): Fu[Option[(Topic, Post)]] = for { - post ← optionT($find.byId[Post](postId)) - topic ← optionT($find.byId[Topic](post.topicId)) + post ← optionT(env.postColl.byId[Post](postId)) + topic ← optionT(env.topicColl.byId[Topic](post.topicId)) } yield topic -> post def views(posts: List[Post]): Fu[List[PostView]] = for { - topics ← $find.byIds[Topic](posts.map(_.topicId).distinct) - categs ← $find.byIds[Categ](topics.map(_.categId).distinct) + topics ← env.topicColl.byIds[Topic](posts.map(_.topicId).distinct) + categs ← env.categColl.byIds[Categ](topics.map(_.categId).distinct) } yield posts map { post => for { topic ← topics find (_.id == post.topicId) @@ -104,13 +103,13 @@ final class PostApi( } flatten def viewsFromIds(postIds: Seq[String]): Fu[List[PostView]] = - $find.byOrderedIds[Post](postIds) flatMap views + env.postColl.byOrderedIds[Post](postIds)(_.id) flatMap views def view(post: Post): Fu[Option[PostView]] = views(List(post)) map (_.headOption) def liteViews(posts: List[Post]): Fu[List[PostLiteView]] = for { - topics ← $find.byIds[Topic](posts.map(_.topicId).distinct) + topics ← env.topicColl.byIds[Topic](posts.map(_.topicId).distinct) } yield posts flatMap { post => topics find (_.id == post.topicId) map { topic => PostLiteView(post, topic) @@ -121,7 +120,7 @@ final class PostApi( liteViews(List(post)) map (_.headOption) def miniPosts(posts: List[Post]): Fu[List[MiniForumPost]] = for { - topics ← $find.byIds[Topic](posts.map(_.topicId).distinct) + topics ← env.topicColl.byIds[Topic](posts.map(_.topicId).distinct) } yield posts flatMap { post => topics find (_.id == post.topicId) map { topic => MiniForumPost( @@ -135,15 +134,17 @@ final class PostApi( } def lastNumberOf(topic: Topic): Fu[Int] = - PostRepo lastByTopics List(topic) map { _ ?? (_.number) } + PostRepo lastByTopics List(topic.id) map { _ ?? (_.number) } def lastPageOf(topic: Topic) = math.ceil(topic.nbPosts / maxPerPage.toFloat).toInt def paginator(topic: Topic, page: Int, troll: Boolean): Fu[Paginator[Post]] = Paginator( new Adapter( - selector = PostRepo(troll) selectTopic topic, - sort = PostRepo.sortQuery :: Nil), + collection = env.postColl, + selector = PostRepo(troll) selectTopic topic.id, + projection = $empty, + sort = PostRepo.sortQuery), currentPage = page, maxPerPage = maxPerPage) @@ -154,17 +155,17 @@ final class PostApi( first ← PostRepo.isFirstPost(view.topic.id, view.post.id) _ ← first.fold( env.topicApi.delete(view.categ, view.topic), - $remove[Post](view.post) >> + env.postColl.remove(view.post) >> (env.topicApi denormalize view.topic) >> (env.categApi denormalize view.categ) >> env.recent.invalidate >>- - (indexer ! RemovePost(post))) - _ ← MasterGranter(_.ModerateForum)(mod) ?? modLog.deletePost(mod, post.userId, post.author, post.ip, + (indexer ! RemovePost(post.id))) + _ ← MasterGranter(_.ModerateForum)(mod) ?? modLog.deletePost(mod.id, post.userId, post.author, post.ip, text = "%s / %s / %s".format(view.categ.name, view.topic.name, post.text)) } yield true.some) } yield ()).run.void - def nbByUser(userId: String) = $count[Post](Json.obj("userId" -> userId)) + def nbByUser(userId: String) = env.postColl.countSel($doc("userId" -> userId)) def userIds(topic: Topic) = PostRepo userIdsByTopicId topic.id } diff --git a/modules/forum/src/main/PostRepo.scala b/modules/forum/src/main/PostRepo.scala index a5d565e48d..a924ae7a06 100644 --- a/modules/forum/src/main/PostRepo.scala +++ b/modules/forum/src/main/PostRepo.scala @@ -1,12 +1,7 @@ package lila.forum -import play.api.libs.json.Json - -import lila.db.api._ -import lila.db.Implicits._ +import lila.db.dsl._ import org.joda.time.DateTime -import reactivemongo.bson.BSONDocument -import tube.postTube object PostRepo extends PostRepo(false) { @@ -17,57 +12,55 @@ object PostRepoTroll extends PostRepo(true) sealed abstract class PostRepo(troll: Boolean) { - private lazy val trollFilter = troll.fold( - Json.obj(), - Json.obj("troll" -> false) - ) + import BSONHandlers.PostBSONHandler + + // dirty + private val coll = Env.current.postColl + + private val trollFilter = troll.fold($empty, $doc("troll" -> false)) def byCategAndId(categSlug: String, id: String): Fu[Option[Post]] = - $find.one(selectCateg(categSlug) ++ $select(id)) + coll.uno[Post](selectCateg(categSlug) ++ $id(id)) def countBeforeNumber(topicId: String, number: Int): Fu[Int] = - $count(selectTopic(topicId) ++ Json.obj("number" -> $lt(number))) + coll.countSel(selectTopic(topicId) ++ $doc("number" -> $lt(number))) def isFirstPost(topicId: String, postId: String): Fu[Boolean] = - $primitive.one( - selectTopic(topicId), - "_id", - _ sort $sort.createdAsc - )(_.asOpt[String]) map { _.??(postId ==) } + coll.primitiveOne[String](selectTopic(topicId), $sort.createdAsc, "_id") map { _ contains postId } def countByTopics(topics: List[String]): Fu[Int] = - $count(selectTopics(topics)) + coll.countSel(selectTopics(topics)) def lastByTopics(topics: List[String]): Fu[Option[Post]] = - $find.one($query(selectTopics(topics)) sort $sort.createdDesc) + coll.find(selectTopics(topics)).sort($sort.createdDesc).uno[Post] def recentInCategs(nb: Int)(categIds: List[String], langs: List[String]): Fu[List[Post]] = - $find($query( + coll.find( selectCategs(categIds) ++ selectLangs(langs) ++ selectNotHidden - ) sort $sort.createdDesc, nb) + ).sort($sort.createdDesc).cursor[Post]().gather[List](nb) - def removeByTopic(topicId: String): Fu[Unit] = - $remove(selectTopic(topicId)) + def removeByTopic(topicId: String): Funit = + coll.remove(selectTopic(topicId)).void - def hideByTopic(topicId: String, value: Boolean): Fu[Unit] = $update( + def hideByTopic(topicId: String, value: Boolean): Funit = coll.update( selectTopic(topicId), - BSONDocument("$set" -> BSONDocument("hidden" -> value)), - multi = true) + $set("hidden" -> value), + multi = true).void - def selectTopic(topicId: String) = Json.obj("topicId" -> topicId) ++ trollFilter - def selectTopics(topicIds: List[String]) = Json.obj("topicId" -> $in(topicIds)) ++ trollFilter + def selectTopic(topicId: String) = $doc("topicId" -> topicId) ++ trollFilter + def selectTopics(topicIds: List[String]) = $doc("topicId" $in (topicIds: _*)) ++ trollFilter - def selectCateg(categId: String) = Json.obj("categId" -> categId) ++ trollFilter - def selectCategs(categIds: List[String]) = Json.obj("categId" -> $in(categIds)) ++ trollFilter + def selectCateg(categId: String) = $doc("categId" -> categId) ++ trollFilter + def selectCategs(categIds: List[String]) = $doc("categId" $in (categIds: _*)) ++ trollFilter - val selectNotHidden = Json.obj("hidden" -> false) + val selectNotHidden = $doc("hidden" -> false) def selectLangs(langs: List[String]) = - if (langs.isEmpty) Json.obj() - else Json.obj("lang" -> $in(langs)) + if (langs.isEmpty) $empty + else $doc("lang" $in (langs: _*)) - def findDuplicate(post: Post): Fu[Option[Post]] = $find.one(Json.obj( - "createdAt" -> $gt($date(DateTime.now.minusHours(1))), + def findDuplicate(post: Post): Fu[Option[Post]] = coll.uno[Post]($doc( + "createdAt" $gt DateTime.now.minusHours(1), "userId" -> ~post.userId, "text" -> post.text )) @@ -75,8 +68,14 @@ sealed abstract class PostRepo(troll: Boolean) { def sortQuery = $sort.createdAsc def userIdsByTopicId(topicId: String): Fu[List[String]] = - postTube.coll.distinct("userId", BSONDocument("topicId" -> topicId).some) map lila.db.BSON.asStrings + coll.distinct("userId", $doc("topicId" -> topicId).some) map lila.db.BSON.asStrings def idsByTopicId(topicId: String): Fu[List[String]] = - postTube.coll.distinct("_id", BSONDocument("topicId" -> topicId).some) map lila.db.BSON.asStrings + coll.distinct("_id", $doc("topicId" -> topicId).some) map lila.db.BSON.asStrings + + import reactivemongo.api.ReadPreference + def cursor( + selector: Bdoc, + readPreference: ReadPreference = ReadPreference.secondaryPreferred) = + coll.find(selector).cursor[Post](readPreference) } diff --git a/modules/forum/src/main/Topic.scala b/modules/forum/src/main/Topic.scala index fcaed0d024..56d04ae0fb 100644 --- a/modules/forum/src/main/Topic.scala +++ b/modules/forum/src/main/Topic.scala @@ -4,7 +4,7 @@ import org.joda.time.DateTime import ornicar.scalalib.Random case class Topic( - id: String, + _id: String, categId: String, slug: String, name: String, @@ -20,6 +20,8 @@ case class Topic( closed: Boolean, hidden: Boolean) { + def id = _id + def updatedAt(troll: Boolean): DateTime = troll.fold(updatedAtTroll, updatedAt) def nbPosts(troll: Boolean): Int = troll.fold(nbPostsTroll, nbPosts) def nbReplies(troll: Boolean): Int = nbPosts(troll) - 1 @@ -54,7 +56,7 @@ object Topic { name: String, troll: Boolean, featured: Boolean): Topic = Topic( - id = Random nextString idSize, + _id = Random nextString idSize, categId = categId, slug = slug, name = name, @@ -69,26 +71,4 @@ object Topic { troll = troll, closed = false, hidden = !featured) - - import lila.db.JsTube - import JsTube.Helpers._ - import play.api.libs.json._ - - private implicit def postTube = Post.tube - - private def defaults = Json.obj("closed" -> false) - - private[forum] lazy val tube = JsTube( - (__.json update ( - merge(defaults) andThen - readDate('createdAt) andThen - readDate('updatedAt) andThen - readDate('updatedAtTroll) - )) andThen Json.reads[Topic], - Json.writes[Topic] andThen (__.json update ( - writeDate('createdAt) andThen - writeDate('updatedAt) andThen - writeDate('updatedAtTroll) - )) - ) } diff --git a/modules/forum/src/main/TopicApi.scala b/modules/forum/src/main/TopicApi.scala index 67d659bde6..a898c7e046 100644 --- a/modules/forum/src/main/TopicApi.scala +++ b/modules/forum/src/main/TopicApi.scala @@ -4,13 +4,11 @@ import actorApi._ import akka.actor.ActorSelection import lila.common.paginator._ -import lila.db.api._ -import lila.db.Implicits._ +import lila.db.dsl._ import lila.db.paginator._ import lila.hub.actorApi.timeline.{ Propagate, ForumPost } import lila.security.{ Granter => MasterGranter } import lila.user.{ User, UserContext } -import tube._ private[forum] final class TopicApi( env: Env, @@ -21,6 +19,8 @@ private[forum] final class TopicApi( timeline: ActorSelection, detectLanguage: lila.common.DetectLanguage) { + import BSONHandlers._ + def show(categSlug: String, slug: String, page: Int, troll: Boolean): Fu[Option[(Categ, Topic, Paginator[Post])]] = for { data ← (for { @@ -57,9 +57,9 @@ private[forum] final class TopicApi( lang = lang map (_.language), number = 1, categId = categ.id) - $insert(post) >> - $insert(topic withPost post) >> - $update(categ withTopic post) >>- + env.postColl.insert(post) >> + env.topicColl.insert(topic withPost post) >> + env.categColl.update($id(categ.id), categ withTopic post) >>- (indexer ! InsertPost(post)) >> env.recent.invalidate >>- ctx.userId.?? { userId => @@ -79,10 +79,12 @@ private[forum] final class TopicApi( def paginator(categ: Categ, page: Int, troll: Boolean): Fu[Paginator[TopicView]] = Paginator( adapter = new Adapter[Topic]( + collection = env.topicColl, selector = TopicRepo(troll) byCategQuery categ, - sort = Seq($sort.updatedDesc) + projection = $empty, + sort = $sort.updatedDesc ) mapFuture { topic => - $find.byId[Post](topic lastPostId troll) map { post => + env.postColl.byId[Post](topic lastPostId troll) map { post => TopicView(categ, topic, post, env.postApi lastPageOf topic, troll) } }, @@ -91,7 +93,7 @@ private[forum] final class TopicApi( def delete(categ: Categ, topic: Topic): Funit = PostRepo.idsByTopicId(topic.id) flatMap { postIds => - (PostRepo removeByTopic topic.id zip $remove(topic)) >> + (PostRepo removeByTopic topic.id zip env.topicColl.remove($id(topic.id))) >> (env.categApi denormalize categ) >>- (indexer ! RemovePosts(postIds)) >> env.recent.invalidate @@ -100,33 +102,29 @@ private[forum] final class TopicApi( def toggleClose(categ: Categ, topic: Topic, mod: User): Funit = TopicRepo.close(topic.id, topic.open) >> { MasterGranter(_.ModerateForum)(mod) ?? - modLog.toggleCloseTopic(mod, categ.name, topic.name, topic.open) + modLog.toggleCloseTopic(mod.id, categ.name, topic.name, topic.open) } def toggleHide(categ: Categ, topic: Topic, mod: User): Funit = TopicRepo.hide(topic.id, topic.visibleOnHome) >> { MasterGranter(_.ModerateForum)(mod) ?? { PostRepo.hideByTopic(topic.id, topic.visibleOnHome) zip - modLog.toggleHideTopic(mod, categ.name, topic.name, topic.visibleOnHome) + modLog.toggleHideTopic(mod.id, categ.name, topic.name, topic.visibleOnHome) } >> env.recent.invalidate } def denormalize(topic: Topic): Funit = for { - nbPosts ← PostRepo countByTopics List(topic) - lastPost ← PostRepo lastByTopics List(topic) - nbPostsTroll ← PostRepoTroll countByTopics List(topic) - lastPostTroll ← PostRepoTroll lastByTopics List(topic) - _ ← $update(topic.copy( + nbPosts ← PostRepo countByTopics List(topic.id) + lastPost ← PostRepo lastByTopics List(topic.id) + nbPostsTroll ← PostRepoTroll countByTopics List(topic.id) + lastPostTroll ← PostRepoTroll lastByTopics List(topic.id) + _ ← env.topicColl.update($id(topic.id), topic.copy( nbPosts = nbPosts, lastPostId = lastPost ?? (_.id), updatedAt = lastPost.fold(topic.updatedAt)(_.createdAt), nbPostsTroll = nbPostsTroll, lastPostIdTroll = lastPostTroll ?? (_.id), updatedAtTroll = lastPostTroll.fold(topic.updatedAtTroll)(_.createdAt) - )) + )).void } yield () - - def denormalize: Funit = $find.all[Topic] flatMap { topics => - topics.map(denormalize).sequenceFu - } void } diff --git a/modules/forum/src/main/TopicRepo.scala b/modules/forum/src/main/TopicRepo.scala index 550866fe50..1f546eaf43 100644 --- a/modules/forum/src/main/TopicRepo.scala +++ b/modules/forum/src/main/TopicRepo.scala @@ -1,11 +1,6 @@ package lila.forum -import play.api.libs.json.Json -import play.modules.reactivemongo.json.ImplicitBSONHandlers.JsObjectWriter - -import lila.db.api._ -import lila.db.Implicits._ -import tube.topicTube +import lila.db.dsl._ object TopicRepo extends TopicRepo(false) { @@ -16,22 +11,24 @@ object TopicRepoTroll extends TopicRepo(true) sealed abstract class TopicRepo(troll: Boolean) { - private lazy val trollFilter = troll.fold( - Json.obj(), - Json.obj("troll" -> false) - ) + import BSONHandlers.TopicBSONHandler + + // dirty + private val coll = Env.current.topicColl + + private val trollFilter = troll.fold($empty, $doc("troll" -> false)) def close(id: String, value: Boolean): Funit = - $update.field(id, "closed", value) + coll.updateField($id(id), "closed", value).void def hide(id: String, value: Boolean): Funit = - $update.field(id, "hidden", value) + coll.updateField($id(id), "hidden", value).void def byCateg(categ: Categ): Fu[List[Topic]] = - $find(byCategQuery(categ)) + coll.list[Topic](byCategQuery(categ)) def byTree(categSlug: String, slug: String): Fu[Option[Topic]] = - $find.one(Json.obj("categId" -> categSlug, "slug" -> slug) ++ trollFilter) + coll.uno[Topic]($doc("categId" -> categSlug, "slug" -> slug) ++ trollFilter) def nextSlug(categ: Categ, name: String, it: Int = 1): Fu[String] = { val slug = Topic.nameToId(name) + ~(it != 1).option("-" + it) @@ -45,7 +42,7 @@ sealed abstract class TopicRepo(troll: Boolean) { } def incViews(topic: Topic): Funit = - $update($select(topic.id), $inc("views" -> 1)) + coll.update($id(topic.id), $inc("views" -> 1)).void - def byCategQuery(categ: Categ) = Json.obj("categId" -> categ.slug) ++ trollFilter + def byCategQuery(categ: Categ) = $doc("categId" -> categ.slug) ++ trollFilter } diff --git a/modules/forum/src/main/package.scala b/modules/forum/src/main/package.scala index 843f03a884..e7df8ee029 100644 --- a/modules/forum/src/main/package.scala +++ b/modules/forum/src/main/package.scala @@ -1,23 +1,6 @@ package lila -import play.api.libs.json._ - -import lila.db.JsTube -import JsTube.Helpers._ - package object forum extends PackageObject with WithPlay { - object tube { - - private[forum] implicit lazy val categTube = - Categ.tube inColl Env.current.categColl - - private[forum] implicit lazy val topicTube = - Topic.tube inColl Env.current.topicColl - - implicit lazy val postTube = - Post.tube inColl Env.current.postColl - } - private[forum] def teamSlug(id: String) = "team-" + id } diff --git a/modules/forumSearch/src/main/ForumSearchApi.scala b/modules/forumSearch/src/main/ForumSearchApi.scala index a8e5c89c1f..47571ed57f 100644 --- a/modules/forumSearch/src/main/ForumSearchApi.scala +++ b/modules/forumSearch/src/main/ForumSearchApi.scala @@ -1,7 +1,7 @@ package lila.forumSearch import lila.forum.actorApi._ -import lila.forum.{ Post, PostView, PostLiteView, PostApi } +import lila.forum.{ Post, PostView, PostLiteView, PostApi, PostRepo } import lila.search._ import play.api.libs.json._ @@ -36,13 +36,14 @@ final class ForumSearchApi( def reset = client match { case c: ESClientHttp => c.putMapping >> { lila.log("forumSearch").info(s"Index to ${c.index.name}") - import lila.db.api._ - import lila.forum.tube.postTube - $enumerate.bulk[Option[Post]]($query[Post](Json.obj()), 500) { postOptions => - (postApi liteViews postOptions.flatten) flatMap { views => - c.storeBulk(views map (v => Id(v.post.id) -> toDoc(v))) + import lila.db.dsl._ + import play.api.libs.iteratee._ + PostRepo.cursor($empty).enumerateBulks(Int.MaxValue) |>>> + Iteratee.foldM[Iterator[Post], Unit](()) { + case (_, posts) => (postApi liteViews posts.toList) flatMap { views => + c.storeBulk(views map (v => Id(v.post.id) -> toDoc(v))) + } } - } } case _ => funit } diff --git a/modules/game/src/main/Cached.scala b/modules/game/src/main/Cached.scala index 039cc32418..0ef62886d0 100644 --- a/modules/game/src/main/Cached.scala +++ b/modules/game/src/main/Cached.scala @@ -2,18 +2,18 @@ package lila.game import scala.concurrent.duration._ -import org.joda.time.DateTime -import play.api.libs.json.JsObject import chess.variant.Variant +import org.joda.time.DateTime +import reactivemongo.bson.BSONDocument -import lila.db.api.$count import lila.db.BSON._ +import lila.db.dsl._ import lila.memo.{ AsyncCache, MongoCache, ExpireSetMemo, Builder } import lila.user.{ User, UidNb } -import tube.gameTube import UidNb.UidNbBSONHandler final class Cached( + coll: Coll, mongoCache: MongoCache.Builder, defaultTtl: FiniteDuration) { @@ -34,13 +34,13 @@ final class Cached( // (nb: Int) => GameRepo.activePlayersSince(DateTime.now minusDays 1, nb), // timeToLive = 1 hour) - private val countShortTtl = AsyncCache[JsObject, Int]( - f = (o: JsObject) => $count(o), + private val countShortTtl = AsyncCache[BSONDocument, Int]( + f = (o: BSONDocument) => coll countSel o, timeToLive = 5.seconds) private val count = mongoCache( prefix = "game:count", - f = (o: JsObject) => $count(o), + f = (o: BSONDocument) => coll countSel o, timeToLive = defaultTtl) object Divider { diff --git a/modules/game/src/main/Captcher.scala b/modules/game/src/main/Captcher.scala index 363d2a31d1..f4cbaa1c9d 100644 --- a/modules/game/src/main/Captcher.scala +++ b/modules/game/src/main/Captcher.scala @@ -10,9 +10,7 @@ import chess.{ Game => ChessGame, Color } import scalaz.{ NonEmptyList, OptionT } import lila.common.Captcha, Captcha._ -import lila.db.api.$find import lila.hub.actorApi.captcha._ -import tube.gameTube // only works with standard chess (not chess960) private final class Captcher extends Actor { @@ -65,7 +63,7 @@ private final class Captcher extends Actor { GameRepo findRandomStandardCheckmate distribution private def getFromDb(id: String): Fu[Option[Captcha]] = - optionT($find byId id) flatMap fromGame + optionT(GameRepo game id) flatMap fromGame private def fromGame(game: Game): OptionT[Fu, Captcha] = optionT(GameRepo getOptionPgn game.id) flatMap { makeCaptcha(game, _) } diff --git a/modules/game/src/main/Cli.scala b/modules/game/src/main/Cli.scala index f7616fb6df..fad2f3c80f 100644 --- a/modules/game/src/main/Cli.scala +++ b/modules/game/src/main/Cli.scala @@ -2,13 +2,10 @@ package lila.game import scala.concurrent.duration._ -import lila.db.api._ +import lila.db.dsl._ import lila.user.UserRepo -import tube.gameTube -private[game] final class Cli( - db: lila.db.Env, - system: akka.actor.ActorSystem) extends lila.common.Cli { +private[game] final class Cli(coll: Coll) extends lila.common.Cli { def process = { @@ -16,21 +13,5 @@ private[game] final class Cli( GameRepo nbPerDay { (days.headOption flatMap parseIntOption) | 30 } map (_ mkString " ") - - case "game" :: "typecheck" :: Nil => - logger.info("Counting games...") - val size = $count($select.all).await - var nb = 0 - val bulkSize = 1000 - ($enumerate.bulk[Option[Game]]($query.all, bulkSize) { gameOptions => - val nbGames = gameOptions.flatten.size - if (nbGames != bulkSize) - logger.warn("Built %d of %d games".format(nbGames, bulkSize)) - nb = nb + nbGames - logger.info("Typechecked %d of %d games".format(nb, size)) - funit - }).await(2.hours) - fuccess("Done") } - } diff --git a/modules/game/src/main/CrosstableApi.scala b/modules/game/src/main/CrosstableApi.scala index 80704deb60..89f60acd4a 100644 --- a/modules/game/src/main/CrosstableApi.scala +++ b/modules/game/src/main/CrosstableApi.scala @@ -1,14 +1,13 @@ package lila.game -import play.api.libs.json.JsObject -import reactivemongo.bson.{ BSONDocument, BSONInteger } import reactivemongo.core.commands._ -import lila.common.PimpedJson._ -import lila.db.Types._ +import lila.db.dsl._ import lila.user.UserRepo -final class CrosstableApi(coll: Coll) { +final class CrosstableApi( + coll: Coll, + gameColl: Coll) { import Crosstable.Result @@ -20,14 +19,14 @@ final class CrosstableApi(coll: Coll) { } def apply(u1: String, u2: String): Fu[Option[Crosstable]] = - coll.find(select(u1, u2)).one[Crosstable] orElse create(u1, u2) recoverWith - lila.db.recoverDuplicateKey(_ => coll.find(select(u1, u2)).one[Crosstable]) + coll.find(select(u1, u2)).uno[Crosstable] orElse create(u1, u2) recoverWith + lila.db.recoverDuplicateKey(_ => coll.find(select(u1, u2)).uno[Crosstable]) def nbGames(u1: String, u2: String): Fu[Int] = coll.find( select(u1, u2), - BSONDocument("n" -> true) - ).one[BSONDocument] map { + $doc("n" -> true) + ).uno[Bdoc] map { ~_.flatMap(_.getAs[Int]("n")) } @@ -35,22 +34,22 @@ final class CrosstableApi(coll: Coll) { case List(u1, u2) => val result = Result(game.id, game.winnerUserId) val bsonResult = Crosstable.crosstableBSONHandler.writeResult(result, u1) - val bson = BSONDocument( - "$inc" -> BSONDocument( - Crosstable.BSONFields.nbGames -> BSONInteger(1), - "s1" -> BSONInteger(game.winnerUserId match { + val bson = $doc( + "$inc" -> $doc( + Crosstable.BSONFields.nbGames -> $int(1), + "s1" -> $int(game.winnerUserId match { case Some(u) if u == u1 => 10 case None => 5 case _ => 0 }), - "s2" -> BSONInteger(game.winnerUserId match { + "s2" -> $int(game.winnerUserId match { case Some(u) if u == u2 => 10 case None => 5 case _ => 0 }) ) - ) ++ BSONDocument("$push" -> BSONDocument( - Crosstable.BSONFields.results -> BSONDocument( + ) ++ $doc("$push" -> $doc( + Crosstable.BSONFields.results -> $doc( "$each" -> List(bsonResult), "$slice" -> -maxGames ))) @@ -64,21 +63,20 @@ final class CrosstableApi(coll: Coll) { private def create(x1: String, x2: String): Fu[Option[Crosstable]] = UserRepo.orderByGameCount(x1, x2) map (_ -> List(x1, x2).sorted) flatMap { case (Some((u1, u2)), List(su1, su2)) => - val gameColl = tube.gameTube.coll - val selector = BSONDocument( - Game.BSONFields.playerUids -> BSONDocument("$all" -> List(u1, u2)), - Game.BSONFields.status -> BSONDocument("$gte" -> chess.Status.Mate.id)) + val selector = $doc( + Game.BSONFields.playerUids -> $doc("$all" -> List(u1, u2)), + Game.BSONFields.status -> $doc("$gte" -> chess.Status.Mate.id)) import reactivemongo.api.collections.bson.BSONBatchCommands.AggregationFramework.{ Match, SumValue, GroupField } import reactivemongo.api.ReadPreference for { localResults <- gameColl.find(selector, - BSONDocument(Game.BSONFields.winnerId -> true) - ).sort(BSONDocument(Game.BSONFields.createdAt -> -1)) - .cursor[BSONDocument](readPreference = ReadPreference.secondaryPreferred) - .collect[List](maxGames).map { + $doc(Game.BSONFields.winnerId -> true) + ).sort($doc(Game.BSONFields.createdAt -> -1)) + .cursor[Bdoc](readPreference = ReadPreference.secondaryPreferred) + .gather[List](maxGames).map { _.flatMap { doc => doc.getAs[String](Game.BSONFields.id).map { id => Result(id, doc.getAs[String](Game.BSONFields.winnerId)) @@ -104,5 +102,5 @@ final class CrosstableApi(coll: Coll) { } private def select(u1: String, u2: String) = - BSONDocument("_id" -> Crosstable.makeKey(u1, u2)) + $doc("_id" -> Crosstable.makeKey(u1, u2)) } diff --git a/modules/game/src/main/Env.scala b/modules/game/src/main/Env.scala index e6ad0b7454..fbca52d97f 100644 --- a/modules/game/src/main/Env.scala +++ b/modules/game/src/main/Env.scala @@ -33,17 +33,21 @@ final class Env( } import settings._ - private[game] lazy val gameColl = db(CollectionGame) + lazy val gameColl = db(CollectionGame) + + lazy val playTime = new PlayTime(gameColl) lazy val pdfExport = PdfExport(PdfExecPath) _ lazy val pngExport = PngExport(PngExecPath) _ lazy val cached = new Cached( + coll = gameColl, mongoCache = mongoCache, defaultTtl = CachedNbTtl) lazy val paginator = new PaginatorBuilder( + coll = gameColl, cached = cached, maxPerPage = PaginatorMaxPerPage) @@ -57,7 +61,9 @@ final class Env( netBaseUrl = netBaseUrl, getLightUser = getLightUser) - lazy val crosstableApi = new CrosstableApi(db(CollectionCrosstable)) + lazy val crosstableApi = new CrosstableApi( + coll = db(CollectionCrosstable), + gameColl = gameColl) // load captcher actor private val captcher = system.actorOf(Props(new Captcher), name = CaptcherName) @@ -66,7 +72,7 @@ final class Env( captcher -> actorApi.NewCaptcha } - def cli = new Cli(db, system = system) + def cli = new Cli(gameColl) def onStart(gameId: String) = GameRepo game gameId foreach { _ foreach { game => diff --git a/modules/game/src/main/Game.scala b/modules/game/src/main/Game.scala index 79ff43af92..4047ef7c08 100644 --- a/modules/game/src/main/Game.scala +++ b/modules/game/src/main/Game.scala @@ -542,8 +542,6 @@ object Game { analysed = false), createdAt = DateTime.now) - private[game] lazy val tube = lila.db.BsTube(BSONHandlers.gameBSONHandler) - object BSONFields { val id = "_id" diff --git a/modules/game/src/main/GameRepo.scala b/modules/game/src/main/GameRepo.scala index 691613fd4e..0865621a6e 100644 --- a/modules/game/src/main/GameRepo.scala +++ b/modules/game/src/main/GameRepo.scala @@ -5,41 +5,39 @@ import scala.util.Random import chess.format.Forsyth import chess.{ Color, Status } import org.joda.time.DateTime -import play.api.libs.json._ -import play.modules.reactivemongo.json.ImplicitBSONHandlers.JsObjectWriter -import reactivemongo.bson.{ BSONDocument, BSONArray, BSONBinary, BSONInteger } +import reactivemongo.api.ReadPreference +import reactivemongo.bson.BSONBinary -import lila.common.PimpedJson._ -import lila.db.api._ import lila.db.BSON.BSONJodaDateTimeHandler import lila.db.ByteArray -import lila.db.Implicits._ +import lila.db.dsl._ import lila.user.{ User, UidNb } object GameRepo { - import tube.gameTube + // dirty + val coll = Env.current.gameColl type ID = String import BSONHandlers._ import Game.{ BSONFields => F } - def game(gameId: ID): Fu[Option[Game]] = $find byId gameId + def game(gameId: ID): Fu[Option[Game]] = coll.byId[Game](gameId) - def games(gameIds: Seq[ID]): Fu[List[Game]] = $find byOrderedIds gameIds + def games(gameIds: Seq[ID]): Fu[List[Game]] = coll.byOrderedIds[Game](gameIds)(_.id) def gameOptions(gameIds: Seq[ID]): Fu[Seq[Option[Game]]] = - $find optionsByOrderedIds gameIds + coll.optionsByOrderedIds[Game](gameIds)(_.id) def finished(gameId: ID): Fu[Option[Game]] = - $find.one($select(gameId) ++ Query.finished) + coll.uno[Game]($id(gameId) ++ Query.finished) def player(gameId: ID, color: Color): Fu[Option[Player]] = - $find byId gameId map2 { (game: Game) => game player color } + game(gameId) map2 { (game: Game) => game player color } def player(gameId: ID, playerId: ID): Fu[Option[Player]] = - $find byId gameId map { gameOption => + game(gameId) map { gameOption => gameOption flatMap { _ player playerId } } @@ -47,13 +45,13 @@ object GameRepo { player(playerRef.gameId, playerRef.playerId) def pov(gameId: ID, color: Color): Fu[Option[Pov]] = - $find byId gameId map2 { (game: Game) => Pov(game, game player color) } + game(gameId) map2 { (game: Game) => Pov(game, game player color) } def pov(gameId: ID, color: String): Fu[Option[Pov]] = Color(color) ?? (pov(gameId, _)) def pov(playerRef: PlayerRef): Fu[Option[Pov]] = - $find byId playerRef.gameId map { gameOption => + coll.byId[Game](playerRef.gameId) map { gameOption => gameOption flatMap { game => game player playerRef.playerId map { Pov(game, _) } } @@ -63,67 +61,76 @@ object GameRepo { def pov(ref: PovRef): Fu[Option[Pov]] = pov(ref.gameId, ref.color) - def remove(id: ID) = $remove($select(id)) - - def recentByUser(userId: String): Fu[List[Game]] = $find( - $query(Query user userId) sort Query.sortCreated - ) + def remove(id: ID) = coll.remove($id(id)).void def userPovsByGameIds(gameIds: List[String], user: User): Fu[List[Pov]] = - $find.byOrderedIds(gameIds) map { _.flatMap(g => Pov(g, user)) } + coll.byOrderedIds[Game](gameIds)(_.id) map { _.flatMap(g => Pov(g, user)) } - def recentPovsByUser(user: User, nb: Int): Fu[List[Pov]] = $find( - $query(Query user user) sort Query.sortCreated, nb - ) map { _.flatMap(g => Pov(g, user)) } + def recentPovsByUser(user: User, nb: Int): Fu[List[Pov]] = + coll.find(Query user user).sort(Query.sortCreated).cursor[Game]().gather[List](nb) + .map { _.flatMap(g => Pov(g, user)) } - def gamesForAssessment(userId: String, nb: Int): Fu[List[Game]] = $find( - $query(Query.finished + def gamesForAssessment(userId: String, nb: Int): Fu[List[Game]] = coll.find( + Query.finished ++ Query.rated ++ Query.user(userId) ++ Query.analysed(true) ++ Query.turnsMoreThan(20) - ++ Query.clock(true)) sort ($sort asc F.createdAt), nb - ) + ++ Query.clock(true)) + .sort($sort asc F.createdAt) + .cursor[Game]() + .gather[List](nb) + + def cursor( + selector: Bdoc, + readPreference: ReadPreference = ReadPreference.secondaryPreferred) = + coll.find(selector).cursor[Game](readPreference) + + def sortedCursor( + selector: Bdoc, + sort: Bdoc, + readPreference: ReadPreference = ReadPreference.secondaryPreferred) = + coll.find(selector).sort(sort).cursor[Game](readPreference) def unrate(gameId: String) = - $update($select(gameId), BSONDocument("$unset" -> BSONDocument( + coll.update($id(gameId), $doc("$unset" -> $doc( F.rated -> true, s"${F.whitePlayer}.${Player.BSONFields.ratingDiff}" -> true, s"${F.blackPlayer}.${Player.BSONFields.ratingDiff}" -> true ))) def goBerserk(pov: Pov): Funit = - $update($select(pov.gameId), BSONDocument("$set" -> BSONDocument( + coll.update($id(pov.gameId), $set( s"${pov.color.fold(F.whitePlayer, F.blackPlayer)}.${Player.BSONFields.berserk}" -> true - ))) + )).void def save(progress: Progress): Funit = GameDiff(progress.origin, progress.game) match { case (Nil, Nil) => funit - case (sets, unsets) => gameTube.coll.update( - $select(progress.origin.id), - nonEmptyMod("$set", BSONDocument(sets)) ++ nonEmptyMod("$unset", BSONDocument(unsets)) + case (sets, unsets) => coll.update( + $id(progress.origin.id), + nonEmptyMod("$set", $doc(sets)) ++ nonEmptyMod("$unset", $doc(unsets)) ).void } - private def nonEmptyMod(mod: String, doc: BSONDocument) = - if (doc.isEmpty) BSONDocument() else BSONDocument(mod -> doc) + private def nonEmptyMod(mod: String, doc: Bdoc) = + if (doc.isEmpty) $empty else $doc(mod -> doc) def setRatingDiffs(id: ID, white: Int, black: Int) = - $update($select(id), BSONDocument("$set" -> BSONDocument( - s"${F.whitePlayer}.${Player.BSONFields.ratingDiff}" -> BSONInteger(white), - s"${F.blackPlayer}.${Player.BSONFields.ratingDiff}" -> BSONInteger(black)))) + coll.update($id(id), $set( + s"${F.whitePlayer}.${Player.BSONFields.ratingDiff}" -> white, + s"${F.blackPlayer}.${Player.BSONFields.ratingDiff}" -> black)) // used by RatingFest def setRatingAndDiffs(id: ID, white: (Int, Int), black: (Int, Int)) = - $update($select(id), BSONDocument("$set" -> BSONDocument( - s"${F.whitePlayer}.${Player.BSONFields.rating}" -> BSONInteger(white._1), - s"${F.blackPlayer}.${Player.BSONFields.rating}" -> BSONInteger(black._1), - s"${F.whitePlayer}.${Player.BSONFields.ratingDiff}" -> BSONInteger(white._2), - s"${F.blackPlayer}.${Player.BSONFields.ratingDiff}" -> BSONInteger(black._2)))) + coll.update($id(id), $set( + s"${F.whitePlayer}.${Player.BSONFields.rating}" -> white._1, + s"${F.blackPlayer}.${Player.BSONFields.rating}" -> black._1, + s"${F.whitePlayer}.${Player.BSONFields.ratingDiff}" -> white._2, + s"${F.blackPlayer}.${Player.BSONFields.ratingDiff}" -> black._2)) def urgentGames(user: User): Fu[List[Pov]] = - $find(Query nowPlaying user.id, 100) map { games => + coll.list[Game](Query nowPlaying user.id, 100) map { games => val povs = games flatMap { Pov(_, user) } try { povs sortWith Pov.priority @@ -139,61 +146,64 @@ object GameRepo { // gets last recently played move game in progress def lastPlayedPlaying(user: User): Fu[Option[Pov]] = - $find.one($query(Query recentlyPlaying user.id) sort Query.sortUpdatedNoIndex) map { - _ flatMap { Pov(_, user) } - } + coll.find(Query recentlyPlaying user.id) + .sort(Query.sortUpdatedNoIndex) + .uno[Game] + .map { _ flatMap { Pov(_, user) } } def lastPlayed(user: User): Fu[Option[Pov]] = - $find($query(Query user user.id) sort ($sort desc F.createdAt), 20) map { - _.sortBy(_.updatedAt).lastOption flatMap { Pov(_, user) } - } + coll.find(Query user user.id) + .sort($sort desc F.createdAt) + .cursor[Game]() + .gather[List](20).map { + _.sortBy(_.updatedAt).lastOption flatMap { Pov(_, user) } + } - def lastFinishedRatedNotFromPosition(user: User): Fu[Option[Game]] = $find.one { - $query { - Query.user(user.id) ++ - Query.rated ++ - Query.finished ++ - Query.turnsMoreThan(2) ++ - Query.notFromPosition - } sort Query.sortAntiChronological - } + def lastFinishedRatedNotFromPosition(user: User): Fu[Option[Game]] = coll.find( + Query.user(user.id) ++ + Query.rated ++ + Query.finished ++ + Query.turnsMoreThan(2) ++ + Query.notFromPosition + ).sort(Query.sortAntiChronological).uno[Game] def setTv(id: ID) { - $update.fieldUnchecked(id, F.tvAt, $date(DateTime.now)) + coll.updateFieldUnchecked($id(id), F.tvAt, DateTime.now) } - def onTv(nb: Int): Fu[List[Game]] = $find($query(Json.obj(F.tvAt -> $exists(true))) sort $sort.desc(F.tvAt), nb) + def onTv(nb: Int): Fu[List[Game]] = coll.find($doc(F.tvAt $exists true)) + .sort($sort desc F.tvAt) + .cursor[Game]() + .gather[List](nb) def setAnalysed(id: ID) { - $update.fieldUnchecked(id, F.analysed, true) + coll.updateFieldUnchecked($id(id), F.analysed, true) } def setUnanalysed(id: ID) { - $update.fieldUnchecked(id, F.analysed, false) + coll.updateFieldUnchecked($id(id), F.analysed, false) } def isAnalysed(id: ID): Fu[Boolean] = - $count.exists($select(id) ++ Query.analysed(true)) + coll.exists($id(id) ++ Query.analysed(true)) def filterAnalysed(ids: Seq[String]): Fu[Set[String]] = - gameTube.coll.distinct("_id", BSONDocument( - "_id" -> BSONDocument("$in" -> ids), + coll.distinct("_id", $doc( + "_id" -> $doc("$in" -> ids), F.analysed -> true ).some) map lila.db.BSON.asStringSet - def exists(id: String) = gameTube.coll.count(BSONDocument("_id" -> id).some).map(0<) + def exists(id: String) = coll.exists($doc("_id" -> id)) def incBookmarks(id: ID, value: Int) = - $update($select(id), $incBson(F.bookmarks -> value)) + coll.update($id(id), $inc(F.bookmarks -> value)).void def setHoldAlert(pov: Pov, mean: Int, sd: Int, ply: Option[Int] = None) = { import Player.holdAlertBSONHandler - $update( - $select(pov.gameId), - BSONDocument( - "$set" -> BSONDocument( - s"p${pov.color.fold(0, 1)}.${Player.BSONFields.holdAlert}" -> - Player.HoldAlert(ply = ply | pov.game.turns, mean = mean, sd = sd) - ) + coll.update( + $id(pov.gameId), + $set( + s"p${pov.color.fold(0, 1)}.${Player.BSONFields.holdAlert}" -> + Player.HoldAlert(ply = ply | pov.game.turns, mean = mean, sd = sd) ) ).void } @@ -204,7 +214,7 @@ object GameRepo { winnerColor: Option[Color], winnerId: Option[String], status: Status) = { - val partialUnsets = BSONDocument( + val partialUnsets = $doc( F.positionHashes -> true, F.playingUids -> true, ("p0." + Player.BSONFields.lastDrawOffer) -> true, @@ -216,21 +226,22 @@ object GameRepo { // keep the checkAt field when game is aborted, // so it gets deleted in 24h val unsets = - if (status >= Status.Mate) partialUnsets ++ BSONDocument(F.checkAt -> true) + if (status >= Status.Mate) partialUnsets ++ $doc(F.checkAt -> true) else partialUnsets - $update( - $select(id), - nonEmptyMod("$set", BSONDocument( + coll.update( + $id(id), + nonEmptyMod("$set", $doc( F.winnerId -> winnerId, F.winnerColor -> winnerColor.map(_.white) - )) ++ BSONDocument("$unset" -> unsets) + )) ++ $doc("$unset" -> unsets) ) } - def findRandomStandardCheckmate(distribution: Int): Fu[Option[Game]] = $find.one( - Query.mate ++ Json.obj("v" -> $exists(false)), - _ sort Query.sortCreated skip (Random nextInt distribution) - ) + def findRandomStandardCheckmate(distribution: Int): Fu[Option[Game]] = coll.find( + Query.mate ++ $doc("v" $exists false) + ).sort(Query.sortCreated) + .skip(Random nextInt distribution) + .uno[Game] def insertDenormalized(g: Game, ratedCheck: Boolean = true, initialFen: Option[chess.format.FEN] = None): Funit = { val g2 = if (ratedCheck && g.rated && g.userIds.distinct.size != 2) @@ -242,12 +253,12 @@ object GameRepo { .option(Forsyth >> g2.toChess) .filter(Forsyth.initial !=) } - val bson = (gameTube.handler write g2) ++ BSONDocument( + val bson = (gameBSONHandler write g2) ++ $doc( F.initialFen -> fen, F.checkAt -> (!g2.isPgnImport).option(DateTime.now plusHours g2.hasClock.fold(1, 10 * 24)), F.playingUids -> (g2.started && userIds.nonEmpty).option(userIds) ) - $insert bson bson + coll insert bson void } >>- { lila.mon.game.create.variant(g.variant.key)() lila.mon.game.create.source(g.source.fold("unknown")(_.name))() @@ -256,32 +267,32 @@ object GameRepo { } def removeRecentChallengesOf(userId: String) = - $remove(Query.created ++ Query.friend ++ Query.user(userId) ++ + coll.remove(Query.created ++ Query.friend ++ Query.user(userId) ++ Query.createdSince(DateTime.now minusHours 1)) def setCheckAt(g: Game, at: DateTime) = - $update($select(g.id), BSONDocument("$set" -> BSONDocument(F.checkAt -> at))) + coll.update($id(g.id), $doc("$set" -> $doc(F.checkAt -> at))) def unsetCheckAt(g: Game) = - $update($select(g.id), BSONDocument("$unset" -> BSONDocument(F.checkAt -> true))) + coll.update($id(g.id), $doc("$unset" -> $doc(F.checkAt -> true))) def unsetPlayingUids(g: Game): Unit = - $update.unchecked($select(g.id), BSONDocument("$unset" -> BSONDocument(F.playingUids -> true))) + coll.uncheckedUpdate($id(g.id), $unset(F.playingUids)) // used to make a compound sparse index def setImportCreatedAt(g: Game) = - $update($select(g.id), BSONDocument( - "$set" -> BSONDocument("pgni.ca" -> g.createdAt) - )) + coll.update($id(g.id), $set("pgni.ca" -> g.createdAt)).void - def saveNext(game: Game, nextId: ID): Funit = $update( - $select(game.id), + def saveNext(game: Game, nextId: ID): Funit = coll.update( + $id(game.id), $set(F.next -> nextId) ++ - $unset("p0." + Player.BSONFields.isOfferingRematch, "p1." + Player.BSONFields.isOfferingRematch) - ) + $unset( + "p0." + Player.BSONFields.isOfferingRematch, + "p1." + Player.BSONFields.isOfferingRematch) + ).void def initialFen(gameId: ID): Fu[Option[String]] = - $primitive.one($select(gameId), F.initialFen)(_.asOpt[String]) + coll.primitiveOne[String]($id(gameId), F.initialFen) def initialFen(game: Game): Fu[Option[String]] = if (game.imported || !game.variant.standardInitialPosition) initialFen(game.id) map { @@ -290,38 +301,38 @@ object GameRepo { } else fuccess(none) - def featuredCandidates: Fu[List[Game]] = $find( - Query.playable ++ Query.clock(true) ++ Json.obj( - F.createdAt -> $gt($date(DateTime.now minusMinutes 5)), - F.updatedAt -> $gt($date(DateTime.now minusSeconds 40)) - ) ++ $or(Seq( - Json.obj(s"${F.whitePlayer}.${Player.BSONFields.rating}" -> $gt(1200)), - Json.obj(s"${F.blackPlayer}.${Player.BSONFields.rating}" -> $gt(1200)) - )) + def featuredCandidates: Fu[List[Game]] = coll.list[Game]( + Query.playable ++ Query.clock(true) ++ $doc( + F.createdAt $gt (DateTime.now minusMinutes 5), + F.updatedAt $gt (DateTime.now minusSeconds 40) + ) ++ $or( + s"${F.whitePlayer}.${Player.BSONFields.rating}" $gt 1200, + s"${F.blackPlayer}.${Player.BSONFields.rating}" $gt 1200 + ) ) - def count(query: Query.type => JsObject): Fu[Int] = $count(query(Query)) + def count(query: Query.type => Bdoc): Fu[Int] = coll countSel query(Query) def nbPerDay(days: Int): Fu[List[Int]] = ((days to 1 by -1).toList map { day => val from = DateTime.now.withTimeAtStartOfDay minusDays day val to = from plusDays 1 - $count(Json.obj(F.createdAt -> ($gte($date(from)) ++ $lt($date(to))))) + coll.countSel($doc(F.createdAt -> ($gte(from) ++ $lt(to)))) }).sequenceFu // #TODO expensive stuff, run on DB replica // Can't be done on reactivemongo 0.11.9 :( def bestOpponents(userId: String, limit: Int): Fu[List[(String, Int)]] = { import reactivemongo.api.collections.bson.BSONBatchCommands.AggregationFramework._ - gameTube.coll.aggregate(Match(BSONDocument(F.playerUids -> userId)), List( - Match(BSONDocument(F.playerUids -> BSONDocument("$size" -> 2))), + coll.aggregate(Match($doc(F.playerUids -> userId)), List( + Match($doc(F.playerUids -> $doc("$size" -> 2))), Sort(Descending(F.createdAt)), Limit(1000), // only look in the last 1000 games - Project(BSONDocument( + Project($doc( F.playerUids -> true, F.id -> false)), Unwind(F.playerUids), - Match(BSONDocument(F.playerUids -> BSONDocument("$ne" -> userId))), + Match($doc(F.playerUids -> $doc("$ne" -> userId))), GroupField(F.playerUids)("gs" -> SumValue(1)), Sort(Descending("gs")), Limit(limit))).map(_.documents.flatMap { obj => @@ -331,31 +342,27 @@ object GameRepo { }) } - def random: Fu[Option[Game]] = $find.one( - $query.all sort Query.sortCreated skip (Random nextInt 1000)) - - def random(nb: Int): Fu[List[Game]] = $find( - $query.all sort Query.sortCreated skip (Random nextInt 1000), nb) - - def findMirror(game: Game): Fu[Option[Game]] = $find.one($query( - BSONDocument( - F.id -> BSONDocument("$ne" -> game.id), - F.playerUids -> BSONDocument("$in" -> game.userIds), - F.status -> Status.Started.id, - F.createdAt -> BSONDocument("$gt" -> (DateTime.now minusMinutes 15)), - F.updatedAt -> BSONDocument("$gt" -> (DateTime.now minusMinutes 5)), - "$or" -> BSONArray( - BSONDocument(s"${F.whitePlayer}.ai" -> BSONDocument("$exists" -> true)), - BSONDocument(s"${F.blackPlayer}.ai" -> BSONDocument("$exists" -> true)) - ), - F.binaryPieces -> game.binaryPieces - ) + def random: Fu[Option[Game]] = coll.find($empty) + .sort(Query.sortCreated) + .skip(Random nextInt 1000) + .uno[Game] + + def findMirror(game: Game): Fu[Option[Game]] = coll.uno[Game]($doc( + F.id -> $doc("$ne" -> game.id), + F.playerUids -> $doc("$in" -> game.userIds), + F.status -> Status.Started.id, + F.createdAt -> $doc("$gt" -> (DateTime.now minusMinutes 15)), + F.updatedAt -> $doc("$gt" -> (DateTime.now minusMinutes 5)), + "$or" -> $arr( + $doc(s"${F.whitePlayer}.ai" -> $doc("$exists" -> true)), + $doc(s"${F.blackPlayer}.ai" -> $doc("$exists" -> true)) + ), + F.binaryPieces -> game.binaryPieces )) - def findPgnImport(pgn: String): Fu[Option[Game]] = - gameTube.coll.find( - BSONDocument(s"${F.pgnImport}.h" -> PgnImport.hash(pgn)) - ).one[Game] + def findPgnImport(pgn: String): Fu[Option[Game]] = coll.uno[Game]( + $doc(s"${F.pgnImport}.h" -> PgnImport.hash(pgn)) + ) def getPgn(id: ID): Fu[PgnMoves] = getOptionPgn(id) map (~_) @@ -363,27 +370,26 @@ object GameRepo { getOptionPgn(id) map (_ filter (_.nonEmpty)) def getOptionPgn(id: ID): Fu[Option[PgnMoves]] = - gameTube.coll.find( - $select(id), Json.obj( + coll.find( + $id(id), $doc( F.id -> false, F.binaryPgn -> true ) - ).one[BSONDocument] map { _ flatMap extractPgnMoves } + ).uno[Bdoc] map { _ flatMap extractPgnMoves } - def lastGameBetween(u1: String, u2: String, since: DateTime): Fu[Option[Game]] = { - $find.one(Json.obj( - F.playerUids -> Json.obj("$all" -> List(u1, u2)), - F.createdAt -> Json.obj("$gt" -> $date(since)) + def lastGameBetween(u1: String, u2: String, since: DateTime): Fu[Option[Game]] = + coll.uno[Game]($doc( + F.playerUids.$all(u1, u2), + F.createdAt $gt since )) - } def getUserIds(id: ID): Fu[List[String]] = - gameTube.coll.find( - $select(id), BSONDocument( + coll.find( + $id(id), $doc( F.id -> false, F.playerUids -> true ) - ).one[BSONDocument] map { ~_.flatMap(_.getAs[List[String]](F.playerUids)) } + ).uno[Bdoc] map { ~_.flatMap(_.getAs[List[String]](F.playerUids)) } // #TODO this breaks it all since reactivemongo > 0.11.9 def activePlayersSinceNOPENOPENOPE(since: DateTime, max: Int): Fu[List[UidNb]] = { @@ -397,13 +403,13 @@ object GameRepo { Unwind } - gameTube.coll.aggregate(Match(BSONDocument( - F.createdAt -> BSONDocument("$gt" -> since), - F.status -> BSONDocument("$gte" -> chess.Status.Mate.id), - s"${F.playerUids}.0" -> BSONDocument("$exists" -> true) + coll.aggregate(Match($doc( + F.createdAt $gt since, + F.status $gte chess.Status.Mate.id, + s"${F.playerUids}.0" $exists true )), List(Unwind(F.playerUids), - Match(BSONDocument( - F.playerUids -> BSONDocument("$ne" -> "") + Match($doc( + F.playerUids -> $doc("$ne" -> "") )), GroupField(F.playerUids)("nb" -> SumValue(1)), Sort(Descending("nb")), @@ -414,7 +420,7 @@ object GameRepo { }) } - private def extractPgnMoves(doc: BSONDocument) = + private def extractPgnMoves(doc: Bdoc) = doc.getAs[BSONBinary](F.binaryPgn) map { bin => BinaryFormat.pgn read { ByteArray.ByteArrayBSONHandler read bin } } diff --git a/modules/game/src/main/PaginatorBuilder.scala b/modules/game/src/main/PaginatorBuilder.scala index f8dbebaa35..386ee24098 100644 --- a/modules/game/src/main/PaginatorBuilder.scala +++ b/modules/game/src/main/PaginatorBuilder.scala @@ -1,21 +1,23 @@ package lila.game -import play.api.libs.json._ - import chess.Status import lila.common.paginator._ +import lila.db.dsl._ import lila.db.paginator._ -import lila.db.Types.Sort -import tube.gameTube -private[game] final class PaginatorBuilder(cached: Cached, maxPerPage: Int) { +private[game] final class PaginatorBuilder( + coll: Coll, + cached: Cached, + maxPerPage: Int) { private val readPreference = reactivemongo.api.ReadPreference.secondaryPreferred - def recentlyCreated(selector: JsObject, nb: Option[Int] = None) = - apply(selector, Seq(Query.sortCreated), nb) _ + import BSONHandlers.gameBSONHandler + + def recentlyCreated(selector: Bdoc, nb: Option[Int] = None) = + apply(selector, Query.sortCreated, nb) _ - def apply(selector: JsObject, sort: Sort, nb: Option[Int] = None)(page: Int): Fu[Paginator[Game]] = + def apply(selector: Bdoc, sort: Bdoc, nb: Option[Int] = None)(page: Int): Fu[Paginator[Game]] = apply(nb.fold(noCacheAdapter(selector, sort)) { cached => cacheAdapter(selector, sort, fuccess(cached)) })(page) @@ -23,14 +25,16 @@ private[game] final class PaginatorBuilder(cached: Cached, maxPerPage: Int) { private def apply(adapter: AdapterLike[Game])(page: Int): Fu[Paginator[Game]] = paginator(adapter, page) - private def cacheAdapter(selector: JsObject, sort: Sort, nbResults: Fu[Int]): AdapterLike[Game] = + private def cacheAdapter(selector: Bdoc, sort: Bdoc, nbResults: Fu[Int]): AdapterLike[Game] = new CachedAdapter( adapter = noCacheAdapter(selector, sort), nbResults = nbResults) - private def noCacheAdapter(selector: JsObject, sort: Sort): AdapterLike[Game] = - new Adapter( + private def noCacheAdapter(selector: Bdoc, sort: Bdoc): AdapterLike[Game] = + new Adapter[Game]( + collection = coll, selector = selector, + projection = $empty, sort = sort, readPreference = readPreference) diff --git a/modules/game/src/main/PlayTime.scala b/modules/game/src/main/PlayTime.scala index 66ace37217..88c8b3e94a 100644 --- a/modules/game/src/main/PlayTime.scala +++ b/modules/game/src/main/PlayTime.scala @@ -1,15 +1,14 @@ package lila.game -import lila.db.api._ +import lila.db.dsl._ import lila.db.ByteArray import lila.user.{ User, UserRepo } import org.joda.time.Period -import tube.gameTube import play.api.libs.iteratee.Iteratee import reactivemongo.bson._ -object PlayTime { +final class PlayTime(gameColl: Coll) { private val moveTimeField = Game.BSONFields.moveTimes private val tvField = Game.BSONFields.tvAt @@ -17,16 +16,16 @@ object PlayTime { def apply(user: User): Fu[User.PlayTime] = user.playTime match { case Some(pt) => fuccess(pt) case None => { - gameTube.coll - .find(BSONDocument( + gameColl + .find($doc( Game.BSONFields.playerUids -> user.id, - Game.BSONFields.status -> BSONDocument("$gte" -> chess.Status.Mate.id) + Game.BSONFields.status -> $doc("$gte" -> chess.Status.Mate.id) )) - .projection(BSONDocument( + .projection($doc( moveTimeField -> true, tvField -> true )) - .cursor[BSONDocument]() + .cursor[Bdoc]() .enumerate() |>>> (Iteratee.fold(User.PlayTime(0, 0)) { case (pt, doc) => val t = doc.getAs[ByteArray](moveTimeField) ?? { times => diff --git a/modules/game/src/main/Query.scala b/modules/game/src/main/Query.scala index e496d8a9af..520c130291 100644 --- a/modules/game/src/main/Query.scala +++ b/modules/game/src/main/Query.scala @@ -2,106 +2,107 @@ package lila.game import chess.{ Color, Status } import org.joda.time.DateTime -import play.api.libs.json._ +import reactivemongo.bson._ -import lila.db.api._ +import lila.db.BSON.BSONJodaDateTimeHandler +import lila.db.dsl._ import lila.user.User object Query { import Game.{ BSONFields => F } - val all: JsObject = $select.all + val rated: Bdoc = F.rated $eq true - val rated: JsObject = Json.obj(F.rated -> true) + def rated(u: String): Bdoc = user(u) ++ rated - def rated(u: String): JsObject = user(u) ++ rated + def status(s: Status) = F.status $eq s.id - def status(s: Status) = Json.obj(F.status -> s.id) + val created: Bdoc = F.status $eq Status.Created.id - val created: JsObject = Json.obj(F.status -> Status.Created.id) + val started: Bdoc = F.status $gte Status.Started.id - val started: JsObject = Json.obj(F.status -> $gte(Status.Started.id)) + def started(u: String): Bdoc = user(u) ++ started - def started(u: String): JsObject = user(u) ++ started + val playable: Bdoc = F.status $lt Status.Aborted.id - val playable = Json.obj(F.status -> $lt(Status.Aborted.id)) + val mate: Bdoc = status(Status.Mate) - val mate = status(Status.Mate) + val draw: Bdoc = F.status $in (Status.Draw.id, Status.Stalemate.id) - val draw: JsObject = Json.obj(F.status -> $in(Seq(Status.Draw.id, Status.Stalemate.id))) + def draw(u: String): Bdoc = user(u) ++ draw - def draw(u: String): JsObject = user(u) ++ draw + val finished: Bdoc = F.status $gte Status.Mate.id - val finished = Json.obj(F.status -> $gte(Status.Mate.id)) + val notFinished: Bdoc = F.status $lte Status.Started.id - val notFinished: JsObject = Json.obj(F.status -> $lte(Status.Started.id)) + def analysed(an: Boolean): Bdoc = F.analysed $eq an - def analysed(an: Boolean): JsObject = Json.obj(F.analysed -> an) + def turnsMoreThan(length: Int): Bdoc = F.turns $eq $gte(length) - def turnsMoreThan(length: Int): JsObject = Json.obj(F.turns -> $gte(length)) + val frozen: Bdoc = F.status $gte Status.Mate.id - val frozen = Json.obj(F.status -> $gte(Status.Mate.id)) + def imported(u: String): Bdoc = s"${F.pgnImport}.user" $eq u - def imported(u: String): JsObject = Json.obj(s"${F.pgnImport}.user" -> u) + val friend: Bdoc = s"${F.source}" $eq Source.Friend.id - val friend = Json.obj(s"${F.source}" -> Source.Friend.id) + def clock(c: Boolean): Bdoc = F.clock $exists c - def clock(c: Boolean) = Json.obj(F.clock -> $exists(c)) + def user(u: String): Bdoc = F.playerUids $eq u + def user(u: User): Bdoc = F.playerUids $eq u.id + def users(u: Seq[String]) = F.playerUids $in (u: _*) - def user(u: String) = Json.obj(F.playerUids -> u) - def users(u: Seq[String]) = Json.obj(F.playerUids -> $in(u)) + val noAi: Bdoc = $doc( + "p0.ai" $exists false, + "p1.ai" $exists false) - val noAi = Json.obj( - "p0.ai" -> $exists(false), - "p1.ai" -> $exists(false)) - - def nowPlaying(u: String) = Json.obj(F.playingUids -> u) + def nowPlaying(u: String) = $doc(F.playingUids -> u) def recentlyPlaying(u: String) = - nowPlaying(u) ++ Json.obj( - F.updatedAt -> $gt($date(DateTime.now minusMinutes 5)) - ) + nowPlaying(u) ++ $doc(F.updatedAt $gt DateTime.now.minusMinutes(5)) // use the us index - def win(u: String) = user(u) ++ Json.obj(F.winnerId -> u) + def win(u: String) = user(u) ++ $doc(F.winnerId -> u) - def loss(u: String) = user(u) ++ - Json.obj(F.status -> $in(Status.finishedWithWinner map (_.id))) ++ - Json.obj(F.winnerId -> ($ne(u) ++ $exists(true))) + def loss(u: String) = user(u) ++ $doc( + F.status $in (Status.finishedWithWinner.map(_.id): _*), + F.winnerId -> $exists(true).++($ne(u)) + ) def opponents(u1: User, u2: User) = - Json.obj(F.playerUids -> $all(List(u1, u2).sortBy(_.count.game).map(_.id))) + $doc(F.playerUids.$all(List(u1, u2).sortBy(_.count.game).map(_.id):_*)) - val noProvisional = Json.obj("p0.p" -> $exists(false), "p1.p" -> $exists(false)) + val noProvisional: Bdoc = $doc( + "p0.p" $exists false, + "p1.p" $exists false) - def bothRatingsGreaterThan(v: Int) = Json.obj("p0.e" -> $gt(v), "p1.e" -> $gt(v)) + def bothRatingsGreaterThan(v: Int) = $doc("p0.e" $gt v, "p1.e" $gt v) - def turnsGt(nb: Int) = Json.obj(F.turns -> $gt(nb)) + def turnsGt(nb: Int) = F.turns $gt nb - def checkable = Json.obj(F.checkAt -> $lt($date(DateTime.now))) + def checkable = F.checkAt $lt DateTime.now def variant(v: chess.variant.Variant) = - Json.obj(F.variant -> v.standard.fold($exists(false), v.id)) + $doc(F.variant -> v.standard.fold[BSONValue]($exists(false), $int(v.id))) - lazy val notHordeOrSincePawnsAreWhite = $or(Seq( - Json.obj(F.variant -> $ne(chess.variant.Horde.id)), + lazy val notHordeOrSincePawnsAreWhite: Bdoc = $or( + F.variant $ne chess.variant.Horde.id, sinceHordePawnsAreWhite - )) + ) - lazy val sinceHordePawnsAreWhite = - Json.obj(F.createdAt -> $gt($date(hordeWhitePawnsSince))) + lazy val sinceHordePawnsAreWhite: Bdoc = + F.createdAt $gt hordeWhitePawnsSince val hordeWhitePawnsSince = new DateTime(2015, 4, 11, 10, 0) - def notFromPosition = - Json.obj(F.variant -> $ne(chess.variant.FromPosition.id)) + val notFromPosition: Bdoc = + F.variant $ne chess.variant.FromPosition.id - def createdSince(d: DateTime) = - Json.obj(F.createdAt -> $gt($date(d))) + def createdSince(d: DateTime): Bdoc = + F.createdAt $gt d - val sortCreated = $sort desc F.createdAt - val sortChronological = $sort asc F.createdAt - val sortAntiChronological = $sort desc F.createdAt - val sortUpdatedNoIndex = $sort desc F.updatedAt + val sortCreated: Bdoc = $sort desc F.createdAt + val sortChronological: Bdoc = $sort asc F.createdAt + val sortAntiChronological: Bdoc = $sort desc F.createdAt + val sortUpdatedNoIndex: Bdoc = $sort desc F.updatedAt } diff --git a/modules/game/src/main/package.scala b/modules/game/src/main/package.scala index 06db34c9e6..84c94e51f3 100644 --- a/modules/game/src/main/package.scala +++ b/modules/game/src/main/package.scala @@ -1,15 +1,8 @@ package lila -import lila.db.{ JsTube, InColl } - package object game extends PackageObject with WithPlay { type PgnMoves = List[String] - object tube { - - implicit lazy val gameTube = Game.tube inColl Env.current.gameColl - } - private[game] def logger = lila.log("game") } diff --git a/modules/gameSearch/src/main/GameSearchApi.scala b/modules/gameSearch/src/main/GameSearchApi.scala index be648d1f3b..909e1dc885 100644 --- a/modules/gameSearch/src/main/GameSearchApi.scala +++ b/modules/gameSearch/src/main/GameSearchApi.scala @@ -7,6 +7,7 @@ import play.api.libs.json._ import scala.util.{ Try, Success, Failure } import lila.common.PimpedJson._ +import lila.db.dsl._ import lila.game.actorApi._ import lila.game.{ Game, GameRepo } import lila.search._ @@ -17,9 +18,7 @@ final class GameSearchApi(client: ESClient) extends SearchReadApi[Game, Query] { def search(query: Query, from: From, size: Size) = client.search(query, from, size) flatMap { res => - import lila.db.api.$find - import lila.game.tube.gameTube - $find.byOrderedIds[lila.game.Game](res.ids) + GameRepo games res.ids } def count(query: Query) = @@ -106,10 +105,10 @@ final class GameSearchApi(client: ESClient) extends SearchReadApi[Game, Query] { val maxGames = Int.MaxValue // val maxGames = 10 * 1000 * 1000 - lila.game.tube.gameTube.coll.find(BSONDocument( - "ca" -> BSONDocument("$gt" -> since) - )).sort(BSONDocument("ca" -> 1)) - .cursor[Game](ReadPreference.secondaryPreferred) + GameRepo.sortedCursor( + selector = $doc("ca" $gt since), + sort = $doc("ca" -> 1), + readPreference = ReadPreference.secondaryPreferred) .enumerate(maxGames, stopOnError = true) &> Enumeratee.grouped(Iteratee takeUpTo batchSize) |>>> Enumeratee.mapM[Seq[Game]].apply[(Seq[Game], Set[String])] { games => diff --git a/modules/history/src/main/HistoryApi.scala b/modules/history/src/main/HistoryApi.scala index ca42aefc97..2dc6ef478a 100644 --- a/modules/history/src/main/HistoryApi.scala +++ b/modules/history/src/main/HistoryApi.scala @@ -4,8 +4,7 @@ import org.joda.time.{ DateTime, Days } import reactivemongo.bson._ import chess.Speed -import lila.db.api._ -import lila.db.Types.Coll +import lila.db.dsl._ import lila.game.Game import lila.user.{ User, Perfs } @@ -46,5 +45,5 @@ final class HistoryApi(coll: Coll) { Days.daysBetween(from.withTimeAtStartOfDay, to.withTimeAtStartOfDay).getDays def get(userId: String): Fu[Option[History]] = - coll.find(BSONDocument("_id" -> userId)).one[History] + coll.find(BSONDocument("_id" -> userId)).uno[History] } diff --git a/modules/hub/src/main/actorApi.scala b/modules/hub/src/main/actorApi.scala index 98765691ed..70eae82f75 100644 --- a/modules/hub/src/main/actorApi.scala +++ b/modules/hub/src/main/actorApi.scala @@ -104,21 +104,6 @@ case class GameEnd(playerId: String, opponent: Option[String], win: Option[Boole case class SimulCreate(userId: String, simulId: String, simulName: String) extends Atom(s"simulCreate", true) case class SimulJoin(userId: String, simulId: String, simulName: String) extends Atom(s"simulJoin", true) -object atomFormat { - implicit val followFormat = Json.format[Follow] - implicit val teamJoinFormat = Json.format[TeamJoin] - implicit val teamCreateFormat = Json.format[TeamCreate] - implicit val forumPostFormat = Json.format[ForumPost] - implicit val noteCreateFormat = Json.format[NoteCreate] - implicit val tourJoinFormat = Json.format[TourJoin] - implicit val qaQuestionFormat = Json.format[QaQuestion] - implicit val qaAnswerFormat = Json.format[QaAnswer] - implicit val qaCommentFormat = Json.format[QaComment] - implicit val gameEndFormat = Json.format[GameEnd] - implicit val simulCreateFormat = Json.format[SimulCreate] - implicit val simulJoinFormat = Json.format[SimulJoin] -} - object propagation { sealed trait Propagation case class Users(users: List[String]) extends Propagation diff --git a/modules/i18n/src/main/DataForm.scala b/modules/i18n/src/main/DataForm.scala index 2ee3bf5038..7124ca1eb5 100644 --- a/modules/i18n/src/main/DataForm.scala +++ b/modules/i18n/src/main/DataForm.scala @@ -5,10 +5,8 @@ import play.api.data._ import play.api.data.Forms._ import play.api.mvc.Request -import lila.db.api.$insert -import tube.translationTube - final class DataForm( + repo: TranslationRepo, keys: I18nKeys, val captcher: akka.actor.ActorSelection, callApi: CallApi) extends lila.hub.CaptchedForm { @@ -28,7 +26,7 @@ final class DataForm( }).toList collect { case (key, Some(value)) => key -> value } - messages.nonEmpty ?? TranslationRepo.nextId flatMap { id => + messages.nonEmpty ?? repo.nextId flatMap { id => val sorted = (keys.keys map { key => messages find (_._1 == key.key) }).flatten @@ -41,7 +39,7 @@ final class DataForm( comment = metadata.comment, author = user.some, createdAt = DateTime.now) - $insert(translation) >>- callApi.submit(code) + repo.insert(translation).void >>- callApi.submit(code) } } diff --git a/modules/i18n/src/main/Env.scala b/modules/i18n/src/main/Env.scala index d1b3260c8d..d23e2efa49 100644 --- a/modules/i18n/src/main/Env.scala +++ b/modules/i18n/src/main/Env.scala @@ -31,7 +31,7 @@ final class Env( def hideCallsCookieName = HideCallsCookieName def hideCallsCookieMaxAge = HideCallsCookieMaxAge - private[i18n] lazy val translationColl = db(CollectionTranslation) + private val translationColl = db(CollectionTranslation) lazy val pool = new I18nPool( langs = Lang.availables(play.api.Play.current).toSet, @@ -63,7 +63,10 @@ final class Env( messages = messages, keys = keys) + lazy val repo = new TranslationRepo(translationColl) + lazy val forms = new DataForm( + repo = repo, keys = keys, captcher = captcher, callApi = callApi) @@ -85,8 +88,7 @@ final class Env( def call = callApi.apply _ def jsonFromVersion(v: Int): Fu[JsValue] = { - import tube.translationTube - TranslationRepo findFrom v map { ts => Json toJson ts } + repo findFrom v map { ts => Json toJson ts } } def cli = new lila.common.Cli { diff --git a/modules/i18n/src/main/Translation.scala b/modules/i18n/src/main/Translation.scala index c937ec6c82..8f53642110 100644 --- a/modules/i18n/src/main/Translation.scala +++ b/modules/i18n/src/main/Translation.scala @@ -17,18 +17,7 @@ private[i18n] case class Translation( private[i18n] object Translation { - import lila.db.JsTube - import JsTube.Helpers._ import play.api.libs.json._ - private def defaults = Json.obj( - "author" -> none[String], - "comment" -> none[String]) - - private[i18n] val tube = JsTube( - (__.json update ( - merge(defaults) andThen readDate('createdAt) - )) andThen Json.reads[Translation], - Json.writes[Translation] andThen (__.json update writeDate('createdAt)) - ) + private[i18n] implicit val translationI18nFormat = Json.format[Translation] } diff --git a/modules/i18n/src/main/TranslationRepo.scala b/modules/i18n/src/main/TranslationRepo.scala index 94b830693a..f02e254bca 100644 --- a/modules/i18n/src/main/TranslationRepo.scala +++ b/modules/i18n/src/main/TranslationRepo.scala @@ -2,20 +2,20 @@ package lila.i18n import play.api.libs.json.Json -import lila.db.api._ -import lila.db.Implicits._ -import tube.translationTube +import lila.db.dsl._ +import lila.db.BSON.BSONJodaDateTimeHandler -private[i18n] object TranslationRepo { +private[i18n] final class TranslationRepo(coll: Coll) { - type ID = Int + private implicit val TranslationBSONHandler = reactivemongo.bson.Macros.handler[Translation] - def nextId: Fu[ID] = $primitive.one( - $select.all, - "_id", - _ sort $sort.descId - )(_.asOpt[Int]) map (opt => ~opt + 1) + def nextId: Fu[Int] = coll.primitiveOne[Int]( + selector = $empty, + sort = $sort desc "_id", + "_id") map (opt => ~opt + 1) - def findFrom(id: ID): Fu[List[Translation]] = - $find($query(Json.obj("_id" -> $gte(id))) sort $sort.ascId) + def findFrom(id: Int): Fu[List[Translation]] = + coll.find($doc("_id" $gte id)).sort($sort asc "_id").cursor[Translation]().gather[List]() + + def insert(t: Translation) = coll insert t } diff --git a/modules/i18n/src/main/UpstreamFetch.scala b/modules/i18n/src/main/UpstreamFetch.scala index b698d6374d..d480a8fcf9 100644 --- a/modules/i18n/src/main/UpstreamFetch.scala +++ b/modules/i18n/src/main/UpstreamFetch.scala @@ -5,12 +5,13 @@ import play.api.libs.json._ import lila.common.PimpedJson._ import play.api.libs.ws.WS import play.api.Play.current -import tube.translationTube private[i18n] final class UpstreamFetch(upstreamUrl: Int => String) { private type Fetched = Fu[List[Translation]] + import Translation.translationI18nFormat + def apply(from: Int): Fetched = fetch(upstreamUrl(from)) map parse flatMap { _.fold(e => fufail(e.toString), fuccess(_)) diff --git a/modules/i18n/src/main/package.scala b/modules/i18n/src/main/package.scala index 10357e188e..44c810abea 100644 --- a/modules/i18n/src/main/package.scala +++ b/modules/i18n/src/main/package.scala @@ -4,12 +4,6 @@ package object i18n extends PackageObject with WithPlay { type Messages = Map[String, Map[String, String]] - object tube { - - private[i18n] implicit lazy val translationTube = - Translation.tube inColl Env.current.translationColl - } - import scala.concurrent.Future private[i18n] def printToFile(f: java.io.File)(op: java.io.PrintWriter => Unit): Funit = Future { diff --git a/modules/importer/src/main/Importer.scala b/modules/importer/src/main/Importer.scala index ea82b7b6ff..6f292e8cdd 100644 --- a/modules/importer/src/main/Importer.scala +++ b/modules/importer/src/main/Importer.scala @@ -8,7 +8,7 @@ import akka.pattern.{ ask, after } import chess.{ Color, MoveOrDrop, Status, Situation } import makeTimeout.large -import lila.db.api._ +import lila.db.dsl._ import lila.game.{ Game, GameRepo, Pov } import lila.hub.actorApi.map.Tell import lila.round.actorApi.round._ diff --git a/modules/insight/src/main/AggregationPipeline.scala b/modules/insight/src/main/AggregationPipeline.scala index 6322d19431..e87b96ea49 100644 --- a/modules/insight/src/main/AggregationPipeline.scala +++ b/modules/insight/src/main/AggregationPipeline.scala @@ -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 ) diff --git a/modules/insight/src/main/BSONHandlers.scala b/modules/insight/src/main/BSONHandlers.scala index 1c3a8b1b03..abd653d2b9 100644 --- a/modules/insight/src/main/BSONHandlers.scala +++ b/modules/insight/src/main/BSONHandlers.scala @@ -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, diff --git a/modules/insight/src/main/Dimension.scala b/modules/insight/src/main/Dimension.scala index 84a44984e8..8f01437828 100644 --- a/modules/insight/src/main/Dimension.scala +++ b/modules/insight/src/main/Dimension.scala @@ -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]( @@ -141,15 +141,15 @@ object Dimension { case MaterialRange => v.id }).toString - def filtersOf[X](d: Dimension[X], selected: List[X]): BSONDocument = d match { + def filtersOf[X](d: Dimension[X], selected: List[X]): Bdoc = d match { case Dimension.MovetimeRange => selected match { - case Nil => BSONDocument() - case xs => BSONDocument(d.dbKey -> BSONDocument("$in" -> xs.flatMap(_.tenths.list))) + case Nil => $empty + case xs => $doc(d.dbKey -> $doc("$in" -> xs.flatMap(_.tenths.list))) } case _ => selected map d.bson.write match { - case Nil => BSONDocument() - case List(x) => BSONDocument(d.dbKey -> x) - case xs => BSONDocument(d.dbKey -> BSONDocument("$in" -> BSONArray(xs))) + case Nil => $empty + case List(x) => $doc(d.dbKey -> x) + case xs => $doc(d.dbKey -> $doc("$in" -> BSONArray(xs))) } } } diff --git a/modules/insight/src/main/Indexer.scala b/modules/insight/src/main/Indexer.scala index 8df5ec8467..aff2a683df 100644 --- a/modules/insight/src/main/Indexer.scala +++ b/modules/insight/src/main/Indexer.scala @@ -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.api._ -import lila.db.BSON._ -import lila.db.Implicits._ +import lila.db.dsl._ +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) + .uno[Game] + } orElse GameRepo.coll + .find(gameQuery(user)) + .sort(Query.sortCreated) + .uno[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 => diff --git a/modules/insight/src/main/InsightApi.scala b/modules/insight/src/main/InsightApi.scala index 1c4bb559eb..c5ae973e4e 100644 --- a/modules/insight/src/main/InsightApi.scala +++ b/modules/insight/src/main/InsightApi.scala @@ -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 diff --git a/modules/insight/src/main/Storage.scala b/modules/insight/src/main/Storage.scala index 0607e4ad24..9a503c5841 100644 --- a/modules/insight/src/main/Storage.scala +++ b/modules/insight/src/main/Storage.scala @@ -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 @@ -22,10 +21,10 @@ private final class Storage(coll: Coll) { coll.aggregate(operators.head, operators.tail, allowDiskUse = true) def fetchFirst(userId: String): Fu[Option[Entry]] = - coll.find(selectUserId(userId)).sort(sortChronological).one[Entry] + coll.find(selectUserId(userId)).sort(sortChronological).uno[Entry] def fetchLast(userId: String): Fu[Option[Entry]] = - coll.find(selectUserId(userId)).sort(sortAntiChronological).one[Entry] + coll.find(selectUserId(userId)).sort(sortAntiChronological).uno[Entry] def count(userId: String): Fu[Int] = coll.count(selectUserId(userId).some) @@ -42,7 +41,7 @@ private final class Storage(coll: Coll) { def removeAll(userId: String) = coll.remove(selectUserId(userId)).void - def find(id: String) = coll.find(selectId(id)).one[Entry] + def find(id: String) = coll.find(selectId(id)).uno[Entry] def ecos(userId: String): Fu[Set[String]] = coll.distinct(F.eco, selectUserId(userId).some) map lila.db.BSON.asStringSet diff --git a/modules/insight/src/main/UserCache.scala b/modules/insight/src/main/UserCache.scala index 73cd309807..c3995786c4 100644 --- a/modules/insight/src/main/UserCache.scala +++ b/modules/insight/src/main/UserCache.scala @@ -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.uno[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 } diff --git a/modules/lobby/src/main/Env.scala b/modules/lobby/src/main/Env.scala index 84374fc0fb..7bc75024fd 100644 --- a/modules/lobby/src/main/Env.scala +++ b/modules/lobby/src/main/Env.scala @@ -59,7 +59,7 @@ final class Env( hub = hub, lobby = lobby, socket = socket, - blocking = blocking) + blocking = blocking)(system) lazy val history = new History[actorApi.Messadata](ttl = MessageTtl) diff --git a/modules/lobby/src/main/Lobby.scala b/modules/lobby/src/main/Lobby.scala index 6b0f3ccf04..3d1a8811d7 100644 --- a/modules/lobby/src/main/Lobby.scala +++ b/modules/lobby/src/main/Lobby.scala @@ -6,7 +6,7 @@ import akka.actor._ import akka.pattern.{ ask, pipe } import actorApi._ -import lila.db.api._ +import lila.db.dsl._ import lila.game.GameRepo import lila.hub.actorApi.{ GetUids, SocketUids } import lila.socket.actorApi.Broom diff --git a/modules/lobby/src/main/SeekApi.scala b/modules/lobby/src/main/SeekApi.scala index 7fdd69d51d..237cb30df5 100644 --- a/modules/lobby/src/main/SeekApi.scala +++ b/modules/lobby/src/main/SeekApi.scala @@ -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,14 +21,14 @@ 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]]( f = { - case ForAnon => allCursor.collect[List](maxPerPage) - case ForUser => allCursor.collect[List]() + case ForAnon => allCursor.gather[List](maxPerPage) + case ForUser => allCursor.gather[List]() }, timeToLive = 3.seconds) @@ -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)).uno[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)) - .cursor[Seek]().collect[List]() + coll.find($doc("user.id" -> userId)) + .sort($doc("createdAt" -> -1)) + .cursor[Seek]().gather[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)).uno[Seek] def removeBy(seekId: String, userId: String) = - coll.remove(BSONDocument( + coll.remove($doc( "_id" -> seekId, "user.id" -> userId )).void >> cache.clear diff --git a/modules/lobby/src/main/Socket.scala b/modules/lobby/src/main/Socket.scala index c5d385f7f5..af84fd63fb 100644 --- a/modules/lobby/src/main/Socket.scala +++ b/modules/lobby/src/main/Socket.scala @@ -5,7 +5,6 @@ import scala.concurrent.Future import akka.actor._ import akka.pattern.ask -import play.api.libs.iteratee._ import play.api.libs.json._ import play.twirl.api.Html @@ -16,7 +15,7 @@ import lila.game.AnonCookie import lila.hub.actorApi.game.ChangeFeatured import lila.hub.actorApi.lobby._ import lila.hub.actorApi.timeline._ -import lila.socket.actorApi.{ Connected => _, _ } +import lila.socket.actorApi._ import lila.socket.{ SocketActor, History, Historical } import makeTimeout.short @@ -52,11 +51,7 @@ private[lobby] final class Socket( } } - case Join(uid, user, blocks, mobile) => - val (enumerator, channel) = Concurrent.broadcast[JsValue] - val member = Member(channel, user, blocks, uid, mobile) - addMember(uid, member) - sender ! Connected(enumerator, member) + case AddMember(uid, member) => addMember(uid, member) case ReloadTournaments(html) => notifyAllAsync(makeMessage("tournaments", html)) diff --git a/modules/lobby/src/main/SocketHandler.scala b/modules/lobby/src/main/SocketHandler.scala index 63bd02b21f..edf3109d8f 100644 --- a/modules/lobby/src/main/SocketHandler.scala +++ b/modules/lobby/src/main/SocketHandler.scala @@ -2,14 +2,13 @@ package lila.lobby import akka.actor._ import akka.pattern.{ ask, pipe } -import play.api.libs.iteratee._ import play.api.libs.json._ import scala.concurrent.duration._ import actorApi._ import lila.common.PimpedJson._ import lila.hub.actorApi.lobby._ -import lila.socket.actorApi.{ Connected => _, _ } +import lila.socket.actorApi._ import lila.socket.Handler import lila.user.User import makeTimeout.short @@ -18,7 +17,7 @@ private[lobby] final class SocketHandler( hub: lila.hub.Env, lobby: ActorRef, socket: ActorRef, - blocking: String => Fu[Set[String]]) { + blocking: String => Fu[Set[String]])(implicit system: ActorSystem) { private def controller( socket: ActorRef, @@ -40,12 +39,14 @@ private[lobby] final class SocketHandler( } lobby ! CancelSeek(id, user) } - def apply(uid: String, user: Option[User], mobile: Boolean): Fu[JsSocketHandler] = - (user ?? (u => blocking(u.id))) flatMap { blockedUserIds => - val join = Join(uid = uid, user = user, blocking = blockedUserIds, mobile = mobile) - Handler(hub, socket, uid, join, user map (_.id)) { - case Connected(enum, member) => - (controller(socket, uid, member), enum, member) + def apply(uid: String, user: Option[User], mobile: Boolean): Fu[JsFlow] = + (user ?? (u => blocking(u.id))) map { blockedUserIds => + Handler.actorRef { out => + val member = Member(out, user, blockedUserIds, uid, mobile) + socket ! AddMember(uid, member) + Handler.props(hub, socket, member, uid, user.map(_.id)) { + controller(socket, uid, member) + } } } } diff --git a/modules/lobby/src/main/actorApi.scala b/modules/lobby/src/main/actorApi.scala index 8b79e18197..f36d17cdb4 100644 --- a/modules/lobby/src/main/actorApi.scala +++ b/modules/lobby/src/main/actorApi.scala @@ -1,6 +1,8 @@ package lila.lobby package actorApi +import akka.actor.ActorRef + import lila.game.Game import lila.socket.SocketMember import lila.user.User @@ -29,7 +31,7 @@ private[lobby] object LobbyUser { } private[lobby] case class Member( - channel: JsChannel, + out: ActorRef, user: Option[LobbyUser], uid: String, mobile: Boolean) extends SocketMember { @@ -40,8 +42,8 @@ private[lobby] case class Member( private[lobby] object Member { - def apply(channel: JsChannel, user: Option[User], blocking: Set[String], uid: String, mobile: Boolean): Member = Member( - channel = channel, + def apply(out: ActorRef, user: Option[User], blocking: Set[String], uid: String, mobile: Boolean): Member = Member( + out = out, user = user map { LobbyUser.make(_, blocking) }, uid = uid, mobile = mobile) @@ -51,7 +53,6 @@ private[lobby] case class HookMeta(hookId: Option[String] = None) private[lobby] case class Messadata(hook: Option[Hook] = None) -private[lobby] case class Connected(enumerator: JsEnumerator, member: Member) private[lobby] case class WithHooks(op: Iterable[String] => Unit) private[lobby] case class SaveHook(msg: AddHook) private[lobby] case class SaveSeek(msg: AddSeek) @@ -64,7 +65,7 @@ private[lobby] case class BiteHook(hookId: String, uid: String, user: Option[Lob private[lobby] case class BiteSeek(seekId: String, user: LobbyUser) private[lobby] case class JoinHook(uid: String, hook: Hook, game: Game, creatorColor: chess.Color) private[lobby] case class JoinSeek(userId: String, seek: Seek, game: Game, creatorColor: chess.Color) -private[lobby] case class Join(uid: String, user: Option[User], blocking: Set[String], mobile: Boolean) +private[lobby] case class AddMember(uid: String, member: Member) private[lobby] case object Resync private[lobby] case class HookIds(ids: Vector[String]) diff --git a/modules/memo/src/main/Env.scala b/modules/memo/src/main/Env.scala index 2bcb677459..9cf9652661 100644 --- a/modules/memo/src/main/Env.scala +++ b/modules/memo/src/main/Env.scala @@ -1,7 +1,7 @@ package lila.memo import com.typesafe.config.Config -import lila.db.Types._ +import lila.db.dsl._ final class Env(config: Config, db: lila.db.Env) { diff --git a/modules/memo/src/main/MongoCache.scala b/modules/memo/src/main/MongoCache.scala index fac348308e..91e2600da4 100644 --- a/modules/memo/src/main/MongoCache.scala +++ b/modules/memo/src/main/MongoCache.scala @@ -7,7 +7,7 @@ import scala.concurrent.duration._ import spray.caching.{ LruCache, Cache } import lila.db.BSON.BSONJodaDateTimeHandler -import lila.db.Types._ +import lila.db.dsl._ final class MongoCache[K, V: MongoCache.Handler] private ( prefix: String, @@ -18,7 +18,7 @@ final class MongoCache[K, V: MongoCache.Handler] private ( keyToString: K => String) { def apply(k: K): Fu[V] = cache(k) { - coll.find(select(k)).one[Entry] flatMap { + coll.find(select(k)).uno[Entry] flatMap { case None => f(k) flatMap { v => coll.insert(makeEntry(k, v)) recover lila.db.recoverDuplicateKey(_ => ()) inject v diff --git a/modules/message/src/main/Api.scala b/modules/message/src/main/Api.scala index b4c836cd13..ad9cf03937 100644 --- a/modules/message/src/main/Api.scala +++ b/modules/message/src/main/Api.scala @@ -3,37 +3,39 @@ package lila.message import akka.pattern.pipe import lila.common.paginator._ -import lila.db.api._ -import lila.db.Implicits._ +import lila.db.dsl._ import lila.db.paginator._ import lila.hub.actorApi.message._ import lila.hub.actorApi.SendTo import lila.security.Granter import lila.user.{ User, UserRepo } -import tube.threadTube final class Api( + coll: Coll, unreadCache: UnreadCache, shutup: akka.actor.ActorSelection, maxPerPage: Int, blocks: (String, String) => Fu[Boolean], bus: lila.common.Bus) { + import Thread.ThreadBSONHandler + def inbox(me: User, page: Int): Fu[Paginator[Thread]] = Paginator( adapter = new Adapter( + collection = coll, selector = ThreadRepo visibleByUserQuery me.id, - sort = Seq(ThreadRepo.recentSort) - ), + projection = $empty, + sort = ThreadRepo.recentSort), currentPage = page, maxPerPage = maxPerPage ) def preview(userId: String): Fu[List[Thread]] = unreadCache(userId) flatMap { ids => - $find byOrderedIds ids + coll.byOrderedIds[Thread](ids)(_.id) } def thread(id: String, me: User): Fu[Option[Thread]] = for { - threadOption ← $find.byId(id) map (_ filter (_ hasUser me)) + threadOption ← coll.byId[Thread](id) map (_ filter (_ hasUser me)) _ ← threadOption.filter(_ isUnReadBy me).??(thread => (ThreadRepo setRead thread) >>- updateUser(me) ) @@ -70,9 +72,9 @@ final class Api( invitedId = lt.to), fromMod = false) >> unreadCache.clear(lt.to) private def sendUnlessBlocked(thread: Thread, fromMod: Boolean): Funit = - if (fromMod) $insert(thread) + if (fromMod) coll.insert(thread).void else blocks(thread.invitedId, thread.creatorId) flatMap { - !_ ?? $insert(thread) + !_ ?? coll.insert(thread).void } def makePost(thread: Thread, text: String, me: User): Fu[Thread] = { @@ -84,7 +86,7 @@ final class Api( case true => fuccess(thread) case false => val newThread = thread + post - $update[ThreadRepo.ID, Thread](newThread) >>- { + coll.update($id(newThread.id), newThread) >>- { UserRepo.named(thread receiverOf post) foreach { _ foreach updateUser } @@ -104,7 +106,7 @@ final class Api( val unreadIds = unreadCache apply _ def updateUser(user: lila.user.User) { - if (!user.kid) (unreadCache refresh user) mapTo manifest[List[String]] foreach { ids => + if (!user.kid) (unreadCache refresh user.id) mapTo manifest[List[String]] foreach { ids => bus.publish(SendTo(user.id, "nbm", ids.size), 'users) } } diff --git a/modules/message/src/main/Env.scala b/modules/message/src/main/Env.scala index 61079d8034..553380980d 100644 --- a/modules/message/src/main/Env.scala +++ b/modules/message/src/main/Env.scala @@ -29,6 +29,7 @@ final class Env( lazy val forms = new DataForm(security = security) lazy val api = new Api( + coll = threadColl, unreadCache = unreadCache, shutup = shutup, maxPerPage = ThreadMaxPerPage, diff --git a/modules/message/src/main/Post.scala b/modules/message/src/main/Post.scala index 9947020f02..0fee0b65b4 100644 --- a/modules/message/src/main/Post.scala +++ b/modules/message/src/main/Post.scala @@ -30,12 +30,6 @@ object Post { isRead = false, createdAt = DateTime.now) - import lila.db.JsTube - import JsTube.Helpers._ - import play.api.libs.json._ - - private[message] lazy val tube = JsTube( - (__.json update readDate('createdAt)) andThen Json.reads[Post], - Json.writes[Post].andThen(__.json update writeDate('createdAt)) - ) + import lila.db.dsl.BSONJodaDateTimeHandler + private[message] implicit val PostBSONHandler = reactivemongo.bson.Macros.handler[Post] } diff --git a/modules/message/src/main/Thread.scala b/modules/message/src/main/Thread.scala index a48a225630..7c32772ff7 100644 --- a/modules/message/src/main/Thread.scala +++ b/modules/message/src/main/Thread.scala @@ -6,7 +6,7 @@ import ornicar.scalalib.Random import lila.user.User case class Thread( - id: String, + _id: String, name: String, createdAt: DateTime, updatedAt: DateTime, @@ -19,6 +19,8 @@ case class Thread( posts = posts :+ post, updatedAt = post.createdAt) + def id = _id + def isCreator(user: User) = creatorId == user.id def isReadBy(user: User) = nbUnreadBy(user) == 0 @@ -67,7 +69,7 @@ object Thread { text: String, creatorId: String, invitedId: String): Thread = Thread( - id = Random nextStringUppercase idSize, + _id = Random nextStringUppercase idSize, name = name, createdAt = DateTime.now, updatedAt = DateTime.now, @@ -79,18 +81,10 @@ object Thread { invitedId = invitedId, visibleByUserIds = List(creatorId, invitedId)) - import lila.db.JsTube - import JsTube.Helpers._ - import play.api.libs.json._ - - private[message] lazy val tube = Post.tube |> { implicit pt => - JsTube( - (__.json update ( - readDate('createdAt) andThen readDate('updatedAt) - )) andThen Json.reads[Thread], - Json.writes[Thread] andThen (__.json update ( - writeDate('createdAt) andThen writeDate('updatedAt) - )) - ) - } + import lila.db.dsl.BSONJodaDateTimeHandler + import Post.PostBSONHandler + private[message] implicit val ThreadBSONHandler = + lila.db.BSON.LoggingHandler(lila.log("message")) { + reactivemongo.bson.Macros.handler[Thread] + } } diff --git a/modules/message/src/main/ThreadRepo.scala b/modules/message/src/main/ThreadRepo.scala index 23ace30ca6..c8570e9f96 100644 --- a/modules/message/src/main/ThreadRepo.scala +++ b/modules/message/src/main/ThreadRepo.scala @@ -1,81 +1,75 @@ package lila.message -import scala.concurrent.Future +import reactivemongo.api.collections.bson.BSONBatchCommands.AggregationFramework._ +import reactivemongo.bson._ -import play.api.libs.json.Json - -import lila.common.PimpedJson._ -import lila.db.api._ -import lila.db.Implicits._ -import tube.threadTube +import lila.db.dsl._ object ThreadRepo { - import play.modules.reactivemongo.json._ + + // dirty + private val coll = Env.current.threadColl type ID = String def byUser(user: ID): Fu[List[Thread]] = - $find($query(userQuery(user)) sort recentSort) + coll.find(userQuery(user)).sort(recentSort).cursor[Thread]().gather[List]() def visibleByUser(user: ID): Fu[List[Thread]] = - $find($query(visibleByUserQuery(user)) sort recentSort) + coll.find(visibleByUserQuery(user)).sort(recentSort).cursor[Thread]().gather[List]() def visibleByUser(user: ID, nb: Int): Fu[List[Thread]] = - $find($query(visibleByUserQuery(user)) sort recentSort, nb) - - def userUnreadIds(userId: String): Fu[List[String]] = { - import reactivemongo.bson._ - import reactivemongo.api.collections.bson.BSONBatchCommands.AggregationFramework._ - threadTube.coll.aggregate( - Match(BSONDocument( - "visibleByUserIds" -> userId, + coll.find(visibleByUserQuery(user)).sort(recentSort).cursor[Thread]().gather[List](nb) + + def userUnreadIds(userId: String): Fu[List[String]] = coll.aggregate( + Match($doc( + "visibleByUserIds" -> userId, + "posts.isRead" -> false + )), + List( + Project($doc( + "m" -> $doc("$eq" -> BSONArray("$creatorId", userId)), + "posts.isByCreator" -> true, + "posts.isRead" -> true + )), + Unwind("posts"), + Match($doc( "posts.isRead" -> false )), - List( - Project(BSONDocument( - "m" -> BSONDocument("$eq" -> BSONArray("$creatorId", userId)), - "posts.isByCreator" -> true, - "posts.isRead" -> true - )), - Unwind("posts"), - Match(BSONDocument( - "posts.isRead" -> false - )), - Project(BSONDocument( - "u" -> BSONDocument("$ne" -> BSONArray("$posts.isByCreator", "$m")) - )), - Match(BSONDocument( - "u" -> true - )), - Group(BSONBoolean(true))("ids" -> AddToSet("_id")) - ), - allowDiskUse = false - ).map { - _.documents.headOption ?? { ~_.getAs[List[String]]("ids") } - } - } + Project($doc( + "u" -> $doc("$ne" -> BSONArray("$posts.isByCreator", "$m")) + )), + Match($doc( + "u" -> true + )), + Group(BSONBoolean(true))("ids" -> AddToSet("_id")) + ), + allowDiskUse = false + ).map { + _.documents.headOption ?? { ~_.getAs[List[String]]("ids") } + } def setRead(thread: Thread): Funit = { List.fill(thread.nbUnread) { - $update( - $select(thread.id) ++ Json.obj("posts.isRead" -> false), + coll.update( + $id(thread.id) ++ $doc("posts.isRead" -> false), $set("posts.$.isRead" -> true) - ) + ).void } }.sequenceFu.void def deleteFor(user: ID)(thread: ID) = - $update($select(thread), $pull("visibleByUserIds", user)) + coll.update($id(thread), $pull("visibleByUserIds", user)).void - def reallyDeleteByCreatorId(user: ID) = $remove(Json.obj("creatorId" -> user)) + def reallyDeleteByCreatorId(user: ID) = coll.remove($doc("creatorId" -> user)) def visibleByUserContainingExists(user: ID, containing: String): Fu[Boolean] = - $count.exists(visibleByUserQuery(user) ++ Json.obj( - "posts.0.text" -> $regex(containing))) + coll.exists(visibleByUserQuery(user) ++ $doc( + "posts.0.text".$regex(containing, ""))) - def userQuery(user: String) = Json.obj("userIds" -> user) + def userQuery(user: String) = $doc("userIds" -> user) - def visibleByUserQuery(user: String) = Json.obj("visibleByUserIds" -> user) + def visibleByUserQuery(user: String) = $doc("visibleByUserIds" -> user) val recentSort = $sort desc "updatedAt" } diff --git a/modules/message/src/main/package.scala b/modules/message/src/main/package.scala index 11df4bd54d..8d108ac8d9 100644 --- a/modules/message/src/main/package.scala +++ b/modules/message/src/main/package.scala @@ -1,10 +1,3 @@ package lila -package object message extends PackageObject with WithPlay { - - object tube { - - private[message] implicit lazy val threadTube = - Thread.tube inColl Env.current.threadColl - } -} +package object message extends PackageObject with WithPlay diff --git a/modules/mod/src/main/AssessApi.scala b/modules/mod/src/main/AssessApi.scala index 2822fbab0b..b8c01d469c 100644 --- a/modules/mod/src/main/AssessApi.scala +++ b/modules/mod/src/main/AssessApi.scala @@ -3,7 +3,7 @@ package lila.mod import akka.actor.ActorSelection import lila.analyse.{ Analysis, AnalysisRepo } import lila.db.BSON.BSONJodaDateTimeHandler -import lila.db.Types.Coll +import lila.db.dsl._ import lila.evaluation.Statistics import lila.evaluation.{ AccountAction, Analysed, GameAssessment, PlayerAssessment, PlayerAggregateAssessment, PlayerFlags, PlayerAssessments, Assessible } import lila.game.{ Game, Player, GameRepo, Source, Pov } @@ -29,17 +29,16 @@ final class AssessApi( private implicit val playerAssessmentBSONhandler = Macros.handler[PlayerAssessment] def createPlayerAssessment(assessed: PlayerAssessment) = - collAssessments.update(BSONDocument("_id" -> assessed._id), assessed, upsert = true).void + collAssessments.update($id(assessed._id), assessed, upsert = true).void def getPlayerAssessmentById(id: String) = - collAssessments.find(BSONDocument("_id" -> id)) - .one[PlayerAssessment] + collAssessments.byId[PlayerAssessment](id) def getPlayerAssessmentsByUserId(userId: String, nb: Int = 100) = - collAssessments.find(BSONDocument("userId" -> userId)) - .sort(BSONDocument("date" -> -1)) + collAssessments.find($doc("userId" -> userId)) + .sort($doc("date" -> -1)) .cursor[PlayerAssessment]() - .collect[List](nb) + .gather[List](nb) def getResultsByGameIdAndColor(gameId: String, color: Color) = getPlayerAssessmentById(gameId + "/" + color.name) diff --git a/modules/mod/src/main/Boosting.scala b/modules/mod/src/main/Boosting.scala index f392475744..1ad9146e39 100644 --- a/modules/mod/src/main/Boosting.scala +++ b/modules/mod/src/main/Boosting.scala @@ -2,7 +2,7 @@ package lila.mod import chess.Color import chess.variant -import lila.db.Types.Coll +import lila.db.dsl._ import lila.game.Game import lila.user.User @@ -25,11 +25,10 @@ final class BoostingApi( variant.ThreeCheck) def getBoostingRecord(id: String): Fu[Option[BoostingRecord]] = - collBoosting.find(BSONDocument("_id" -> id)) - .one[BoostingRecord] + collBoosting.byId[BoostingRecord](id) def createBoostRecord(record: BoostingRecord) = - collBoosting.update(BSONDocument("_id" -> record.id), record, upsert = true).void + collBoosting.update($id(record.id), record, upsert = true).void def determineBoosting(record: BoostingRecord, winner: User, loser: User): Funit = (record.games >= nbGamesToMark) ?? { diff --git a/modules/mod/src/main/Env.scala b/modules/mod/src/main/Env.scala index 220f10077a..4b89d9bbcf 100644 --- a/modules/mod/src/main/Env.scala +++ b/modules/mod/src/main/Env.scala @@ -3,7 +3,7 @@ package lila.mod import akka.actor._ import com.typesafe.config.Config -import lila.db.Types.Coll +import lila.db.dsl.Coll import lila.security.{ Firewall, UserSpy } final class Env( @@ -31,7 +31,7 @@ final class Env( private[mod] lazy val logColl = db(CollectionModlog) - lazy val logApi = new ModlogApi + lazy val logApi = new ModlogApi(logColl) lazy val api = new ModApi( logApi = logApi, diff --git a/modules/mod/src/main/Gamify.scala b/modules/mod/src/main/Gamify.scala index 501fd6cbf8..1364004ae6 100644 --- a/modules/mod/src/main/Gamify.scala +++ b/modules/mod/src/main/Gamify.scala @@ -6,7 +6,7 @@ import reactivemongo.api.collections.bson.BSONBatchCommands.AggregationFramework import reactivemongo.bson._ import scala.concurrent.duration._ -import lila.db.Implicits._ +import lila.db.dsl._ import lila.memo.AsyncCache final class Gamify( @@ -19,10 +19,10 @@ final class Gamify( def history(orCompute: Boolean = true): Fu[List[HistoryMonth]] = { val until = DateTime.now minusMonths 1 withDayOfMonth 1 val lastId = HistoryMonth.makeId(until.getYear, until.getMonthOfYear) - historyColl.find(BSONDocument()).sort(BSONDocument( + historyColl.find($empty).sort($doc( "year" -> -1, "month" -> -1 - )).cursor[HistoryMonth]().collect[List]().flatMap { months => + )).cursor[HistoryMonth]().gather[List]().flatMap { months => months.headOption match { case Some(m) if m._id == lastId || !orCompute => fuccess(months) case Some(m) => buildHistoryAfter(m.year, m.month, until) >> history(false) @@ -49,7 +49,7 @@ final class Gamify( }.toList }.toList.sequenceFu.map(_.flatten).flatMap { _.map { month => - historyColl.update(BSONDocument("_id" -> month._id), month, upsert = true) + historyColl.update($doc("_id" -> month._id), month, upsert = true).void }.sequenceFu }.void @@ -73,12 +73,12 @@ final class Gamify( } private def dateRange(from: DateTime, toOption: Option[DateTime]) = - BSONDocument("$gte" -> from) ++ toOption.?? { to => BSONDocument("$lt" -> to) } + $doc("$gte" -> from) ++ toOption.?? { to => $doc("$lt" -> to) } - private val notLichess = BSONDocument("$ne" -> "lichess") + private val notLichess = $doc("$ne" -> "lichess") private def actionLeaderboard(after: DateTime, before: Option[DateTime]): Fu[List[ModCount]] = - logColl.aggregate(Match(BSONDocument( + logColl.aggregate(Match($doc( "date" -> dateRange(after, before), "mod" -> notLichess )), List( @@ -91,7 +91,7 @@ final class Gamify( private def reportLeaderboard(after: DateTime, before: Option[DateTime]): Fu[List[ModCount]] = reportColl.aggregate( - Match(BSONDocument( + Match($doc( "createdAt" -> dateRange(after, before), "processedBy" -> notLichess )), List( diff --git a/modules/mod/src/main/ModApi.scala b/modules/mod/src/main/ModApi.scala index 4d90c4f613..44fa7ae965 100644 --- a/modules/mod/src/main/ModApi.scala +++ b/modules/mod/src/main/ModApi.scala @@ -1,9 +1,8 @@ package lila.mod import chess.Color -import lila.db.api._ +import lila.db.dsl._ import lila.security.{ Firewall, UserSpy, Store => SecurityStore } -import lila.user.tube.userTube import lila.user.{ User, UserRepo, LightUserApi } final class ModApi( @@ -58,7 +57,7 @@ final class ModApi( val changed = value != u.troll val user = u.copy(troll = value) changed ?? { - UserRepo.updateTroll(user) >>- + UserRepo.updateTroll(user).void >>- logApi.troll(mod, user.id, user.troll) } >>- (reporter ! lila.hub.actorApi.report.MarkTroll(user.id, mod)) inject user.troll diff --git a/modules/mod/src/main/Modlog.scala b/modules/mod/src/main/Modlog.scala index 724860eb2b..72324bba23 100644 --- a/modules/mod/src/main/Modlog.scala +++ b/modules/mod/src/main/Modlog.scala @@ -66,18 +66,4 @@ object Modlog { val streamConfig = "streamConfig" val deleteTeam = "deleteTeam" val terminateTournament = "terminateTournament " - - import lila.db.JsTube - import JsTube.Helpers._ - import play.api.libs.json._ - - private[mod] lazy val tube = JsTube[Modlog]( - (__.json update ( - merge(defaults) andThen readDate('date) - )) andThen Json.reads[Modlog], - Json.writes[Modlog] andThen (__.json update writeDate('date)), - flags = Seq(_.NoId) - ) - - private def defaults = Json.obj("details" -> none[String]) } diff --git a/modules/mod/src/main/ModlogApi.scala b/modules/mod/src/main/ModlogApi.scala index 944fa638bf..41dee7dc80 100644 --- a/modules/mod/src/main/ModlogApi.scala +++ b/modules/mod/src/main/ModlogApi.scala @@ -1,11 +1,11 @@ package lila.mod -import lila.db.api._ -import lila.db.Implicits._ -import tube.modlogTube -import play.api.libs.json.Json +import lila.db.dsl._ -final class ModlogApi { +final class ModlogApi(coll: Coll) { + + import lila.db.BSON.BSONJodaDateTimeHandler + private implicit val ModlogBSONHandler = reactivemongo.bson.Macros.handler[Modlog] def streamConfig(mod: String) = add { Modlog(mod, none, Modlog.streamConfig) @@ -86,24 +86,24 @@ final class ModlogApi { Modlog(mod, none, Modlog.terminateTournament, details = name.some) } - def recent = $find($query($select.all) sort $sort.naturalDesc, 100) + def recent = coll.find($empty).sort($sort naturalDesc).cursor[Modlog]().gather[List](100) - def wasUnengined(userId: String) = $count.exists(Json.obj( + def wasUnengined(userId: String) = coll.exists($doc( "user" -> userId, "action" -> Modlog.unengine )) - def wasUnbooster(userId: String) = $count.exists(Json.obj( + def wasUnbooster(userId: String) = coll.exists($doc( "user" -> userId, "action" -> Modlog.unbooster )) def userHistory(userId: String): Fu[List[Modlog]] = - $find($query(Json.obj("user" -> userId)) sort $sort.desc("date"), 100) + coll.find($doc("user" -> userId)).sort($sort desc "date").cursor[Modlog]().gather[List](100) private def add(m: Modlog): Funit = { lila.mon.mod.log.create() lila.log("mod").info(m.toString) - $insert(m) + coll.insert(m).void } } diff --git a/modules/mod/src/main/package.scala b/modules/mod/src/main/package.scala index 6380d96d18..3f7717389d 100644 --- a/modules/mod/src/main/package.scala +++ b/modules/mod/src/main/package.scala @@ -1,12 +1,3 @@ package lila -import lila.db.JsTube - -package object mod extends PackageObject with WithPlay { - - object tube { - - private[mod] implicit lazy val modlogTube = - Modlog.tube inColl Env.current.logColl - } -} +package object mod extends PackageObject with WithPlay diff --git a/modules/opening/src/main/Finisher.scala b/modules/opening/src/main/Finisher.scala index ff73245ec4..b5dcea003e 100644 --- a/modules/opening/src/main/Finisher.scala +++ b/modules/opening/src/main/Finisher.scala @@ -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) } } diff --git a/modules/opening/src/main/OpeningApi.scala b/modules/opening/src/main/OpeningApi.scala index 918f3bcdfd..0b8ad8acd0 100644 --- a/modules/opening/src/main/OpeningApi.scala +++ b/modules/opening/src/main/OpeningApi.scala @@ -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] + )).uno[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) + ).uno[Bdoc] map { obj => ~obj.??(_.getAs[List[String]]("names")) } } diff --git a/modules/opening/src/main/Selector.scala b/modules/opening/src/main/Selector.scala index 2f829d4ba3..f710cce5a1 100644 --- a/modules/opening/src/main/Selector.scala +++ b/modules/opening/src/main/Selector.scala @@ -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,9 +18,9 @@ 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)) - .one[Opening] flatten "Can't find a opening for anon player!" + openingColl.find($empty) + .skip(Random nextInt anonSkipMax) + .uno[Opening] flatten "Can't find a opening for anon player!" case Some(user) => api.attempt.playedIds(user, modulo) flatMap { ids => tryRange(user, toleranceStep, ids) } recoverWith { @@ -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.uno[Opening]($doc( + Opening.BSONFields.id -> $doc("$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!") + } } diff --git a/modules/opening/src/main/UserInfos.scala b/modules/opening/src/main/UserInfos.scala index 3af25628d7..5cb1060f02 100644 --- a/modules/opening/src/main/UserInfos.scala +++ b/modules/opening/src/main/UserInfos.scala @@ -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 @@ -35,7 +34,7 @@ object UserInfos { Attempt.BSONFields.userId -> userId )).sort(BSONDocument( Attempt.BSONFields.date -> -1 - )).cursor[Attempt]().collect[List](math.max(historySize, chartSize)) + )).cursor[Attempt]().gather[List](math.max(historySize, chartSize)) } private def makeHistory(attempts: List[Attempt]) = attempts.take(historySize) diff --git a/modules/perfStat/src/main/PerfStatIndexer.scala b/modules/perfStat/src/main/PerfStatIndexer.scala index 00b3806541..d523e84252 100644 --- a/modules/perfStat/src/main/PerfStatIndexer.scala +++ b/modules/perfStat/src/main/PerfStatIndexer.scala @@ -3,9 +3,8 @@ package lila.perfStat import akka.actor.ActorRef import play.api.libs.iteratee._ -import lila.db.api._ -import lila.db.Implicits._ -import lila.game.{ Game, Pov, Query } +import lila.db.dsl._ +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) => diff --git a/modules/perfStat/src/main/PerfStatStorage.scala b/modules/perfStat/src/main/PerfStatStorage.scala index 27b4d47fac..2cbe3b358c 100644 --- a/modules/perfStat/src/main/PerfStatStorage.scala +++ b/modules/perfStat/src/main/PerfStatStorage.scala @@ -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 diff --git a/modules/playban/src/main/PlaybanApi.scala b/modules/playban/src/main/PlaybanApi.scala index 934db021c4..7d4e12fb3d 100644 --- a/modules/playban/src/main/PlaybanApi.scala +++ b/modules/playban/src/main/PlaybanApi.scala @@ -8,7 +8,7 @@ import scala.concurrent.duration._ import chess.Color import lila.db.BSON._ -import lila.db.Types.Coll +import lila.db.dsl._ import lila.game.{ Pov, Game, Player, Source } final class PlaybanApi( @@ -48,23 +48,23 @@ final class PlaybanApi( } def currentBan(userId: String): Fu[Option[TempBan]] = coll.find( - BSONDocument("_id" -> userId, "b.0" -> BSONDocument("$exists" -> true)), - BSONDocument("_id" -> false, "b" -> BSONDocument("$slice" -> -1)) - ).one[BSONDocument].map { + $doc("_id" -> userId, "b.0" -> $doc("$exists" -> true)), + $doc("_id" -> false, "b" -> $doc("$slice" -> -1)) + ).uno[Bdoc].map { _.flatMap(_.getAs[List[TempBan]]("b")).??(_.find(_.inEffect)) } def bans(userId: String): Fu[List[TempBan]] = coll.find( - BSONDocument("_id" -> userId, "b.0" -> BSONDocument("$exists" -> true)), - BSONDocument("_id" -> false, "b" -> true) - ).one[BSONDocument].map { + $doc("_id" -> userId, "b.0" -> $doc("$exists" -> true)), + $doc("_id" -> false, "b" -> true) + ).uno[Bdoc].map { ~_.flatMap(_.getAs[List[TempBan]]("b")) } def bans(userIds: List[String]): Fu[Map[String, Int]] = coll.find( - BSONDocument("_id" -> BSONDocument("$in" -> userIds)), - BSONDocument("b" -> true) - ).cursor[BSONDocument]().collect[List]().map { + $inIds(userIds), + $doc("b" -> true) + ).cursor[Bdoc]().gather[List]().map { _.flatMap { obj => obj.getAs[String]("_id") flatMap { id => obj.getAs[BSONArray]("b") map { id -> _.stream.size } @@ -74,9 +74,9 @@ final class PlaybanApi( private def save(outcome: Outcome): String => Funit = userId => { coll.findAndUpdate( - selector = BSONDocument("_id" -> userId), - update = BSONDocument("$push" -> BSONDocument( - "o" -> BSONDocument( + selector = $doc("_id" -> userId), + update = $doc("$push" -> $doc( + "o" -> $doc( "$each" -> List(outcome), "$slice" -> -20) )), @@ -89,11 +89,11 @@ final class PlaybanApi( private def legiferate(record: UserRecord): Funit = record.newBan ?? { ban => coll.update( - BSONDocument("_id" -> record.userId), - BSONDocument( - "$unset" -> BSONDocument("o" -> true), - "$push" -> BSONDocument( - "b" -> BSONDocument( + $doc("_id" -> record.userId), + $doc( + "$unset" -> $doc("o" -> true), + "$push" -> $doc( + "b" -> $doc( "$each" -> List(ban), "$slice" -> -30) ) diff --git a/modules/pref/src/main/Pref.scala b/modules/pref/src/main/Pref.scala index 5ad36c7164..d9b4e68186 100644 --- a/modules/pref/src/main/Pref.scala +++ b/modules/pref/src/main/Pref.scala @@ -1,7 +1,5 @@ package lila.pref -import lila.db.JsTube -import lila.db.JsTube.Helpers._ import lila.user.User case class Pref( diff --git a/modules/pref/src/main/PrefApi.scala b/modules/pref/src/main/PrefApi.scala index 20791731e5..2edba2529b 100644 --- a/modules/pref/src/main/PrefApi.scala +++ b/modules/pref/src/main/PrefApi.scala @@ -4,7 +4,7 @@ import play.api.libs.json.Json import scala.concurrent.duration.Duration import lila.db.BSON -import lila.db.Types._ +import lila.db.dsl._ import lila.hub.actorApi.SendTo import lila.memo.AsyncCache import lila.user.User @@ -15,7 +15,7 @@ final class PrefApi( cacheTtl: Duration, bus: lila.common.Bus) { - private def fetchPref(id: String): Fu[Option[Pref]] = coll.find(BSONDocument("_id" -> id)).one[Pref] + private def fetchPref(id: String): Fu[Option[Pref]] = coll.find(BSONDocument("_id" -> id)).uno[Pref] private val cache = AsyncCache(fetchPref, timeToLive = cacheTtl) private implicit val prefBSONHandler = new BSON[Pref] { @@ -110,7 +110,7 @@ final class PrefApi( def getPref[A](userId: String, pref: Pref => A): Fu[A] = getPref(userId) map pref def followable(userId: String): Fu[Boolean] = - coll.find(BSONDocument("_id" -> userId), BSONDocument("follow" -> true)).one[BSONDocument] map { + coll.find(BSONDocument("_id" -> userId), BSONDocument("follow" -> true)).uno[BSONDocument] map { _ flatMap (_.getAs[Boolean]("follow")) getOrElse Pref.default.follow } diff --git a/modules/pref/src/main/package.scala b/modules/pref/src/main/package.scala index ea62cd2383..0830ba1a17 100644 --- a/modules/pref/src/main/package.scala +++ b/modules/pref/src/main/package.scala @@ -1,5 +1,3 @@ package lila -import lila.db.JsTube - package object pref extends PackageObject with WithPlay diff --git a/modules/push/src/main/DeviceApi.scala b/modules/push/src/main/DeviceApi.scala index 03c586b333..c2bc851717 100644 --- a/modules/push/src/main/DeviceApi.scala +++ b/modules/push/src/main/DeviceApi.scala @@ -3,8 +3,7 @@ package lila.push import org.joda.time.DateTime import reactivemongo.bson._ -import lila.db.BSON._ -import lila.db.Types.Coll +import lila.db.dsl._ import lila.user.User private final class DeviceApi(coll: Coll) { @@ -12,20 +11,20 @@ private final class DeviceApi(coll: Coll) { private implicit val DeviceBSONHandler = Macros.handler[Device] private[push] def findByDeviceId(deviceId: String): Fu[Option[Device]] = - coll.find(BSONDocument("_id" -> deviceId)).one[Device] + coll.find($id(deviceId)).uno[Device] private[push] def findByUserId(userId: String): Fu[List[Device]] = - coll.find(BSONDocument("userId" -> userId)).cursor[Device]().collect[List]() + coll.find($doc("userId" -> userId)).cursor[Device]().gather[List]() private[push] def findLastByUserId(platform: String)(userId: String): Fu[Option[Device]] = - coll.find(BSONDocument( + coll.find($doc( "platform" -> platform, "userId" -> userId - )).sort(BSONDocument("seenAt" -> -1)).one[Device] + )).sort($doc("seenAt" -> -1)).uno[Device] def register(user: User, platform: String, deviceId: String) = { lila.mon.push.register.in(platform)() - coll.update(BSONDocument("_id" -> deviceId), Device( + coll.update($id(deviceId), Device( _id = deviceId, platform = platform, userId = user.id, @@ -35,6 +34,6 @@ private final class DeviceApi(coll: Coll) { def unregister(user: User) = { lila.mon.push.register.out() - coll.remove(BSONDocument("userId" -> user.id)).void + coll.remove($doc("userId" -> user.id)).void } } diff --git a/modules/puzzle/src/main/Daily.scala b/modules/puzzle/src/main/Daily.scala index 677a285b50..ce0ed51286 100644 --- a/modules/puzzle/src/main/Daily.scala +++ b/modules/puzzle/src/main/Daily.scala @@ -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))) - ).one[Puzzle] + $doc("day" -> $doc("$gt" -> DateTime.now.minusMinutes(24 * 60 - 15))) + ).uno[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)).uno[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) } diff --git a/modules/puzzle/src/main/Finisher.scala b/modules/puzzle/src/main/Finisher.scala index d2587c4328..43c51c560a 100644 --- a/modules/puzzle/src/main/Finisher.scala +++ b/modules/puzzle/src/main/Finisher.scala @@ -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) } diff --git a/modules/puzzle/src/main/PuzzleApi.scala b/modules/puzzle/src/main/PuzzleApi.scala index 525925f213..9020e0aa5c 100644 --- a/modules/puzzle/src/main/PuzzleApi.scala +++ b/modules/puzzle/src/main/PuzzleApi.scala @@ -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,13 +20,13 @@ 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)).uno[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) + .gather[List](nb) def importBatch(json: JsValue, token: String): Fu[List[Try[PuzzleId]]] = if (token != apiToken) fufail("Invalid API token") @@ -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,24 +55,24 @@ 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)) - .cursor[Puzzle]().collect[List](nb / 2) + puzzleColl.find($doc("mate" -> mate)) + .sort($doc(Puzzle.BSONFields.voteSum -> -1)) + .cursor[Puzzle]().gather[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] + )).uno[Attempt] def vote(a1: Attempt, v: Boolean): Fu[(Puzzle, Attempt)] = puzzle find a1.puzzleId flatMap { case None => fufail(s"Can't vote for non existing puzzle ${a1.puzzleId}") @@ -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,23 +95,23 @@ 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]() - .collect[List](5) map { + )).sort($doc(Attempt.BSONFields.date -> -1)) + .cursor[Bdoc]() + .gather[List](5) map { case attempts if attempts.size < 5 => true case attempts => attempts.foldLeft(false) { case (true, _) => true diff --git a/modules/puzzle/src/main/Selector.scala b/modules/puzzle/src/main/Selector.scala index 5d2151e96e..52a88f2795 100644 --- a/modules/puzzle/src/main/Selector.scala +++ b/modules/puzzle/src/main/Selector.scala @@ -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,8 +33,8 @@ private[puzzle] final class Selector( me match { case None => puzzleColl.find(popularSelector(isMate) ++ mateSelector(isMate)) - .options(QueryOpts(skipN = Random nextInt anonSkipMax)) - .one[Puzzle] + .skip(Random nextInt anonSkipMax) + .uno[Puzzle] case Some(user) if user.perfs.puzzle.nb > maxAttempts => fuccess(none) case Some(user) => val rating = user.perfs.puzzle.intRating min 2300 max 900 @@ -55,15 +52,14 @@ 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)) - .one[Puzzle] flatMap { + 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 -> $doc("$nin" -> ids), + Puzzle.BSONFields.rating $gt + (rating - tolerance + decay) $lt + (rating + tolerance + decay) + )).sort($sort desc Puzzle.BSONFields.voteSum) + .uno[Puzzle] flatMap { case None if (tolerance + step) <= toleranceMax => tryRange(rating, tolerance + step, step, decay, ids, isMate) case res => fuccess(res) diff --git a/modules/puzzle/src/main/UserInfos.scala b/modules/puzzle/src/main/UserInfos.scala index 710dbc020c..6bc4339703 100644 --- a/modules/puzzle/src/main/UserInfos.scala +++ b/modules/puzzle/src/main/UserInfos.scala @@ -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 @@ -39,7 +39,7 @@ object UserInfos { )).sort(BSONDocument( Attempt.BSONFields.date -> -1 )).cursor[Attempt]() - .collect[List](math.max(historySize, chartSize)) + .gather[List](math.max(historySize, chartSize)) } private def makeHistory(attempts: List[Attempt]) = attempts.take(historySize) diff --git a/modules/qa/src/main/QaApi.scala b/modules/qa/src/main/QaApi.scala index 1ceff699b9..78482ace68 100644 --- a/modules/qa/src/main/QaApi.scala +++ b/modules/qa/src/main/QaApi.scala @@ -8,9 +8,8 @@ import org.joda.time.DateTime import spray.caching.{ LruCache, Cache } import lila.common.paginator._ -import lila.db.BSON._ import lila.db.paginator._ -import lila.db.Types.Coll +import lila.db.dsl._ import lila.user.{ User, UserRepo } final class QaApi( @@ -51,34 +50,34 @@ final class QaApi( def edit(data: QuestionData, id: QuestionId): Fu[Option[Question]] = findById(id) flatMap { _ ?? { q => val q2 = q.copy(title = data.title, body = data.body, tags = data.tags).editNow - questionColl.update(BSONDocument("_id" -> q2.id), q2) >> + questionColl.update($doc("_id" -> q2.id), q2) >> tag.clearCache >> relation.clearCache inject q2.some } } def findById(id: QuestionId): Fu[Option[Question]] = - questionColl.find(BSONDocument("_id" -> id)).one[Question] + questionColl.find($doc("_id" -> id)).uno[Question] def findByIds(ids: List[QuestionId]): Fu[List[Question]] = - questionColl.find(BSONDocument("_id" -> BSONDocument("$in" -> ids.distinct))).cursor[Question]().collect[List]() + questionColl.find($doc("_id" -> $doc("$in" -> ids.distinct))).cursor[Question]().gather[List]() def accept(q: Question) = questionColl.update( - BSONDocument("_id" -> q.id), - BSONDocument("$set" -> BSONDocument("acceptedAt" -> DateTime.now)) + $doc("_id" -> q.id), + $doc("$set" -> $doc("acceptedAt" -> DateTime.now)) ) def count: Fu[Int] = questionColl.count(None) def recentPaginator(page: Int, perPage: Int): Fu[Paginator[Question]] = - paginator(BSONDocument(), BSONDocument("createdAt" -> -1), page, perPage) + paginator($empty, $doc("createdAt" -> -1), page, perPage) - private def paginator(selector: BSONDocument, sort: BSONDocument, page: Int, perPage: Int): Fu[Paginator[Question]] = + private def paginator(selector: Bdoc, sort: Bdoc, page: Int, perPage: Int): Fu[Paginator[Question]] = Paginator( - adapter = new BSONAdapter[Question]( + adapter = new Adapter[Question]( collection = questionColl, selector = selector, - projection = BSONDocument(), + projection = $empty, sort = sort ), currentPage = page, @@ -86,62 +85,62 @@ final class QaApi( private def popularCache = mongoCache( prefix = "qa:popular", - f = (nb: Int) => questionColl.find(BSONDocument()) - .sort(BSONDocument("vote.score" -> -1)) - .cursor[Question]().collect[List](nb), + f = (nb: Int) => questionColl.find($empty) + .sort($doc("vote.score" -> -1)) + .cursor[Question]().gather[List](nb), timeToLive = 3 hour) def popular(max: Int): Fu[List[Question]] = popularCache(max) def byTag(tag: String, max: Int): Fu[List[Question]] = - questionColl.find(BSONDocument("tags" -> tag.toLowerCase)) - .sort(BSONDocument("vote.score" -> -1)) - .cursor[Question]().collect[List](max) + questionColl.find($doc("tags" -> tag.toLowerCase)) + .sort($doc("vote.score" -> -1)) + .cursor[Question]().gather[List](max) def byTags(tags: List[String], max: Int): Fu[List[Question]] = - questionColl.find(BSONDocument("tags" -> BSONDocument("$in" -> tags.map(_.toLowerCase)))).cursor[Question]().collect[List](max) + questionColl.find($doc("tags" -> $doc("$in" -> tags.map(_.toLowerCase)))).cursor[Question]().gather[List](max) def addComment(c: Comment)(q: Question) = questionColl.update( - BSONDocument("_id" -> q.id), - BSONDocument("$push" -> BSONDocument("comments" -> c))) + $doc("_id" -> q.id), + $doc("$push" -> $doc("comments" -> c))) def vote(id: QuestionId, user: User, v: Boolean): Fu[Option[Vote]] = question findById id flatMap { _ ?? { q => val newVote = q.vote.add(user.id, v) questionColl.update( - BSONDocument("_id" -> q.id), - BSONDocument("$set" -> BSONDocument("vote" -> newVote)) + $doc("_id" -> q.id), + $doc("$set" -> $doc("vote" -> newVote)) ) inject newVote.some } } def incViews(q: Question) = questionColl.update( - BSONDocument("_id" -> q.id), - BSONDocument("$inc" -> BSONDocument("views" -> BSONInteger(1)))) + $doc("_id" -> q.id), + $doc("$inc" -> $doc("views" -> BSONInteger(1)))) def recountAnswers(id: QuestionId) = answer.countByQuestionId(id) flatMap { setAnswers(id, _) } def setAnswers(id: QuestionId, nb: Int) = questionColl.update( - BSONDocument("_id" -> id), - BSONDocument( - "$set" -> BSONDocument( + $doc("_id" -> id), + $doc( + "$set" -> $doc( "answers" -> BSONInteger(nb), "updatedAt" -> DateTime.now ) )).void def remove(id: QuestionId) = - questionColl.remove(BSONDocument("_id" -> id)) >> + questionColl.remove($doc("_id" -> id)) >> (answer removeByQuestion id) >> tag.clearCache >> relation.clearCache def removeComment(id: QuestionId, c: CommentId) = questionColl.update( - BSONDocument("_id" -> id), - BSONDocument("$pull" -> BSONDocument("comments" -> BSONDocument("id" -> c))) + $doc("_id" -> id), + $doc("$pull" -> $doc("comments" -> $doc("id" -> c))) ) } @@ -172,26 +171,26 @@ final class QaApi( def edit(body: String, id: AnswerId): Fu[Option[Answer]] = findById(id) flatMap { _ ?? { a => val a2 = a.copy(body = body).editNow - answerColl.update(BSONDocument("_id" -> a2.id), a2) inject a2.some + answerColl.update($doc("_id" -> a2.id), a2) inject a2.some } } def findById(id: AnswerId): Fu[Option[Answer]] = - answerColl.find(BSONDocument("_id" -> id)).one[Answer] + answerColl.find($doc("_id" -> id)).uno[Answer] def accept(q: Question, a: Answer) = (question accept q) >> answerColl.update( - BSONDocument("questionId" -> q.id), - BSONDocument("$unset" -> BSONDocument("acceptedAt" -> true)), + $doc("questionId" -> q.id), + $doc("$unset" -> $doc("acceptedAt" -> true)), multi = true ) >> answerColl.update( - BSONDocument("_id" -> a.id), - BSONDocument("$set" -> BSONDocument("acceptedAt" -> DateTime.now)) + $doc("_id" -> a.id), + $doc("$set" -> $doc("acceptedAt" -> DateTime.now)) ) def popular(questionId: QuestionId): Fu[List[Answer]] = - answerColl.find(BSONDocument("questionId" -> questionId)) - .sort(BSONDocument("vote.score" -> -1)) - .cursor[Answer]().collect[List]() + answerColl.find($doc("questionId" -> questionId)) + .sort($doc("vote.score" -> -1)) + .cursor[Answer]().gather[List]() def zipWithQuestions(answers: List[Answer]): Fu[List[AnswerWithQuestion]] = question.findByIds(answers.map(_.questionId)) map { qs => @@ -201,32 +200,32 @@ final class QaApi( } def addComment(c: Comment)(a: Answer) = answerColl.update( - BSONDocument("_id" -> a.id), - BSONDocument("$push" -> BSONDocument("comments" -> c))) + $doc("_id" -> a.id), + $doc("$push" -> $doc("comments" -> c))) def vote(id: QuestionId, user: User, v: Boolean): Fu[Option[Vote]] = answer findById id flatMap { _ ?? { a => val newVote = a.vote.add(user.id, v) answerColl.update( - BSONDocument("_id" -> a.id), - BSONDocument("$set" -> BSONDocument("vote" -> newVote)) + $doc("_id" -> a.id), + $doc("$set" -> $doc("vote" -> newVote)) ) inject newVote.some } } def remove(a: Answer): Fu[Unit] = - answerColl.remove(BSONDocument("_id" -> a.id)) >> + answerColl.remove($doc("_id" -> a.id)) >> (question recountAnswers a.questionId).void def remove(id: AnswerId): Fu[Unit] = findById(id) flatMap { _ ?? remove } def removeByQuestion(id: QuestionId) = - answerColl.remove(BSONDocument("questionId" -> id)) + answerColl.remove($doc("questionId" -> id)) def removeComment(id: QuestionId, c: CommentId) = answerColl.update( - BSONDocument("questionId" -> id), - BSONDocument("$pull" -> BSONDocument("comments" -> BSONDocument("id" -> c))), + $doc("questionId" -> id), + $doc("$pull" -> $doc("comments" -> $doc("id" -> c))), multi = true) def moveToQuestionComment(a: Answer, q: Question) = { @@ -251,7 +250,7 @@ final class QaApi( } def countByQuestionId(id: QuestionId) = - answerColl.count(Some(BSONDocument("questionId" -> id))) + answerColl.count(Some($doc("questionId" -> id))) } object comment { @@ -291,7 +290,7 @@ final class QaApi( val col = questionColl import reactivemongo.api.collections.bson.BSONBatchCommands.AggregationFramework.{ AddToSet, Group, Project, Unwind } - col.aggregate(Project(BSONDocument("tags" -> BSONBoolean(true))), List( + col.aggregate(Project($doc("tags" -> BSONBoolean(true))), List( Unwind("tags"), Group(BSONBoolean(true))("tags" -> AddToSet("tags")))). map(_.documents.headOption.flatMap(_.getAs[List[String]]("tags")). getOrElse(List.empty[String]).map(_.toLowerCase).distinct) diff --git a/modules/qa/src/main/Search.scala b/modules/qa/src/main/Search.scala index ce8de51363..35ac32c8ec 100644 --- a/modules/qa/src/main/Search.scala +++ b/modules/qa/src/main/Search.scala @@ -3,8 +3,7 @@ package lila.qa import reactivemongo.bson._ import reactivemongo.core.commands._ -import lila.db.BSON.BSONJodaDateTimeHandler -import lila.db.Types.Coll +import lila.db.dsl._ final class Search(collection: Coll) { @@ -38,6 +37,6 @@ final class Search(collection: Coll) { def apply(q: String): Fu[List[Question]] = collection.find(BSONDocument( "$text" -> BSONDocument("$search" -> q) - )).cursor[Question]().collect[List]() + )).cursor[Question]().gather[List]() } diff --git a/modules/rating/src/main/Glicko.scala b/modules/rating/src/main/Glicko.scala index 4e42cac5cc..63f6d807f2 100644 --- a/modules/rating/src/main/Glicko.scala +++ b/modules/rating/src/main/Glicko.scala @@ -67,6 +67,4 @@ case object Glicko { case object Loss extends Result(0) { def negate = Win } case object Draw extends Result(0.5) { def negate = Draw } } - - lazy val tube = lila.db.BsTube(glickoBSONHandler) } diff --git a/modules/relation/src/main/Env.scala b/modules/relation/src/main/Env.scala index 11baae8c20..5a14e0eef1 100644 --- a/modules/relation/src/main/Env.scala +++ b/modules/relation/src/main/Env.scala @@ -25,8 +25,10 @@ final class Env( } import settings._ + private[relation] val coll = db(CollectionRelation) + lazy val api = new RelationApi( - coll = relationColl, + coll = coll, actor = hub.actor.relation, bus = system.lilaBus, timeline = hub.actor.timeline, @@ -50,8 +52,6 @@ final class Env( } } } - - private[relation] lazy val relationColl = db(CollectionRelation) } object Env { diff --git a/modules/relation/src/main/RelationApi.scala b/modules/relation/src/main/RelationApi.scala index a21da70e8d..3cbafc9d7f 100644 --- a/modules/relation/src/main/RelationApi.scala +++ b/modules/relation/src/main/RelationApi.scala @@ -4,14 +4,11 @@ import akka.actor.ActorSelection import scala.concurrent.duration._ import scala.util.Success -import lila.db.api._ -import lila.db.Implicits._ +import lila.db.dsl._ import lila.db.paginator._ import lila.hub.actorApi.timeline.{ Propagate, Follow => FollowUser } import lila.memo.AsyncCache -import lila.user.tube.userTube import lila.user.{ User => UserModel, UserRepo } -import tube.relationTube import BSONHandlers._ import reactivemongo.api.collections.bson.BSONBatchCommands.AggregationFramework._ @@ -30,9 +27,9 @@ final class RelationApi( import RelationRepo.makeId def fetchRelation(u1: ID, u2: ID): Fu[Option[Relation]] = coll.find( - BSONDocument("u1" -> u1, "u2" -> u2), - BSONDocument("r" -> true, "_id" -> false) - ).one[BSONDocument].map { + $doc("u1" -> u1, "u2" -> u2), + $doc("r" -> true, "_id" -> false) + ).uno[BSONDocument].map { _.flatMap(_.getAs[Boolean]("r")) } @@ -42,64 +39,64 @@ final class RelationApi( def fetchBlocking = RelationRepo blocking _ - def fetchFriends(userId: ID) = coll.aggregate(Match(BSONDocument( - "$or" -> BSONArray(BSONDocument("u1" -> userId), BSONDocument("u2" -> userId)), + def fetchFriends(userId: ID) = coll.aggregate(Match($doc( + "$or" -> BSONArray($doc("u1" -> userId), $doc("u2" -> userId)), "r" -> Follow )), List( Group(BSONNull)( "u1" -> AddToSet("u1"), "u2" -> AddToSet("u2")), - Project(BSONDocument( - "_id" -> BSONDocument("$setIntersection" -> BSONArray("$u1", "$u2")) + Project($doc( + "_id" -> $doc("$setIntersection" -> BSONArray("$u1", "$u2")) )) )).map { ~_.documents.headOption.flatMap(_.getAs[Set[String]]("_id")) - userId } def fetchFollows(u1: ID, u2: ID) = - coll.count(BSONDocument("_id" -> makeId(u1, u2), "r" -> Follow).some).map(0!=) + coll.count($doc("_id" -> makeId(u1, u2), "r" -> Follow).some).map(0!=) def fetchBlocks(u1: ID, u2: ID) = - coll.count(BSONDocument("_id" -> makeId(u1, u2), "r" -> Block).some).map(0!=) + coll.count($doc("_id" -> makeId(u1, u2), "r" -> Block).some).map(0!=) def fetchAreFriends(u1: ID, u2: ID) = fetchFollows(u1, u2) flatMap { _ ?? fetchFollows(u2, u1) } private val countFollowingCache = AsyncCache[ID, Int]( - f = userId => coll.count(BSONDocument("u1" -> userId, "r" -> Follow).some), + f = userId => coll.count($doc("u1" -> userId, "r" -> Follow).some), timeToLive = 10 minutes) def countFollowing(userId: ID) = countFollowingCache(userId) private val countFollowersCache = AsyncCache[ID, Int]( - f = userId => coll.count(BSONDocument("u2" -> userId, "r" -> Follow).some), + f = userId => coll.count($doc("u2" -> userId, "r" -> Follow).some), timeToLive = 10 minutes) def countFollowers(userId: ID) = countFollowersCache(userId) def countBlocking(userId: ID) = - coll.count(BSONDocument("u1" -> userId, "r" -> Block).some) + coll.count($doc("u1" -> userId, "r" -> Block).some) def countBlockers(userId: ID) = - coll.count(BSONDocument("u2" -> userId, "r" -> Block).some) + coll.count($doc("u2" -> userId, "r" -> Block).some) - def followingPaginatorAdapter(userId: ID) = new BSONAdapter[Followed]( + def followingPaginatorAdapter(userId: ID) = new Adapter[Followed]( collection = coll, - selector = BSONDocument("u1" -> userId, "r" -> Follow), - projection = BSONDocument("u2" -> true, "_id" -> false), - sort = BSONDocument()).map(_.userId) + selector = $doc("u1" -> userId, "r" -> Follow), + projection = $doc("u2" -> true, "_id" -> false), + sort = $empty).map(_.userId) - def followersPaginatorAdapter(userId: ID) = new BSONAdapter[Follower]( + def followersPaginatorAdapter(userId: ID) = new Adapter[Follower]( collection = coll, - selector = BSONDocument("u2" -> userId, "r" -> Follow), - projection = BSONDocument("u1" -> true, "_id" -> false), - sort = BSONDocument()).map(_.userId) + selector = $doc("u2" -> userId, "r" -> Follow), + projection = $doc("u1" -> true, "_id" -> false), + sort = $empty).map(_.userId) - def blockingPaginatorAdapter(userId: ID) = new BSONAdapter[Blocked]( + def blockingPaginatorAdapter(userId: ID) = new Adapter[Blocked]( collection = coll, - selector = BSONDocument("u1" -> userId, "r" -> Block), - projection = BSONDocument("u2" -> true, "_id" -> false), - sort = BSONDocument()).map(_.userId) + selector = $doc("u1" -> userId, "r" -> Block), + projection = $doc("u2" -> true, "_id" -> false), + sort = $empty).map(_.userId) def follow(u1: ID, u2: ID): Funit = if (u1 == u2) funit diff --git a/modules/relation/src/main/RelationRepo.scala b/modules/relation/src/main/RelationRepo.scala index 2e85ff5312..a4c1d8e484 100644 --- a/modules/relation/src/main/RelationRepo.scala +++ b/modules/relation/src/main/RelationRepo.scala @@ -1,16 +1,13 @@ package lila.relation -import play.api.libs.json._ import reactivemongo.bson._ -import lila.common.PimpedJson._ -import lila.db.api._ -import lila.db.Implicits._ -import tube.relationTube +import lila.db.dsl._ private[relation] object RelationRepo { - val coll = relationTube.coll + // dirty + private val coll = Env.current.coll def followers(userId: ID) = relaters(userId, Follow) def following(userId: ID) = relating(userId, Follow) @@ -35,24 +32,26 @@ private[relation] object RelationRepo { def block(u1: ID, u2: ID): Funit = save(u1, u2, Block) def unblock(u1: ID, u2: ID): Funit = remove(u1, u2) - def unfollowAll(u1: ID): Funit = $remove(Json.obj("u1" -> u1)) + def unfollowAll(u1: ID): Funit = coll.remove($doc("u1" -> u1)).void - private def save(u1: ID, u2: ID, relation: Relation): Funit = $save( - makeId(u1, u2), - Json.obj("u1" -> u1, "u2" -> u2, "r" -> relation) - ) + private def save(u1: ID, u2: ID, relation: Relation): Funit = coll.update( + $id(makeId(u1, u2)), + $doc("u1" -> u1, "u2" -> u2, "r" -> relation), + upsert = true).void - def remove(u1: ID, u2: ID): Funit = $remove byId makeId(u1, u2) + def remove(u1: ID, u2: ID): Funit = coll.remove($id(makeId(u1, u2))).void def drop(userId: ID, relation: Relation, nb: Int) = - $primitive( - Json.obj("u1" -> userId, "r" -> relation), - "_id", - _ sort $sort.naturalAsc, - max = nb.some, - hint = reactivemongo.bson.BSONDocument("u1" -> 1) - )(_.asOpt[String]) flatMap { ids => - $remove(Json.obj("_id" -> $in(ids))) + coll.find( + $doc("u1" -> userId, "r" -> relation), + $doc("_id" -> true) + ).sort($sort.naturalAsc) + .hint($doc("u1" -> 1)) + .cursor[Bdoc]() + .gather[List](nb).map { + _.flatMap { _.getAs[String]("_id") } + } flatMap { ids => + coll.remove($inIds(ids)).void } def makeId(u1: String, u2: String) = s"$u1/$u2" diff --git a/modules/relation/src/main/package.scala b/modules/relation/src/main/package.scala index b71cb067d0..51e3c680dc 100644 --- a/modules/relation/src/main/package.scala +++ b/modules/relation/src/main/package.scala @@ -1,7 +1,5 @@ package lila -import lila.db.JsTube - package object relation extends PackageObject with WithPlay { type Relation = Boolean @@ -9,10 +7,4 @@ package object relation extends PackageObject with WithPlay { private[relation] val Block: Relation = false private[relation] type ID = String - - object tube { - - private[relation] implicit lazy val relationTube = - JsTube.json inColl Env.current.relationColl - } } diff --git a/modules/report/src/main/Env.scala b/modules/report/src/main/Env.scala index 729cb212fb..2ba1bfd034 100644 --- a/modules/report/src/main/Env.scala +++ b/modules/report/src/main/Env.scala @@ -16,7 +16,7 @@ final class Env( lazy val forms = new DataForm(hub.actor.captcher) - lazy val api = new ReportApi + lazy val api = new ReportApi(reportColl) // api actor system.actorOf(Props(new Actor { diff --git a/modules/report/src/main/Report.scala b/modules/report/src/main/Report.scala index 24293aa80d..13dae8f92d 100644 --- a/modules/report/src/main/Report.scala +++ b/modules/report/src/main/Report.scala @@ -6,7 +6,7 @@ import ornicar.scalalib.Random import lila.user.User case class Report( - id: String, // also the url slug + _id: String, // also the url slug user: String, // the reportee reason: String, text: String, @@ -14,7 +14,8 @@ case class Report( createdAt: DateTime, createdBy: String) { - def slug = id + def id = _id + def slug = _id def isCreator(user: String) = user == createdBy @@ -47,19 +48,11 @@ object Report { reason: Reason, text: String, createdBy: User): Report = new Report( - id = Random nextStringUppercase 8, + _id = Random nextStringUppercase 8, user = user.id, reason = reason.name, text = text, processedBy = none, createdAt = DateTime.now, createdBy = createdBy.id) - - import lila.db.JsTube, JsTube.Helpers._ - import play.api.libs.json._ - - private[report] lazy val tube = JsTube( - (__.json update readDate('createdAt)) andThen Json.reads[Report], - Json.writes[Report] andThen (__.json update writeDate('createdAt)) - ) } diff --git a/modules/report/src/main/ReportApi.scala b/modules/report/src/main/ReportApi.scala index 9d0b05c51d..8c77a2e32b 100644 --- a/modules/report/src/main/ReportApi.scala +++ b/modules/report/src/main/ReportApi.scala @@ -2,15 +2,14 @@ package lila.report import akka.actor.ActorSelection import org.joda.time.DateTime -import play.api.libs.json._ -import play.modules.reactivemongo.json.ImplicitBSONHandlers._ -import lila.db.api._ -import lila.db.Implicits._ +import lila.db.dsl._ import lila.user.{ User, UserRepo } -import tube.reportTube -private[report] final class ReportApi { +private[report] final class ReportApi(coll: Coll) { + + import lila.db.BSON.BSONJodaDateTimeHandler + private implicit val ReportBSONHandler = reactivemongo.bson.Macros.handler[Report] def create(setup: ReportSetup, by: User): Funit = !by.troll ?? { Reason(setup.reason).fold[Funit](fufail(s"Invalid report reason ${setup.reason}")) { reason => @@ -22,13 +21,13 @@ private[report] final class ReportApi { createdBy = by) !isAlreadySlain(report, user) ?? { lila.mon.mod.report.create(reason.name)() - if (by.id == UserRepo.lichessId) reportTube.coll.update( + if (by.id == UserRepo.lichessId) coll.update( selectRecent(user, reason), - Json.obj("$set" -> (reportTube.toMongo(report).get - "processedBy" - "_id")) + $doc("$set" -> ReportBSONHandler.write(report).remove("processedBy", "_id")) ) flatMap { res => - (res.n == 0) ?? $insert(report) + (res.n == 0) ?? coll.insert(report).void } - else $insert(report) + else coll.insert(report).void } } >>- monitorUnprocessed } @@ -93,41 +92,41 @@ private[report] final class ReportApi { } } - def clean(userId: String): Funit = $update( - Json.obj( + def clean(userId: String): Funit = coll.update( + $doc( "user" -> userId, "reason" -> "cheat" ) ++ unprocessedSelect, $set("processedBy" -> "lichess"), - multi = true) + multi = true).void - def process(id: String, by: User): Funit = $find byId id flatMap { + def process(id: String, by: User): Funit = coll.byId[Report](id) flatMap { _ ?? { report => - $update( - Json.obj( + coll.update( + $doc( "user" -> report.user, "reason" -> report.reason ) ++ unprocessedSelect, $set("processedBy" -> by.id), - multi = true) + multi = true).void } >>- monitorUnprocessed >>- lila.mon.mod.report.close() } - def processEngine(userId: String, byModId: String): Funit = $update( - Json.obj( + def processEngine(userId: String, byModId: String): Funit = coll.update( + $doc( "user" -> userId, - "reason" -> $in(List(Reason.Cheat.name, Reason.CheatPrint.name)) + "reason" -> $in(Reason.Cheat.name, Reason.CheatPrint.name) ) ++ unprocessedSelect, $set("processedBy" -> byModId), - multi = true) >>- monitorUnprocessed + multi = true).void >>- monitorUnprocessed - def processTroll(userId: String, byModId: String): Funit = $update( - Json.obj( + def processTroll(userId: String, byModId: String): Funit = coll.update( + $doc( "user" -> userId, - "reason" -> $in(List(Reason.Insult.name, Reason.Troll.name, Reason.Other.name)) + "reason" -> $in(Reason.Insult.name, Reason.Troll.name, Reason.Other.name) ) ++ unprocessedSelect, $set("processedBy" -> byModId), - multi = true) >>- monitorUnprocessed + multi = true).void >>- monitorUnprocessed def autoInsultReport(userId: String, text: String): Funit = { UserRepo byId userId zip UserRepo.lichess flatMap { @@ -142,16 +141,17 @@ private[report] final class ReportApi { } >>- monitorUnprocessed def autoProcess(userId: String): Funit = - $update( - Json.obj("user" -> userId.toLowerCase), - Json.obj("processedBy" -> "lichess")) >>- monitorUnprocessed + coll.update( + $doc("user" -> userId.toLowerCase), + $doc("processedBy" -> "lichess")).void >>- monitorUnprocessed - private val unprocessedSelect = Json.obj("processedBy" -> $exists(false)) - private val processedSelect = Json.obj("processedBy" -> $exists(true)) + private val unprocessedSelect: Bdoc = "processedBy" $exists false + private val processedSelect: Bdoc = "processedBy" $exists true - def nbUnprocessed = $count(unprocessedSelect) + def nbUnprocessed = coll.countSel(unprocessedSelect) - def recent(nb: Int) = $find($query.all sort $sort.createdDesc, nb) + def recent(nb: Int) = + coll.find($empty).sort($sort.createdDesc).cursor[Report]().gather[List](nb) def unprocessedAndRecent(nb: Int): Fu[List[Report.WithUser]] = recentUnprocessed(nb) |+| recentProcessed(nb) flatMap { all => @@ -164,15 +164,16 @@ private[report] final class ReportApi { } def recentUnprocessed(nb: Int) = - $find($query(unprocessedSelect) sort $sort.createdDesc, nb) + coll.find(unprocessedSelect).sort($sort.createdDesc).cursor[Report]().gather[List](nb) - def recentProcessed(nb: Int) = $find($query(processedSelect) sort $sort.createdDesc, nb) + def recentProcessed(nb: Int) = + coll.find(processedSelect).sort($sort.createdDesc).cursor[Report]().gather[List](nb) - private def selectRecent(user: User, reason: Reason) = Json.obj( - "createdAt" -> $gt($date(DateTime.now minusDays 7)), + private def selectRecent(user: User, reason: Reason) = $doc( + "createdAt" $gt DateTime.now.minusDays(7), "user" -> user.id, "reason" -> reason.name) private def findRecent(user: User, reason: Reason): Fu[Option[Report]] = - $find.one(selectRecent(user, reason)) + coll.uno[Report](selectRecent(user, reason)) } diff --git a/modules/report/src/main/package.scala b/modules/report/src/main/package.scala index 781e0205cb..b2acce360c 100644 --- a/modules/report/src/main/package.scala +++ b/modules/report/src/main/package.scala @@ -1,12 +1,3 @@ package lila -import lila.db.JsTube - -package object report extends PackageObject with WithPlay { - - object tube { - - private[report] implicit lazy val reportTube = - Report.tube inColl Env.current.reportColl - } -} +package object report extends PackageObject with WithPlay diff --git a/modules/round/src/main/Cli.scala b/modules/round/src/main/Cli.scala deleted file mode 100644 index 8e6659cded..0000000000 --- a/modules/round/src/main/Cli.scala +++ /dev/null @@ -1,27 +0,0 @@ -package lila.round - -import scala.concurrent.duration._ - -import lila.db.api._ -import lila.user.UserRepo -import lila.game.Game -import lila.game.BSONHandlers._ -import lila.game.tube.gameTube - -private[round] final class Cli( - db: lila.db.Env, - roundMap: akka.actor.ActorRef, - system: akka.actor.ActorSystem) extends lila.common.Cli { - - def process = { - - case "round" :: "abort" :: "clock" :: Nil => - $enumerate[Game]($query(play.api.libs.json.Json.obj( - Game.BSONFields.status -> chess.Status.Started.id, - Game.BSONFields.clock -> $exists(true) - ))) { game => - roundMap ! lila.hub.actorApi.map.Tell(game.id, actorApi.round.AbortForMaintenance) - } inject "done" - } - -} diff --git a/modules/round/src/main/Drawer.scala b/modules/round/src/main/Drawer.scala index 1665b3e4a3..71f7367182 100644 --- a/modules/round/src/main/Drawer.scala +++ b/modules/round/src/main/Drawer.scala @@ -3,7 +3,7 @@ package lila.round import akka.pattern.ask import chess.{ Game => ChessGame, Board, Clock } -import lila.db.api._ +import lila.db.dsl._ import lila.game.{ Game, Event, Progress, Pov, PlayerRef, Namer, Source } import lila.pref.{ Pref, PrefApi } import makeTimeout.short diff --git a/modules/round/src/main/Env.scala b/modules/round/src/main/Env.scala index e97e332360..57ff57ffc8 100644 --- a/modules/round/src/main/Env.scala +++ b/modules/round/src/main/Env.scala @@ -118,7 +118,7 @@ final class Env( roundMap = roundMap, socketHub = socketHub, messenger = messenger, - bus = system.lilaBus) + bus = system.lilaBus)(system) lazy val perfsUpdater = new PerfsUpdater(historyApi, rankingApi) @@ -155,8 +155,6 @@ final class Env( private lazy val cheatDetector = new CheatDetector(reporter = hub.actor.report) - lazy val cli = new Cli(db, roundMap = roundMap, system = system) - lazy val messenger = new Messenger( socketHub = socketHub, chat = hub.actor.chat, diff --git a/modules/round/src/main/Finisher.scala b/modules/round/src/main/Finisher.scala index a6936b32cf..781dbcbbd8 100644 --- a/modules/round/src/main/Finisher.scala +++ b/modules/round/src/main/Finisher.scala @@ -4,12 +4,11 @@ import chess.Color._ import chess.Status._ import chess.{ Status, Color, Speed } -import lila.db.api._ +import lila.db.dsl._ import lila.game.actorApi.{ FinishGame, AbortedBy } import lila.game.{ GameRepo, Game, Pov, Event } import lila.i18n.I18nKey.{ Select => SelectI18nKey } import lila.playban.{ PlaybanApi, Outcome } -import lila.user.tube.userTube import lila.user.{ User, UserRepo, Perfs } private[round] final class Finisher( @@ -118,6 +117,6 @@ private[round] final class Finisher( else if (game.loserUserId exists (user.id==)) -1 else 0, totalTime = totalTime, - tvTime = tvTime) + tvTime = tvTime).void } } diff --git a/modules/round/src/main/ForecastApi.scala b/modules/round/src/main/ForecastApi.scala index ee2afc8e6c..4f9eebe264 100644 --- a/modules/round/src/main/ForecastApi.scala +++ b/modules/round/src/main/ForecastApi.scala @@ -3,7 +3,7 @@ package lila.round import reactivemongo.bson._ import lila.db.BSON.BSONJodaDateTimeHandler -import lila.db.Implicits._ +import lila.db.dsl._ import org.joda.time.DateTime import scala.concurrent.duration.Duration import scala.concurrent.Promise @@ -28,7 +28,7 @@ final class ForecastApi(coll: Coll, roundMap: akka.actor.ActorSelection) { private def saveSteps(pov: Pov, steps: Forecast.Steps): Funit = { lila.mon.round.forecast.create() coll.update( - BSONDocument("_id" -> pov.fullId), + $doc("_id" -> pov.fullId), Forecast( _id = pov.fullId, steps = steps, @@ -37,7 +37,7 @@ final class ForecastApi(coll: Coll, roundMap: akka.actor.ActorSelection) { } def save(pov: Pov, steps: Forecast.Steps): Funit = firstStep(steps) match { - case None => coll.remove(BSONDocument("_id" -> pov.fullId)).void + case None => coll.remove($doc("_id" -> pov.fullId)).void case Some(step) if pov.game.turns == step.ply - 1 => saveSteps(pov, steps) case _ => fufail(Forecast.OutOfSync) } @@ -59,7 +59,7 @@ final class ForecastApi(coll: Coll, roundMap: akka.actor.ActorSelection) { } def loadForDisplay(pov: Pov): Fu[Option[Forecast]] = - pov.forecastable ?? coll.find(BSONDocument("_id" -> pov.fullId)).one[Forecast] flatMap { + pov.forecastable ?? coll.find($doc("_id" -> pov.fullId)).uno[Forecast] flatMap { case None => fuccess(none) case Some(fc) => if (firstStep(fc.steps).exists(_.ply != pov.game.turns + 1)) clearPov(pov) inject none @@ -67,7 +67,7 @@ final class ForecastApi(coll: Coll, roundMap: akka.actor.ActorSelection) { } def loadForPlay(pov: Pov): Fu[Option[Forecast]] = - pov.game.forecastable ?? coll.find(BSONDocument("_id" -> pov.fullId)).one[Forecast] flatMap { + pov.game.forecastable ?? coll.find($doc("_id" -> pov.fullId)).uno[Forecast] flatMap { case None => fuccess(none) case Some(fc) => if (firstStep(fc.steps).exists(_.ply != pov.game.turns)) clearPov(pov) inject none @@ -79,7 +79,7 @@ final class ForecastApi(coll: Coll, roundMap: akka.actor.ActorSelection) { case None => fuccess(none) case Some(fc) => fc(g, last) match { case Some((newFc, uciMove)) if newFc.steps.nonEmpty => - coll.update(BSONDocument("_id" -> fc._id), newFc) inject uciMove.some + coll.update($doc("_id" -> fc._id), newFc) inject uciMove.some case Some((newFc, uciMove)) => clearPov(Pov player g) inject uciMove.some case _ => clearPov(Pov player g) inject none } @@ -88,9 +88,9 @@ final class ForecastApi(coll: Coll, roundMap: akka.actor.ActorSelection) { private def firstStep(steps: Forecast.Steps) = steps.headOption.flatMap(_.headOption) - def clearGame(g: Game) = coll.remove(BSONDocument( - "_id" -> BSONDocument("$in" -> chess.Color.all.map(g.fullIdOf)) + def clearGame(g: Game) = coll.remove($doc( + "_id" -> $doc("$in" -> chess.Color.all.map(g.fullIdOf)) )).void - def clearPov(pov: Pov) = coll.remove(BSONDocument("_id" -> pov.fullId)).void + def clearPov(pov: Pov) = coll.remove($doc("_id" -> pov.fullId)).void } diff --git a/modules/round/src/main/History.scala b/modules/round/src/main/History.scala index 65d5bf2c31..7aa2a68387 100644 --- a/modules/round/src/main/History.scala +++ b/modules/round/src/main/History.scala @@ -8,7 +8,7 @@ import org.joda.time.DateTime import reactivemongo.bson._ import lila.db.BSON.BSONJodaDateTimeHandler -import lila.db.Types.Coll +import lila.db.dsl._ import lila.game.Event import lila.socket.actorApi.GetVersion @@ -79,19 +79,19 @@ private[round] object History { private def serverStarting = !lila.common.PlayApp.startedSinceMinutes(5) private def load(coll: Coll, gameId: String, withPersistence: Boolean): Fu[VersionedEvents] = - coll.find(BSONDocument("_id" -> gameId)).one[BSONDocument].map { + coll.byId[Bdoc](gameId).map { _.flatMap(_.getAs[VersionedEvents]("e")) ?? (_.reverse) } addEffect { - case events if events.nonEmpty && !withPersistence => coll.remove(BSONDocument("_id" -> gameId)).void + case events if events.nonEmpty && !withPersistence => coll.remove($doc("_id" -> gameId)).void case _ => } private def persist(coll: Coll, gameId: String)(vevs: List[VersionedEvent]) { if (vevs.nonEmpty) coll.uncheckedUpdate( - BSONDocument("_id" -> gameId), - BSONDocument( - "$set" -> BSONDocument("e" -> vevs.reverse), - "$setOnInsert" -> BSONDocument("d" -> DateTime.now)), + $doc("_id" -> gameId), + $doc( + "$set" -> $doc("e" -> vevs.reverse), + "$setOnInsert" -> $doc("d" -> DateTime.now)), upsert = true) } } diff --git a/modules/round/src/main/NoteApi.scala b/modules/round/src/main/NoteApi.scala index 5b53941b9d..a7b21bbb41 100644 --- a/modules/round/src/main/NoteApi.scala +++ b/modules/round/src/main/NoteApi.scala @@ -1,21 +1,19 @@ package lila.round -import lila.db.Types.Coll +import lila.db.dsl._ import reactivemongo.bson._ final class NoteApi(coll: Coll) { def get(gameId: String, userId: String): Fu[String] = - coll.find(BSONDocument("_id" -> makeId(gameId, userId))).one[BSONDocument] map { - _ flatMap (_.getAs[String]("t")) getOrElse "" - } + coll.primitiveOne[String]($id(makeId(gameId, userId)), "t") map (~_) def set(gameId: String, userId: String, text: String) = { if (text.isEmpty) coll.remove(BSONDocument("_id" -> makeId(gameId, userId))) else coll.update( - BSONDocument("_id" -> makeId(gameId, userId)), - BSONDocument("$set" -> BSONDocument("t" -> text)), + $id(makeId(gameId, userId)), + $set("t" -> text), upsert = true) }.void diff --git a/modules/round/src/main/Rematcher.scala b/modules/round/src/main/Rematcher.scala index d4f57038d8..caf0fe0d3f 100644 --- a/modules/round/src/main/Rematcher.scala +++ b/modules/round/src/main/Rematcher.scala @@ -7,7 +7,7 @@ import chess.variant._ import chess.{ Game => ChessGame, Board, Clock, Color => ChessColor, Castles } import ChessColor.{ White, Black } -import lila.db.api._ +import lila.db.dsl._ import lila.game.{ GameRepo, Game, Event, Progress, Pov, Source, AnonCookie, PerfPicker } import lila.memo.ExpireSetMemo import lila.user.{ User, UserRepo } diff --git a/modules/round/src/main/Socket.scala b/modules/round/src/main/Socket.scala index 4c8cb40b4c..00f1a14f97 100644 --- a/modules/round/src/main/Socket.scala +++ b/modules/round/src/main/Socket.scala @@ -18,7 +18,7 @@ import lila.hub.actorApi.round.{ IsOnGame, AnalysisAvailable } import lila.hub.actorApi.tv.{ Select => TvSelect } import lila.hub.TimeBomb import lila.socket._ -import lila.socket.actorApi.{ Connected => _, _ } +import lila.socket.actorApi._ import makeTimeout.short private[round] final class Socket( @@ -139,16 +139,13 @@ private[round] final class Socket( blackIsGone = blackIsGone) } pipeTo sender - case Join(uid, user, color, playerId, ip, userTv) => - val (enumerator, channel) = Concurrent.broadcast[JsValue] - val member = Member(channel, user, color, playerId, ip, userTv = userTv) + case AddMember(uid, member) => addMember(uid, member) notifyCrowd - playerDo(color, _.ping) - sender ! Connected(enumerator, member) + playerDo(member.color, _.ping) if (member.userTv.isDefined) refreshSubscriptions if (member.owner) lilaBus.publish( - lila.hub.actorApi.round.SocketEvent.OwnerJoin(gameId, color, ip), + lila.hub.actorApi.round.SocketEvent.OwnerJoin(gameId, member.color, member.ip), 'roundDoor) case Nil => diff --git a/modules/round/src/main/SocketHandler.scala b/modules/round/src/main/SocketHandler.scala index ff7292af94..4c3519cd5d 100644 --- a/modules/round/src/main/SocketHandler.scala +++ b/modules/round/src/main/SocketHandler.scala @@ -14,7 +14,7 @@ import lila.common.PimpedJson._ import lila.game.{ Game, Pov, PovRef, PlayerRef, GameRepo } import lila.hub.actorApi.map._ import lila.hub.actorApi.round.Berserk -import lila.socket.actorApi.{ Connected => _, _ } +import lila.socket.actorApi._ import lila.socket.Handler import lila.user.User import makeTimeout.short @@ -24,7 +24,7 @@ private[round] final class SocketHandler( socketHub: ActorRef, hub: lila.hub.Env, messenger: Messenger, - bus: lila.common.Bus) { + bus: lila.common.Bus)(implicit val system: ActorSystem) { private def controller( gameId: String, @@ -100,7 +100,7 @@ private[round] final class SocketHandler( uid: String, user: Option[User], ip: String, - userTv: Option[String]): Fu[Option[JsSocketHandler]] = + userTv: Option[String]): Fu[Option[JsFlow]] = GameRepo.pov(gameId, colorName) flatMap { _ ?? { join(_, none, uid, "", user, ip, userTv = userTv) map some } } @@ -110,7 +110,7 @@ private[round] final class SocketHandler( uid: String, token: String, user: Option[User], - ip: String): Fu[JsSocketHandler] = + ip: String): Fu[JsFlow] = join(pov, Some(pov.playerId), uid, token, user, ip, userTv = none) private def join( @@ -120,21 +120,16 @@ private[round] final class SocketHandler( token: String, user: Option[User], ip: String, - userTv: Option[String]): Fu[JsSocketHandler] = { - val join = Join( - uid = uid, - user = user, - color = pov.color, - playerId = playerId, - ip = ip, - userTv = userTv) - socketHub ? Get(pov.gameId) mapTo manifest[ActorRef] flatMap { socket => - Handler(hub, socket, uid, join, user map (_.id)) { - case Connected(enum, member) => - (controller(pov.gameId, socket, uid, pov.ref, member), enum, member) + userTv: Option[String]): Fu[JsFlow] = + socketHub ? Get(pov.gameId) mapTo manifest[ActorRef] map { socket => + Handler.actorRef { out => + val member = Member(out, user, pov.color, playerId, ip, userTv = userTv) + socket ! AddMember(uid, member) + Handler.props(hub, socket, member, uid, user.map(_.id)) { + controller(pov.gameId, socket, uid, pov.ref, member) + } } } - } private def parseMove(o: JsObject) = for { d ← o obj "d" @@ -142,7 +137,7 @@ private[round] final class SocketHandler( dest ← d str "to" prom = d str "promotion" move <- Uci.Move.fromStrings(orig, dest, prom) - blur = (d int "b") == Some(1) + blur = d int "b" contains 1 lag = d int "lag" } yield (move, blur, ~lag) @@ -151,7 +146,7 @@ private[round] final class SocketHandler( role ← d str "role" pos ← d str "pos" drop <- Uci.Drop.fromStrings(role, pos) - blur = (d int "b") == Some(1) + blur = d int "b" contains 1 lag = d int "lag" } yield (drop, blur, ~lag) diff --git a/modules/round/src/main/Titivate.scala b/modules/round/src/main/Titivate.scala index e67746e08e..2ec107e0ae 100644 --- a/modules/round/src/main/Titivate.scala +++ b/modules/round/src/main/Titivate.scala @@ -6,8 +6,7 @@ import play.api.libs.iteratee._ import reactivemongo.api._ import scala.concurrent.duration._ -import lila.db.api._ -import lila.game.tube.gameTube +import lila.db.dsl._ import lila.game.{ Query, Game, GameRepo } import lila.game.BSONHandlers.gameBSONHandler import lila.hub.actorApi.map.Tell @@ -37,8 +36,7 @@ private[round] final class Titivate( throw new RuntimeException(msg) case Run => - $query(Query.checkable) - .cursor[Game]() + GameRepo.cursor(Query.checkable) .enumerate(5000, stopOnError = true) .|>>>(Iteratee.foldM[Game, Int](0) { case (count, game) => { diff --git a/modules/round/src/main/TvBroadcast.scala b/modules/round/src/main/TvBroadcast.scala index 3d1baf435f..2a6d8e50ad 100644 --- a/modules/round/src/main/TvBroadcast.scala +++ b/modules/round/src/main/TvBroadcast.scala @@ -1,28 +1,30 @@ package lila.round import akka.actor._ +import akka.stream._ +import akka.stream.scaladsl._ import lila.hub.actorApi.game.ChangeFeatured import lila.hub.actorApi.round.MoveEvent import lila.socket.Socket.makeMessage -import play.api.libs.iteratee._ import play.api.libs.json._ private final class TvBroadcast extends Actor { - private val (enumerator, channel) = Concurrent.broadcast[JsValue] + val (out, publisher) = + lila.common.AkkaStream.actorPublisher(20, OverflowStrategy.dropHead)(materializer(context.system)) private var featuredId = none[String] def receive = { - case TvBroadcast.GetEnumerator => sender ! enumerator + case TvBroadcast.GetPublisher => sender ! publisher case ChangeFeatured(id, msg) => featuredId = id.some - channel push msg + out ! msg case move: MoveEvent if Some(move.gameId) == featuredId => - channel push makeMessage("fen", Json.obj( + out ! makeMessage("fen", Json.obj( "fen" -> move.fen, "lm" -> move.move )) @@ -31,7 +33,7 @@ private final class TvBroadcast extends Actor { object TvBroadcast { - type EnumeratorType = Enumerator[JsValue] + type PublisherType = org.reactivestreams.Publisher[JsValue] - case object GetEnumerator + case object GetPublisher } diff --git a/modules/round/src/main/actorApi.scala b/modules/round/src/main/actorApi.scala index 431e94a5e7..d2bfe3da1a 100644 --- a/modules/round/src/main/actorApi.scala +++ b/modules/round/src/main/actorApi.scala @@ -3,6 +3,7 @@ package actorApi import scala.concurrent.duration.FiniteDuration import scala.concurrent.Promise +import akka.actor.ActorRef import chess.Color import chess.format.Uci @@ -29,7 +30,7 @@ sealed trait Member extends SocketMember { object Member { def apply( - channel: JsChannel, + out: ActorRef, user: Option[User], color: Color, playerIdOption: Option[String], @@ -37,14 +38,14 @@ object Member { userTv: Option[String]): Member = { val userId = user map (_.id) val troll = user.??(_.troll) - playerIdOption.fold[Member](Watcher(channel, userId, color, troll, ip, userTv)) { playerId => - Owner(channel, userId, playerId, color, troll, ip) + playerIdOption.fold[Member](Watcher(out, userId, color, troll, ip, userTv)) { playerId => + Owner(out, userId, playerId, color, troll, ip) } } } case class Owner( - channel: JsChannel, + out: ActorRef, userId: Option[String], playerId: String, color: Color, @@ -56,7 +57,7 @@ case class Owner( } case class Watcher( - channel: JsChannel, + out: ActorRef, userId: Option[String], color: Color, troll: Boolean, @@ -66,14 +67,8 @@ case class Watcher( val playerIdOption = none } -case class Join( - uid: String, - user: Option[User], - color: Color, - playerId: Option[String], - ip: String, - userTv: Option[String]) -case class Connected(enumerator: JsEnumerator, member: Member) +case class AddMember(uid: String, member: Member) + case class Bye(color: Color) case class IsGone(color: Color) case object GetSocketStatus diff --git a/modules/security/src/main/Api.scala b/modules/security/src/main/Api.scala index 71540da6ce..4150524ade 100644 --- a/modules/security/src/main/Api.scala +++ b/modules/security/src/main/Api.scala @@ -7,10 +7,12 @@ import play.api.data.Forms._ import play.api.mvc.RequestHeader import reactivemongo.bson._ +import lila.db.dsl._ import lila.db.BSON.BSONJodaDateTimeHandler import lila.user.{ User, UserRepo } final class Api( + coll: Coll, firewall: Firewall, tor: Tor, geoIP: GeoIP, @@ -78,16 +80,16 @@ final class Api( def userIdsSharingFingerprint = userIdsSharingField("fp") _ private def userIdsSharingField(field: String)(userId: String): Fu[List[String]] = - tube.storeColl.distinct( + coll.distinct( field, - BSONDocument("user" -> userId, field -> BSONDocument("$exists" -> true)).some + $doc("user" -> userId, field -> $doc("$exists" -> true)).some ).flatMap { case Nil => fuccess(Nil) - case values => tube.storeColl.distinct( + case values => coll.distinct( "user", - BSONDocument( - field -> BSONDocument("$in" -> values), - "user" -> BSONDocument("$ne" -> userId) + $doc( + field -> $doc("$in" -> values), + "user" -> $doc("$ne" -> userId) ).some ) map lila.db.BSON.asStrings } @@ -97,11 +99,11 @@ final class Api( def recentUserIdsByIp = recentUserIdsByField("ip") _ private def recentUserIdsByField(field: String)(value: String): Fu[List[String]] = - tube.storeColl.distinct( + coll.distinct( "user", - BSONDocument( + $doc( field -> value, - "date" -> BSONDocument("$gt" -> DateTime.now.minusYears(1)) + "date" -> $doc("$gt" -> DateTime.now.minusYears(1)) ).some ) map lila.db.BSON.asStrings } diff --git a/modules/security/src/main/Cli.scala b/modules/security/src/main/Cli.scala index 6e0dddc030..d77d9038a0 100644 --- a/modules/security/src/main/Cli.scala +++ b/modules/security/src/main/Cli.scala @@ -16,7 +16,7 @@ private[security] final class Cli extends lila.common.Cli { case "security" :: "grant" :: uid :: roles => perform(uid, user => - UserRepo.setRoles(user.id, roles map (_.toUpperCase)) + UserRepo.setRoles(user.id, roles map (_.toUpperCase)).void ) } diff --git a/modules/security/src/main/DataForm.scala b/modules/security/src/main/DataForm.scala index baac8c98c3..b7dca4af58 100644 --- a/modules/security/src/main/DataForm.scala +++ b/modules/security/src/main/DataForm.scala @@ -5,9 +5,7 @@ import play.api.data.Forms._ import play.api.data.validation.Constraints import lila.common.LameName -import lila.db.api.$count -import lila.user.tube.userTube -import lila.user.User +import lila.user.{ User, UserRepo } final class DataForm( val captcher: akka.actor.ActorSelection, @@ -42,7 +40,7 @@ final class DataForm( Constraints.pattern( regex = """^[^\d].+$""".r, error = "The username must not start with a number") - ).verifying("This user already exists", u => !$count.exists(u.toLowerCase) awaitSeconds 2) + ).verifying("This user already exists", u => !UserRepo.nameExists(u).awaitSeconds(2)) .verifying("This username is not acceptable", u => !LameName(u)) val website = Form(mapping( diff --git a/modules/security/src/main/Env.scala b/modules/security/src/main/Env.scala index 206388b9eb..170dbddcf0 100644 --- a/modules/security/src/main/Env.scala +++ b/modules/security/src/main/Env.scala @@ -7,7 +7,7 @@ import com.typesafe.config.Config import scala.concurrent.duration._ import lila.common.PimpedConfig._ -import lila.db.Types.Coll +import lila.db.dsl.Coll import lila.user.{ User, UserRepo } final class Env( @@ -52,6 +52,7 @@ final class Env( val RecaptchaPublicKey = config getString "recaptcha.public_key" lazy val firewall = new Firewall( + coll = firewallColl, cookieName = FirewallCookieName.some filter (_ => FirewallCookieEnabled), enabled = FirewallEnabled, cachedIpsTtl = FirewallCachedIpsTtl) @@ -72,7 +73,7 @@ final class Env( file = GeoIPFile, cacheTtl = GeoIPCacheTtl) - lazy val userSpy = UserSpy(firewall, geoIP) _ + lazy val userSpy = UserSpy(firewall, geoIP)(storeColl) _ def store = Store @@ -107,7 +108,7 @@ final class Env( scheduler.once(30 seconds)(tor.refresh(_ => funit)) scheduler.effect(TorRefreshDelay, "Refresh Tor exit nodes")(tor.refresh(firewall.unblockIps)) - lazy val api = new Api(firewall, tor, geoIP, emailAddress) + lazy val api = new Api(storeColl, firewall, tor, geoIP, emailAddress) def cli = new Cli diff --git a/modules/security/src/main/Firewall.scala b/modules/security/src/main/Firewall.scala index f79c195802..961f421384 100644 --- a/modules/security/src/main/Firewall.scala +++ b/modules/security/src/main/Firewall.scala @@ -8,15 +8,15 @@ import ornicar.scalalib.Random import play.api.libs.json._ import play.api.mvc.Results.Redirect import play.api.mvc.{ RequestHeader, Handler, Action, Cookies } -import play.modules.reactivemongo.json.ImplicitBSONHandlers._ import spray.caching.{ LruCache, Cache } import lila.common.LilaCookie import lila.common.PimpedJson._ -import lila.db.api._ -import tube.firewallTube +import lila.db.dsl._ +import lila.db.BSON.BSONJodaDateTimeHandler final class Firewall( + coll: Coll, cookieName: Option[String], enabled: Boolean, cachedIpsTtl: Duration) { @@ -36,11 +36,11 @@ final class Firewall( def accepts(req: RequestHeader): Fu[Boolean] = blocks(req) map (!_) def blockIp(ip: String): Funit = validIp(ip) ?? { - $update(Json.obj("_id" -> ip), Json.obj("_id" -> ip, "date" -> $date(DateTime.now)), upsert = true) >>- refresh + coll.update($id(ip), $doc("_id" -> ip, "date" -> DateTime.now), upsert = true).void >>- refresh } def unblockIps(ips: Iterable[String]): Funit = - $remove($select.byIds(ips filter validIp)) >>- refresh + coll.remove($inIds(ips filter validIp)).void >>- refresh private def infectCookie(name: String)(implicit req: RequestHeader) = Action { logger.info("Infect cookie " + formatReq(req)) @@ -75,7 +75,7 @@ final class Firewall( def clear { cache.clear } def contains(ip: String) = apply map (_ contains strToIp(ip)) def fetch: Fu[Set[IP]] = - firewallTube.coll.distinct("_id") map { res => + coll.distinct("_id") map { res => lila.db.BSON.asStringSet(res) map strToIp } addEffect { ips => lila.mon.security.firewall.ip(ips.size) diff --git a/modules/security/src/main/Store.scala b/modules/security/src/main/Store.scala index 4ff3d9b418..4e2d152646 100644 --- a/modules/security/src/main/Store.scala +++ b/modules/security/src/main/Store.scala @@ -4,23 +4,24 @@ import scala.concurrent.Future import org.joda.time.DateTime import play.api.mvc.RequestHeader -import reactivemongo.bson.BSONDocument import reactivemongo.bson.Macros -import lila.db.api._ +import lila.db.dsl._ import lila.db.BSON.BSONJodaDateTimeHandler import lila.user.{ User, UserRepo } import lila.common.HTTPRequest -import tube.storeColl object Store { + // dirty + private val coll = Env.current.storeColl + private[security] def save( sessionId: String, userId: String, req: RequestHeader, apiVersion: Option[Int]): Funit = - storeColl.insert(BSONDocument( + coll.insert($doc( "_id" -> sessionId, "user" -> userId, "ip" -> HTTPRequest.lastRemoteAddress(req), @@ -31,48 +32,48 @@ object Store { )).void def userId(sessionId: String): Fu[Option[String]] = - storeColl.find( - BSONDocument("_id" -> sessionId, "up" -> true), - BSONDocument("user" -> true, "_id" -> false) - ).one[BSONDocument] map { _ flatMap (_.getAs[String]("user")) } + coll.find( + $doc("_id" -> sessionId, "up" -> true), + $doc("user" -> true, "_id" -> false) + ).uno[Bdoc] map { _ flatMap (_.getAs[String]("user")) } case class UserIdAndFingerprint(user: String, fp: Option[String]) private implicit val UserIdAndFingerprintBSONReader = Macros.reader[UserIdAndFingerprint] def userIdAndFingerprint(sessionId: String): Fu[Option[UserIdAndFingerprint]] = - storeColl.find( - BSONDocument("_id" -> sessionId, "up" -> true), - BSONDocument("user" -> true, "fp" -> true, "_id" -> false) - ).one[UserIdAndFingerprint] + coll.find( + $doc("_id" -> sessionId, "up" -> true), + $doc("user" -> true, "fp" -> true, "_id" -> false) + ).uno[UserIdAndFingerprint] def delete(sessionId: String): Funit = - storeColl.update( - BSONDocument("_id" -> sessionId), - BSONDocument("$set" -> BSONDocument("up" -> false))).void + coll.update( + $doc("_id" -> sessionId), + $doc("$set" -> $doc("up" -> false))).void def closeUserAndSessionId(userId: String, sessionId: String): Funit = - storeColl.update( - BSONDocument("user" -> userId, "_id" -> sessionId, "up" -> true), - BSONDocument("$set" -> BSONDocument("up" -> false))).void + coll.update( + $doc("user" -> userId, "_id" -> sessionId, "up" -> true), + $doc("$set" -> $doc("up" -> false))).void def closeUserExceptSessionId(userId: String, sessionId: String): Funit = - storeColl.update( - BSONDocument("user" -> userId, "_id" -> BSONDocument("$ne" -> sessionId), "up" -> true), - BSONDocument("$set" -> BSONDocument("up" -> false)), + coll.update( + $doc("user" -> userId, "_id" -> $doc("$ne" -> sessionId), "up" -> true), + $doc("$set" -> $doc("up" -> false)), multi = true).void // useful when closing an account, // we want to logout too - def disconnect(userId: String): Funit = storeColl.update( - BSONDocument("user" -> userId), - BSONDocument("$set" -> BSONDocument("up" -> false)), + def disconnect(userId: String): Funit = coll.update( + $doc("user" -> userId), + $doc("$set" -> $doc("up" -> false)), multi = true).void private implicit val UserSessionBSONHandler = Macros.handler[UserSession] def openSessions(userId: String, nb: Int): Fu[List[UserSession]] = - storeColl.find( - BSONDocument("user" -> userId, "up" -> true) - ).sort(BSONDocument("date" -> -1)).cursor[UserSession]().collect[List](nb) + coll.find( + $doc("user" -> userId, "up" -> true) + ).sort($doc("date" -> -1)).cursor[UserSession]().gather[List](nb) def setFingerprint(id: String, fingerprint: String): Fu[String] = { import java.util.Base64 @@ -82,9 +83,9 @@ object Store { Hex decodeHex fingerprint.toArray } take 8 } flatMap { hash => - storeColl.update( - BSONDocument("_id" -> id), - BSONDocument("$set" -> BSONDocument("fp" -> hash)) + coll.update( + $doc("_id" -> id), + $doc("$set" -> $doc("fp" -> hash)) ) inject hash } } @@ -95,10 +96,10 @@ object Store { private implicit val InfoBSONHandler = Macros.handler[Info] def findInfoByUser(userId: String): Fu[List[Info]] = - storeColl.find( - BSONDocument("user" -> userId), - BSONDocument("_id" -> false, "ip" -> true, "ua" -> true, "fp" -> true) - ).cursor[Info]().collect[List]() + coll.find( + $doc("user" -> userId), + $doc("_id" -> false, "ip" -> true, "ua" -> true, "fp" -> true) + ).cursor[Info]().gather[List]() private case class DedupInfo(_id: String, ip: String, ua: String) { def compositeKey = s"$ip $ua" @@ -106,15 +107,13 @@ object Store { private implicit val DedupInfoBSONHandler = Macros.handler[DedupInfo] def dedup(userId: String, keepSessionId: String): Funit = - storeColl.find(BSONDocument( + coll.find($doc( "user" -> userId, "up" -> true - )).sort(BSONDocument("date" -> -1)) - .cursor[DedupInfo]().collect[List]() flatMap { sessions => + )).sort($doc("date" -> -1)) + .cursor[DedupInfo]().gather[List]() flatMap { sessions => val olds = sessions.groupBy(_.compositeKey).values.map(_ drop 1).flatten .filter(_._id != keepSessionId) - storeColl.remove( - BSONDocument("_id" -> BSONDocument("$in" -> olds.map(_._id))) - ).void + coll.remove($inIds(olds.map(_._id))).void } } diff --git a/modules/security/src/main/UserSpy.scala b/modules/security/src/main/UserSpy.scala index d7e252dbba..c70dc60a53 100644 --- a/modules/security/src/main/UserSpy.scala +++ b/modules/security/src/main/UserSpy.scala @@ -1,14 +1,11 @@ package lila.security -import scala.concurrent.Future - import play.api.mvc.RequestHeader import reactivemongo.bson._ import lila.common.PimpedJson._ -import lila.db.api._ +import lila.db.dsl._ import lila.user.{ User, UserRepo } -import tube.storeColl case class UserSpy( ips: List[UserSpy.IPData], @@ -42,7 +39,7 @@ object UserSpy { case class IPData(ip: IP, blocked: Boolean, location: Location) - private[security] def apply(firewall: Firewall, geoIP: GeoIP)(userId: String): Fu[UserSpy] = for { + private[security] def apply(firewall: Firewall, geoIP: GeoIP)(coll: Coll)(userId: String): Fu[UserSpy] = for { user ← UserRepo named userId flatten "[spy] user not found" infos ← Store.findInfoByUser(user.id) ips = infos.map(_.ip).distinct @@ -50,8 +47,8 @@ object UserSpy { locations <- scala.concurrent.Future { ips map geoIP.orUnknown } - sharingIp ← exploreSimilar("ip")(user) - sharingFingerprint ← exploreSimilar("fp")(user) + sharingIp ← exploreSimilar("ip")(user)(coll) + sharingFingerprint ← exploreSimilar("fp")(user)(coll) } yield UserSpy( ips = ips zip blockedIps zip locations map { case ((ip, blocked), location) => IPData(ip, blocked, location) @@ -60,25 +57,25 @@ object UserSpy { usersSharingIp = (sharingIp + user).toList.sortBy(-_.createdAt.getMillis), usersSharingFingerprint = (sharingFingerprint + user).toList.sortBy(-_.createdAt.getMillis)) - private def exploreSimilar(field: String)(user: User): Fu[Set[User]] = + private def exploreSimilar(field: String)(user: User)(implicit coll: Coll): Fu[Set[User]] = nextValues(field)(user) flatMap { nValues => nextUsers(field)(nValues, user) map { _ + user } } - private def nextValues(field: String)(user: User): Fu[Set[Value]] = - storeColl.find( - BSONDocument("user" -> user.id), - BSONDocument(field -> true) - ).cursor[BSONDocument]().collect[List]() map { + private def nextValues(field: String)(user: User)(implicit coll: Coll): Fu[Set[Value]] = + coll.find( + $doc("user" -> user.id), + $doc(field -> true) + ).cursor[Bdoc]().gather[List]() map { _.flatMap(_.getAs[Value](field)).toSet } - private def nextUsers(field: String)(values: Set[Value], user: User): Fu[Set[User]] = + private def nextUsers(field: String)(values: Set[Value], user: User)(implicit coll: Coll): Fu[Set[User]] = values.nonEmpty ?? { - storeColl.distinct("user", - BSONDocument( - field -> BSONDocument("$in" -> values), - "user" -> BSONDocument("$ne" -> user.id) + coll.distinct("user", + $doc( + field -> $doc("$in" -> values), + "user" -> $doc("$ne" -> user.id) ).some ) map lila.db.BSON.asStrings flatMap { userIds => userIds.nonEmpty ?? (UserRepo byIds userIds) map (_.toSet) diff --git a/modules/security/src/main/package.scala b/modules/security/src/main/package.scala index 7bee97fcbd..0d390f0d94 100644 --- a/modules/security/src/main/package.scala +++ b/modules/security/src/main/package.scala @@ -1,16 +1,6 @@ package lila -import lila.db.JsTube - package object security extends PackageObject with WithPlay { - object tube { - - private[security] def storeColl = Env.current.storeColl - - private[security] implicit lazy val firewallTube = - JsTube.json inColl Env.current.firewallColl - } - private[security] def logger = lila.log("security") } diff --git a/modules/setup/src/main/AnonConfigRepo.scala b/modules/setup/src/main/AnonConfigRepo.scala index 458253ebee..c0ed4a7002 100644 --- a/modules/setup/src/main/AnonConfigRepo.scala +++ b/modules/setup/src/main/AnonConfigRepo.scala @@ -5,19 +5,20 @@ import reactivemongo.api._ import reactivemongo.bson._ import lila.common.{ LilaCookie, LilaException } -import lila.db.api._ -import lila.db.Implicits._ +import lila.db.dsl._ 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] = diff --git a/modules/setup/src/main/FilterConfig.scala b/modules/setup/src/main/FilterConfig.scala index 1c0e3743aa..c2dc9e35d3 100644 --- a/modules/setup/src/main/FilterConfig.scala +++ b/modules/setup/src/main/FilterConfig.scala @@ -74,6 +74,4 @@ object FilterConfig { "s" -> o.speed.map(_.id), "e" -> o.ratingRange.toString) } - - private[setup] val tube = lila.db.BsTube(filterConfigBSONHandler) } diff --git a/modules/setup/src/main/FormFactory.scala b/modules/setup/src/main/FormFactory.scala index a0fc1a8ec4..bd829ddab7 100644 --- a/modules/setup/src/main/FormFactory.scala +++ b/modules/setup/src/main/FormFactory.scala @@ -1,12 +1,11 @@ package lila.setup import lila.rating.RatingRange -import lila.db.api._ +import lila.db.dsl._ 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) { diff --git a/modules/setup/src/main/Processor.scala b/modules/setup/src/main/Processor.scala index a5dfb4784b..327a2ac8c4 100644 --- a/modules/setup/src/main/Processor.scala +++ b/modules/setup/src/main/Processor.scala @@ -5,14 +5,13 @@ import akka.pattern.ask import chess.{ Game => ChessGame, Board, Color => ChessColor } import play.api.libs.json.{ Json, JsObject } -import lila.db.api._ +import lila.db.dsl._ import lila.game.{ Game, GameRepo, Pov, Progress, PerfPicker } import lila.i18n.I18nDomain 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, diff --git a/modules/setup/src/main/UserConfig.scala b/modules/setup/src/main/UserConfig.scala index 90810d1307..4d91f99560 100644 --- a/modules/setup/src/main/UserConfig.scala +++ b/modules/setup/src/main/UserConfig.scala @@ -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) } diff --git a/modules/setup/src/main/UserConfigRepo.scala b/modules/setup/src/main/UserConfigRepo.scala index 2dfdea06c1..e73571f7fd 100644 --- a/modules/setup/src/main/UserConfigRepo.scala +++ b/modules/setup/src/main/UserConfigRepo.scala @@ -4,35 +4,31 @@ import reactivemongo.api._ import reactivemongo.bson._ import lila.common.LilaException -import lila.db.api._ -import lila.db.Implicits._ +import lila.db.dsl._ 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) } diff --git a/modules/setup/src/main/package.scala b/modules/setup/src/main/package.scala index ff1f318fc1..5d9b72c58e 100644 --- a/modules/setup/src/main/package.scala +++ b/modules/setup/src/main/package.scala @@ -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") } diff --git a/modules/shutup/src/main/ShutupApi.scala b/modules/shutup/src/main/ShutupApi.scala index b3b0cc7f90..c787d3f4eb 100644 --- a/modules/shutup/src/main/ShutupApi.scala +++ b/modules/shutup/src/main/ShutupApi.scala @@ -2,12 +2,10 @@ package lila.shutup import org.joda.time.DateTime import reactivemongo.bson._ -import reactivemongo.bson.Macros import reactivemongo.core.commands._ import scala.concurrent.duration._ -import lila.db.BSON._ -import lila.db.Types.Coll +import lila.db.dsl._ import lila.game.GameRepo import lila.user.UserRepo @@ -20,8 +18,8 @@ final class ShutupApi( private implicit val UserRecordBSONHandler = Macros.handler[UserRecord] def getPublicLines(userId: String): Fu[List[String]] = - coll.find(BSONDocument("_id" -> userId), BSONDocument("pub" -> 1)) - .one[BSONDocument].map { + coll.find($doc("_id" -> userId), $doc("pub" -> 1)) + .uno[Bdoc].map { ~_.flatMap(_.getAs[List[String]]("pub")) } @@ -47,25 +45,25 @@ final class ShutupApi( case false => val analysed = Analyser(text) val pushPublicLine = - if (textType == TextType.PublicChat && analysed.nbBadWords > 0) BSONDocument( - "pub" -> BSONDocument( + if (textType == TextType.PublicChat && analysed.nbBadWords > 0) $doc( + "pub" -> $doc( "$each" -> List(text), "$slice" -> -20) ) - else BSONDocument() - val push = BSONDocument( - textType.key -> BSONDocument( + else $empty + val push = $doc( + textType.key -> $doc( "$each" -> List(BSONDouble(analysed.ratio)), "$slice" -> -textType.rotation) ) ++ pushPublicLine coll.findAndUpdate( - selector = BSONDocument("_id" -> userId), - update = BSONDocument("$push" -> push), + selector = $doc("_id" -> userId), + update = $doc("$push" -> push), fetchNewObject = true, upsert = true).map(_.value) map2 UserRecordBSONHandler.read flatMap { - case None => fufail(s"can't find user record for $userId") - case Some(userRecord) => legiferate(userRecord) - } logFailure lila.log("shutup") + case None => fufail(s"can't find user record for $userId") + case Some(userRecord) => legiferate(userRecord) + } logFailure lila.log("shutup") } } @@ -73,8 +71,8 @@ final class ShutupApi( userRecord.reports.exists(_.unacceptable) ?? { reporter ! lila.hub.actorApi.report.Shutup(userRecord.userId, reportText(userRecord)) coll.update( - BSONDocument("_id" -> userRecord.userId), - BSONDocument("$unset" -> BSONDocument( + $doc("_id" -> userRecord.userId), + $doc("$unset" -> $doc( TextType.PublicForumMessage.key -> true, TextType.TeamForumMessage.key -> true, TextType.PrivateMessage.key -> true, diff --git a/modules/simul/src/main/Env.scala b/modules/simul/src/main/Env.scala index 7012f67ba2..e5d4c5266c 100644 --- a/modules/simul/src/main/Env.scala +++ b/modules/simul/src/main/Env.scala @@ -18,7 +18,6 @@ final class Env( scheduler: lila.common.Scheduler, db: lila.db.Env, mongoCache: lila.memo.MongoCache.Builder, - flood: lila.security.Flood, hub: lila.hub.Env, lightUser: String => Option[lila.common.LightUser], onGameStart: String => Unit, @@ -71,8 +70,7 @@ final class Env( hub = hub, socketHub = socketHub, chat = hub.actor.chat, - flood = flood, - exists = repo.exists) + exists = repo.exists)(system) system.lilaBus.subscribe(system.actorOf(Props(new Actor { import akka.pattern.pipe @@ -122,7 +120,6 @@ object Env { scheduler = lila.common.PlayApp.scheduler, db = lila.db.Env.current, mongoCache = lila.memo.Env.current.mongoCache, - flood = lila.security.Env.current.flood, hub = lila.hub.Env.current, lightUser = lila.user.Env.current.lightUser, onGameStart = lila.game.Env.current.onStart, diff --git a/modules/simul/src/main/SimulApi.scala b/modules/simul/src/main/SimulApi.scala index a9a84a04be..710d59e616 100644 --- a/modules/simul/src/main/SimulApi.scala +++ b/modules/simul/src/main/SimulApi.scala @@ -8,7 +8,7 @@ import scala.concurrent.duration._ import chess.Status import chess.variant.Variant import lila.common.Debouncer -import lila.db.Types.Coll +import lila.db.dsl.Coll import lila.game.{ Game, GameRepo } import lila.hub.actorApi.lobby.ReloadSimuls import lila.hub.actorApi.map.Tell diff --git a/modules/simul/src/main/SimulRepo.scala b/modules/simul/src/main/SimulRepo.scala index 04babfcf9b..1700208ea7 100644 --- a/modules/simul/src/main/SimulRepo.scala +++ b/modules/simul/src/main/SimulRepo.scala @@ -7,7 +7,7 @@ import reactivemongo.core.commands._ import chess.Status import chess.variant.Variant import lila.db.BSON -import lila.db.Types.Coll +import lila.db.dsl._ import lila.game.{ Game, GameRepo } import lila.user.{ User, UserRepo } @@ -34,7 +34,7 @@ private[simul] final class SimulRepo(simulColl: Coll) { status = r.get[Status]("status"), wins = r boolO "wins", hostColor = r.strO("hostColor").flatMap(chess.Color.apply) | chess.White) - def writes(w: BSON.Writer, o: SimulPairing) = BSONDocument( + def writes(w: BSON.Writer, o: SimulPairing) = $doc( "player" -> o.player, "gameId" -> o.gameId, "status" -> o.status, @@ -44,20 +44,19 @@ private[simul] final class SimulRepo(simulColl: Coll) { private implicit val SimulBSONHandler = Macros.handler[Simul] - private val createdSelect = BSONDocument("status" -> SimulStatus.Created.id) - private val startedSelect = BSONDocument("status" -> SimulStatus.Started.id) - private val finishedSelect = BSONDocument("status" -> SimulStatus.Finished.id) - private val createdSort = BSONDocument("createdAt" -> -1) + private val createdSelect = $doc("status" -> SimulStatus.Created.id) + private val startedSelect = $doc("status" -> SimulStatus.Started.id) + private val finishedSelect = $doc("status" -> SimulStatus.Finished.id) + private val createdSort = $doc("createdAt" -> -1) def find(id: Simul.ID): Fu[Option[Simul]] = - simulColl.find(BSONDocument("_id" -> id)).one[Simul] + simulColl.byId[Simul](id) def exists(id: Simul.ID): Fu[Boolean] = - simulColl.count(BSONDocument("_id" -> id).some) map (0 !=) + simulColl.countSel($id(id)) map (0 !=) def createdByHostId(hostId: String): Fu[List[Simul]] = - simulColl.find(createdSelect ++ BSONDocument("hostId" -> hostId)) - .cursor[Simul]().collect[List]() + simulColl.find(createdSelect ++ $doc("hostId" -> hostId)).list[Simul]() def findStarted(id: Simul.ID): Fu[Option[Simul]] = find(id) map (_ filter (_.isStarted)) @@ -65,50 +64,47 @@ private[simul] final class SimulRepo(simulColl: Coll) { def findCreated(id: Simul.ID): Fu[Option[Simul]] = find(id) map (_ filter (_.isCreated)) - def allCreated: Fu[List[Simul]] = simulColl.find( - createdSelect - ).sort(createdSort).cursor[Simul]().collect[List]() + def allCreated: Fu[List[Simul]] = + simulColl.find(createdSelect).sort(createdSort).list[Simul]() def allCreatedFeaturable: Fu[List[Simul]] = simulColl.find( - createdSelect ++ BSONDocument( - "createdAt" -> BSONDocument("$gte" -> DateTime.now.minusMinutes(15)), - "hostRating" -> BSONDocument("$gte" -> 1700) + createdSelect ++ $doc( + "createdAt" -> $doc("$gte" -> DateTime.now.minusMinutes(15)), + "hostRating" -> $doc("$gte" -> 1700) ) - ).sort(createdSort).cursor[Simul]().collect[List]() + ).sort(createdSort).list[Simul]() def allStarted: Fu[List[Simul]] = simulColl.find( startedSelect - ).sort(createdSort).cursor[Simul]().collect[List]() + ).sort(createdSort).list[Simul]() def allFinished(max: Int): Fu[List[Simul]] = simulColl.find( finishedSelect - ).sort(createdSort).cursor[Simul]().collect[List](max) + ).sort(createdSort).list[Simul](max) def allNotFinished = - simulColl.find( - BSONDocument("status" -> BSONDocument("$ne" -> SimulStatus.Finished.id)) - ).cursor[Simul]().collect[List]() + simulColl.find($doc("status" $ne SimulStatus.Finished.id)).list[Simul]() def create(simul: Simul): Funit = simulColl insert simul void def update(simul: Simul) = - simulColl.update(BSONDocument("_id" -> simul.id), simul).void + simulColl.update($id(simul.id), simul).void def remove(simul: Simul) = - simulColl.remove(BSONDocument("_id" -> simul.id)).void + simulColl.remove($id(simul.id)).void def setHostGameId(simul: Simul, gameId: String) = simulColl.update( - BSONDocument("_id" -> simul.id), - BSONDocument("$set" -> BSONDocument("hostGameId" -> gameId)) + $id(simul.id), + $set("hostGameId" -> gameId) ).void def setHostSeenNow(simul: Simul) = simulColl.update( - BSONDocument("_id" -> simul.id), - BSONDocument("$set" -> BSONDocument("hostSeenAt" -> DateTime.now)) + $id(simul.id), + $set("hostSeenAt" -> DateTime.now) ).void def cleanup = simulColl.remove( - createdSelect ++ BSONDocument( - "createdAt" -> BSONDocument("$lt" -> (DateTime.now minusMinutes 60)))) + createdSelect ++ $doc( + "createdAt" -> $doc("$lt" -> (DateTime.now minusMinutes 60)))) } diff --git a/modules/simul/src/main/Socket.scala b/modules/simul/src/main/Socket.scala index 95ebb592cd..71376f8053 100644 --- a/modules/simul/src/main/Socket.scala +++ b/modules/simul/src/main/Socket.scala @@ -1,7 +1,6 @@ package lila.simul import akka.actor._ -import play.api.libs.iteratee._ import play.api.libs.json._ import scala.concurrent.duration._ @@ -9,7 +8,7 @@ import actorApi._ import lila.common.LightUser import lila.hub.actorApi.round.MoveEvent import lila.hub.TimeBomb -import lila.socket.actorApi.{ Connected => _, _ } +import lila.socket.actorApi._ import lila.socket.{ SocketActor, History, Historical } private[simul] final class Socket( @@ -78,12 +77,9 @@ private[simul] final class Socket( case Socket.GetUserIds => sender ! userIds - case Join(uid, user) => - val (enumerator, channel) = Concurrent.broadcast[JsValue] - val member = Member(channel, user) + case AddMember(uid, member) => addMember(uid, member) notifyCrowd - sender ! Connected(enumerator, member) case Quit(uid) => quit(uid) diff --git a/modules/simul/src/main/SocketHandler.scala b/modules/simul/src/main/SocketHandler.scala index e88d0e157c..b3dc5455d1 100644 --- a/modules/simul/src/main/SocketHandler.scala +++ b/modules/simul/src/main/SocketHandler.scala @@ -1,16 +1,12 @@ package lila.simul -import scala.concurrent.duration._ - import akka.actor._ import akka.pattern.ask import actorApi._ import lila.common.PimpedJson._ import lila.hub.actorApi.map._ -import lila.security.Flood -import akka.actor.ActorSelection -import lila.socket.actorApi.{ Connected => _, _ } +import lila.socket.actorApi._ import lila.socket.Handler import lila.user.User import makeTimeout.short @@ -19,35 +15,26 @@ private[simul] final class SocketHandler( hub: lila.hub.Env, socketHub: ActorRef, chat: ActorSelection, - flood: Flood, - exists: Simul.ID => Fu[Boolean]) { + exists: Simul.ID => Fu[Boolean])(implicit val system: ActorSystem) { def join( simId: String, uid: String, - user: Option[User]): Fu[Option[JsSocketHandler]] = - exists(simId) flatMap { - _ ?? { - for { - socket ← socketHub ? Get(simId) mapTo manifest[ActorRef] - join = Join(uid = uid, user = user) - handler ← Handler(hub, socket, uid, join, user map (_.id)) { - case Connected(enum, member) => - (controller(socket, simId, uid, member), enum, member) + user: Option[User]): Fu[Option[JsFlow]] = exists(simId) flatMap { + _ ?? { + socketHub ? Get(simId) mapTo manifest[ActorRef] map { socket => + Handler.actorRef { out => + val member = Member(out, user) + socket ! AddMember(uid, member) + Handler.props(hub, socket, member, uid, user.map(_.id)) { + case ("p", o) => o int "v" foreach { v => socket ! PingVersion(uid, v) } + case ("talk", o) => o str "d" foreach { text => + member.userId foreach { userId => + chat ! lila.chat.actorApi.UserTalk(simId, userId, text, socket) + } + } } - } yield handler.some - } - } - - private def controller( - socket: ActorRef, - simId: String, - uid: String, - member: Member): Handler.Controller = { - case ("p", o) => o int "v" foreach { v => socket ! PingVersion(uid, v) } - case ("talk", o) => o str "d" foreach { text => - member.userId foreach { userId => - chat ! lila.chat.actorApi.UserTalk(simId, userId, text, socket) + }.some } } } diff --git a/modules/simul/src/main/actorApi.scala b/modules/simul/src/main/actorApi.scala index f4c646c03c..aeeb4706df 100644 --- a/modules/simul/src/main/actorApi.scala +++ b/modules/simul/src/main/actorApi.scala @@ -1,34 +1,33 @@ package lila.simul package actorApi +import akka.actor.ActorRef + +import lila.game.Game import lila.socket.SocketMember import lila.user.User -import lila.game.Game private[simul] case class Member( - channel: JsChannel, + out: ActorRef, userId: Option[String], troll: Boolean) extends SocketMember private[simul] object Member { - def apply(channel: JsChannel, user: Option[User]): Member = Member( - channel = channel, + def apply(out: ActorRef, user: Option[User]): Member = Member( + out = out, userId = user map (_.id), troll = user.??(_.troll)) } +private[simul] case class AddMember(uid: String, member: Member) private[simul] case class Messadata(trollish: Boolean = false) -private[simul] case class Join( - uid: String, - user: Option[User]) private[simul] case class Talk(tourId: String, u: String, t: String, troll: Boolean) private[simul] case class StartGame(game: Game, hostId: String) private[simul] case class StartSimul(firstGame: Game, hostId: String) private[simul] case class HostIsOn(gameId: String) private[simul] case object Reload private[simul] case object Aborted -private[simul] case class Connected(enumerator: JsEnumerator, member: Member) private[simul] case object NotifyCrowd diff --git a/modules/site/src/main/Env.scala b/modules/site/src/main/Env.scala index bff39ae737..f83f35f192 100644 --- a/modules/site/src/main/Env.scala +++ b/modules/site/src/main/Env.scala @@ -17,7 +17,7 @@ final class Env( private val socket = system.actorOf( Props(new Socket(timeout = SocketUidTtl)), name = SocketName) - lazy val socketHandler = new SocketHandler(socket, hub) + lazy val socketHandler = new SocketHandler(socket, hub)(system) } object Env { diff --git a/modules/site/src/main/Socket.scala b/modules/site/src/main/Socket.scala index 69e7fece13..8163be4188 100644 --- a/modules/site/src/main/Socket.scala +++ b/modules/site/src/main/Socket.scala @@ -16,16 +16,11 @@ private[site] final class Socket(timeout: Duration) extends SocketActor[Member]( def receiveSpecific = { - case Join(uid, username, tags) => { - val (enumerator, channel) = Concurrent.broadcast[JsValue] - val member = Member(channel, username, tags) - addMember(uid, member) - sender ! Connected(enumerator, member) - } + case AddMember(uid, member) => addMember(uid, member) case SendToFlag(flag, message) => members.values filter (_ hasFlag flag) foreach { - _.channel push message + _ push message } } } diff --git a/modules/site/src/main/SocketHandler.scala b/modules/site/src/main/SocketHandler.scala index 5ce078c98a..40dd23b241 100644 --- a/modules/site/src/main/SocketHandler.scala +++ b/modules/site/src/main/SocketHandler.scala @@ -1,26 +1,19 @@ package lila.site -import scala.concurrent.duration._ - import akka.actor._ import play.api.libs.json._ import actorApi._ -import lila.common.PimpedJson._ import lila.socket._ -import lila.socket.actorApi.StartWatching private[site] final class SocketHandler( socket: ActorRef, - hub: lila.hub.Env) { - - def apply( - uid: String, - userId: Option[String], - flag: Option[String]): Fu[JsSocketHandler] = { + hub: lila.hub.Env)(implicit val system: ActorSystem) { - Handler(hub, socket, uid, Join(uid, userId, flag), userId) { - case Connected(enum, member) => (Handler.emptyController, enum, member) + def apply(uid: String, userId: Option[String], flag: Option[String]): JsFlow = + Handler.actorRef { out => + val member = Member(out, userId, flag) + socket ! AddMember(uid, member) + Handler.props(hub, socket, member, uid, userId)(Handler.emptyController) } - } } diff --git a/modules/site/src/main/actorApi.scala b/modules/site/src/main/actorApi.scala index 7b1e0c0625..1d5e29710e 100644 --- a/modules/site/src/main/actorApi.scala +++ b/modules/site/src/main/actorApi.scala @@ -6,7 +6,7 @@ import play.api.libs.json._ import lila.socket.SocketMember case class Member( - channel: JsChannel, + out: akka.actor.ActorRef, userId: Option[String], flag: Option[String]) extends SocketMember { @@ -14,6 +14,4 @@ case class Member( def hasFlag(f: String) = flag ?? (f ==) } - -case class Join(uid: String, userId: Option[String], flag: Option[String]) -private[site] case class Connected(enumerator: JsEnumerator, member: Member) +case class AddMember(uid: String, member: Member) diff --git a/modules/socket/src/main/Channel.scala b/modules/socket/src/main/Channel.scala index 67c52c7708..af059679dd 100644 --- a/modules/socket/src/main/Channel.scala +++ b/modules/socket/src/main/Channel.scala @@ -2,7 +2,7 @@ package lila.socket import actorApi.SocketLeave import akka.actor._ -import play.api.libs.json.JsValue +import play.api.libs.json.JsObject final class Channel extends Actor { @@ -36,5 +36,5 @@ object Channel { case class Sub(member: SocketMember) case class UnSub(member: SocketMember) - case class Publish(msg: JsValue) + case class Publish(msg: JsObject) } diff --git a/modules/socket/src/main/Handler.scala b/modules/socket/src/main/Handler.scala index ddae564bc9..7bd8cc615b 100644 --- a/modules/socket/src/main/Handler.scala +++ b/modules/socket/src/main/Handler.scala @@ -1,99 +1,100 @@ package lila.socket -import akka.actor.ActorRef +import akka.actor.{ ActorRef, Props, ActorSystem } import akka.pattern.{ ask, pipe } -import play.api.libs.iteratee.{ Iteratee, Enumerator } +import akka.stream._ +import akka.stream.scaladsl._ import play.api.libs.json._ +import play.api.libs.streams.ActorFlow import scala.concurrent.duration._ import actorApi._ import lila.common.PimpedJson._ import lila.hub.actorApi.relation.ReloadOnlineFriends +import lila.socket.Socket.makeMessage import makeTimeout.large -import Step.openingWriter object Handler { type Controller = PartialFunction[(String, JsObject), Unit] - type Connecter = PartialFunction[Any, (Controller, JsEnumerator, SocketMember)] val emptyController: Controller = PartialFunction.empty lazy val AnaRateLimit = new lila.memo.RateLimit(90, 60 seconds, "socket analysis move") - def apply( + def actorRef(withOut: ActorRef => Props)(implicit system: ActorSystem): JsFlow = + ActorFlow.actorRef[JsObject, JsObject](withOut) + + def props( hub: lila.hub.Env, socket: ActorRef, + member: SocketMember, uid: String, - join: Any, - userId: Option[String])(connecter: Connecter): Fu[JsSocketHandler] = { - - def baseController(member: SocketMember): Controller = { - case ("p", _) => socket ! Ping(uid) - case ("following_onlines", _) => userId foreach { u => - hub.actor.relation ! ReloadOnlineFriends(u) - } - case ("startWatching", o) => o str "d" foreach { ids => - hub.actor.moveBroadcast ! StartWatching(uid, member, ids.split(' ').toSet) - } - case ("moveLat", o) => hub.channel.roundMoveTime ! (~(o boolean "d")).fold( - Channel.Sub(member), - Channel.UnSub(member)) - case ("anaMove", o) => AnaRateLimit(uid) { - AnaMove parse o foreach { anaMove => - anaMove.step match { - case scalaz.Success(step) => - member push lila.socket.Socket.makeMessage("step", Json.obj( - "step" -> step.toJson, - "path" -> anaMove.path - )) - case scalaz.Failure(err) => - member push lila.socket.Socket.makeMessage("stepFailure", err.toString) - } - } + userId: Option[String])(controller: Controller): Props = { + val control = controller orElse baseController(hub, socket, member, uid, userId) + lila.socket.SocketMemberActor.props(in => + in str "t" foreach { t => + control.lift(t -> in) } - case ("anaDrop", o) => AnaRateLimit(uid) { - AnaDrop parse o foreach { anaDrop => - anaDrop.step match { - case scalaz.Success(step) => - member push lila.socket.Socket.makeMessage("step", Json.obj( - "step" -> step.toJson, - "path" -> anaDrop.path - )) - case scalaz.Failure(err) => - member push lila.socket.Socket.makeMessage("stepFailure", err.toString) - } - } - } - case ("anaDests", o) => AnaRateLimit(uid) { - AnaDests parse o match { - case Some(req) => - member push lila.socket.Socket.makeMessage("dests", Json.obj( - "dests" -> req.dests, - "path" -> req.path - ) ++ req.opening.?? { o => - Json.obj("opening" -> o) - }) - case None => - member push lila.socket.Socket.makeMessage("destsFailure", "Bad dests request") + ) + } + + def baseController( + hub: lila.hub.Env, + socket: ActorRef, + member: SocketMember, + uid: String, + userId: Option[String]): Controller = { + case ("p", _) => socket ! Ping(uid) + case ("following_onlines", _) => userId foreach { u => + hub.actor.relation ! ReloadOnlineFriends(u) + } + case ("startWatching", o) => o str "d" foreach { ids => + hub.actor.moveBroadcast ! StartWatching(uid, member, ids.split(' ').toSet) + } + case ("moveLat", o) => hub.channel.roundMoveTime ! (~(o boolean "d")).fold( + Channel.Sub(member), + Channel.UnSub(member)) + case ("anaMove", o) => AnaRateLimit(uid) { + AnaMove parse o foreach { anaMove => + anaMove.step match { + case scalaz.Success(step) => + member push makeMessage("step", Json.obj( + "step" -> step.toJson, + "path" -> anaMove.path + )) + case scalaz.Failure(err) => + member push makeMessage("stepFailure", err.toString) } } - case _ => // logwarn("Unhandled msg: " + msg) } - - def iteratee(controller: Controller, member: SocketMember): JsIteratee = { - val control = controller orElse baseController(member) - Iteratee.foreach[JsValue](jsv => - jsv.asOpt[JsObject] foreach { obj => - obj str "t" foreach { t => - control.lift(t -> obj) - } + case ("anaDrop", o) => AnaRateLimit(uid) { + AnaDrop parse o foreach { anaDrop => + anaDrop.step match { + case scalaz.Success(step) => + member push makeMessage("step", Json.obj( + "step" -> step.toJson, + "path" -> anaDrop.path + )) + case scalaz.Failure(err) => + member push makeMessage("stepFailure", err.toString) } - ).map(_ => socket ! Quit(uid)) + } } - - socket ? join map connecter map { - case (controller, enum, member) => iteratee(controller, member) -> enum + case ("anaDests", o) => AnaRateLimit(uid) { + import Step.openingWriter + AnaDests parse o match { + case Some(req) => + member push makeMessage("dests", Json.obj( + "dests" -> req.dests, + "path" -> req.path + ) ++ req.opening.?? { o => + Json.obj("opening" -> o) + }) + case None => + member push makeMessage("destsFailure", "Bad dests request") + } } + case _ => // logwarn("Unhandled msg: " + msg) } } diff --git a/modules/socket/src/main/SocketMember.scala b/modules/socket/src/main/SocketMember.scala index 9d544729a4..b15d73c439 100644 --- a/modules/socket/src/main/SocketMember.scala +++ b/modules/socket/src/main/SocketMember.scala @@ -1,10 +1,11 @@ package lila.socket -import play.api.libs.json.JsValue +import akka.actor.{ ActorRef, PoisonPill } +import play.api.libs.json.JsObject trait SocketMember extends Ordered[SocketMember] { - protected val channel: JsChannel + protected val out: ActorRef val userId: Option[String] val troll: Boolean @@ -12,21 +13,17 @@ trait SocketMember extends Ordered[SocketMember] { def compare(other: SocketMember) = ~userId compare ~other.userId - def push(msg: JsValue) { - channel push msg - } + def push(msg: JsObject) = out ! msg - def end { - channel.end - } + def end = out ! PoisonPill } object SocketMember { - def apply(c: JsChannel): SocketMember = apply(c, none, false) + def apply(o: ActorRef): SocketMember = apply(o, none, false) - def apply(c: JsChannel, uid: Option[String], tr: Boolean): SocketMember = new SocketMember { - val channel = c + def apply(o: ActorRef, uid: Option[String], tr: Boolean): SocketMember = new SocketMember { + val out = o val userId = uid val troll = tr } diff --git a/modules/socket/src/main/SocketMemberActor.scala b/modules/socket/src/main/SocketMemberActor.scala new file mode 100644 index 0000000000..35ba55fe6a --- /dev/null +++ b/modules/socket/src/main/SocketMemberActor.scala @@ -0,0 +1,22 @@ +package lila.socket + +import akka.actor._ +import play.api.libs.json.JsObject + +// implements ActorFlow.actorRef out actor +final class SocketMemberActor( + handler: JsObject => Unit) extends Actor { + + def receive = { + + case msg: JsObject => handler(msg) + + case m => lila.log("socket").warn(s"SocketMemberActor received $m") + } +} + +object SocketMemberActor { + + def props(handler: JsObject => Unit) = + Props(new SocketMemberActor(handler)) +} diff --git a/modules/socket/src/main/WithSocket.scala b/modules/socket/src/main/WithSocket.scala index 6467c900b3..a3fa4d177f 100644 --- a/modules/socket/src/main/WithSocket.scala +++ b/modules/socket/src/main/WithSocket.scala @@ -1,14 +1,10 @@ package lila.socket -import ornicar.scalalib.Zero -import play.api.libs.iteratee.{ Iteratee, Enumerator } -import play.api.libs.json._ - +import akka.stream.scaladsl.Flow +import play.api.libs.json.JsObject +import play.api.libs.streams.ActorFlow trait WithSocket { - type JsChannel = play.api.libs.iteratee.Concurrent.Channel[JsValue] - type JsEnumerator = Enumerator[JsValue] - type JsIteratee = Iteratee[JsValue, _] - type JsSocketHandler = (JsIteratee, JsEnumerator) + type JsFlow = Flow[JsObject, JsObject, _] } diff --git a/modules/socket/src/main/actorApi.scala b/modules/socket/src/main/actorApi.scala index 741740805d..6c011a2410 100644 --- a/modules/socket/src/main/actorApi.scala +++ b/modules/socket/src/main/actorApi.scala @@ -2,11 +2,7 @@ package lila.socket package actorApi import play.api.libs.json.JsObject -import akka.actor.ActorRef -case class Connected[M <: SocketMember]( - enumerator: JsEnumerator, - member: M) case class Sync(uid: String, friends: List[String]) case class Ping(uid: String) case class PingVersion(uid: String, version: Int) diff --git a/modules/team/src/main/BSONHandlers.scala b/modules/team/src/main/BSONHandlers.scala new file mode 100644 index 0000000000..99673e23ca --- /dev/null +++ b/modules/team/src/main/BSONHandlers.scala @@ -0,0 +1,9 @@ +package lila.team + +private object BSONHandlers { + + import lila.db.dsl.BSONJodaDateTimeHandler + implicit val TeamBSONHandler = reactivemongo.bson.Macros.handler[Team] + implicit val RequestBSONHandler = reactivemongo.bson.Macros.handler[Request] + implicit val MemberBSONHandler = reactivemongo.bson.Macros.handler[Member] +} diff --git a/modules/team/src/main/Cli.scala b/modules/team/src/main/Cli.scala index b4be03c0f4..74193568b1 100644 --- a/modules/team/src/main/Cli.scala +++ b/modules/team/src/main/Cli.scala @@ -1,10 +1,11 @@ package lila.team -import lila.db.api.$find +import lila.db.dsl._ import lila.user.{ User, UserRepo } -import tube.teamTube -private[team] final class Cli(api: TeamApi) extends lila.common.Cli { +private[team] final class Cli(api: TeamApi, coll: Colls) extends lila.common.Cli { + + import BSONHandlers._ def process = { @@ -21,12 +22,12 @@ private[team] final class Cli(api: TeamApi) extends lila.common.Cli { } private def perform(teamId: String)(op: Team => Funit): Fu[String] = - $find byId teamId flatMap { + coll.team.byId[Team](teamId) flatMap { _.fold(fufail[String]("Team not found")) { u => op(u) inject "Success" } } private def perform(teamId: String, userIds: List[String])(op: (Team, String) => Funit): Fu[String] = - $find byId teamId flatMap { + coll.team.byId[Team](teamId) flatMap { _.fold(fufail[String]("Team not found")) { team => UserRepo nameds userIds flatMap { users => users.map(user => { diff --git a/modules/team/src/main/Colls.scala b/modules/team/src/main/Colls.scala new file mode 100644 index 0000000000..daca0a498a --- /dev/null +++ b/modules/team/src/main/Colls.scala @@ -0,0 +1,8 @@ +package lila.team + +import lila.db.dsl.Coll + +private final class Colls( + val team: Coll, + val request: Coll, + val member: Coll) diff --git a/modules/team/src/main/DataForm.scala b/modules/team/src/main/DataForm.scala index ce9fd1ebf4..37acae60cf 100644 --- a/modules/team/src/main/DataForm.scala +++ b/modules/team/src/main/DataForm.scala @@ -4,10 +4,11 @@ import play.api.data._ import play.api.data.Forms._ import play.api.data.validation.Constraints._ -import lila.db.api.{ $count, $select } -import tube.teamTube +import lila.db.dsl._ -private[team] final class DataForm(val captcher: akka.actor.ActorSelection) extends lila.hub.CaptchedForm { +private[team] final class DataForm( + teamColl: Coll, + val captcher: akka.actor.ActorSelection) extends lila.hub.CaptchedForm { import lila.common.Form._ @@ -61,7 +62,7 @@ private[team] final class DataForm(val captcher: akka.actor.ActorSelection) exte def createWithCaptcha = withCaptcha(create) private def teamExists(setup: TeamSetup) = - $count.exists[Team]($select(Team nameToId setup.trim.name)) + teamColl.exists($id(Team nameToId setup.trim.name)) } private[team] case class TeamSetup( diff --git a/modules/team/src/main/Env.scala b/modules/team/src/main/Env.scala index 6b98e75dbb..7e02648959 100644 --- a/modules/team/src/main/Env.scala +++ b/modules/team/src/main/Env.scala @@ -16,9 +16,15 @@ final class Env(config: Config, hub: lila.hub.Env, db: lila.db.Env) { } import settings._ - lazy val forms = new DataForm(hub.actor.captcher) + private[team] lazy val colls = new Colls( + team = db(CollectionTeam), + request = db(CollectionRequest), + member = db(CollectionMember)) + + lazy val forms = new DataForm(colls.team, hub.actor.captcher) lazy val api = new TeamApi( + coll = colls, cached = cached, notifier = notifier, forum = hub.actor.forum, @@ -26,17 +32,14 @@ final class Env(config: Config, hub: lila.hub.Env, db: lila.db.Env) { timeline = hub.actor.timeline) lazy val paginator = new PaginatorBuilder( + coll = colls, maxPerPage = PaginatorMaxPerPage, maxUserPerPage = PaginatorMaxUserPerPage) - lazy val cli = new Cli(api) + lazy val cli = new Cli(api, colls) lazy val cached = new Cached - private[team] lazy val teamColl = db(CollectionTeam) - private[team] lazy val requestColl = db(CollectionRequest) - private[team] lazy val memberColl = db(CollectionMember) - private lazy val notifier = new Notifier( sender = NotifierSender, messenger = hub.actor.messenger, diff --git a/modules/team/src/main/Member.scala b/modules/team/src/main/Member.scala index 8f1c8a96a8..c376bb9e21 100644 --- a/modules/team/src/main/Member.scala +++ b/modules/team/src/main/Member.scala @@ -5,13 +5,15 @@ import org.joda.time.DateTime import lila.user.User private[team] case class Member( - id: String, + _id: String, team: String, user: String, date: DateTime) { def is(userId: String): Boolean = user == userId def is(user: User): Boolean = is(user.id) + + def id = _id } private[team] object Member { @@ -19,18 +21,10 @@ private[team] object Member { def makeId(team: String, user: String) = user + "@" + team def make(team: String, user: String): Member = new Member( - id = makeId(team, user), - user = user, - team = team, + _id = makeId(team, user), + user = user, + team = team, date = DateTime.now) - - import lila.db.JsTube, JsTube.Helpers._ - import play.api.libs.json._ - - private[team] lazy val tube = JsTube( - (__.json update readDate('date)) andThen Json.reads[Member], - Json.writes[Member] andThen (__.json update writeDate('date)) - ) } case class MemberWithUser(member: Member, user: User) { diff --git a/modules/team/src/main/MemberRepo.scala b/modules/team/src/main/MemberRepo.scala index 41f560d851..a8ec7b30df 100644 --- a/modules/team/src/main/MemberRepo.scala +++ b/modules/team/src/main/MemberRepo.scala @@ -1,41 +1,44 @@ package lila.team -import play.api.libs.json.Json import reactivemongo.api._ import reactivemongo.bson._ -import lila.db.api._ -import tube.memberTube +import lila.db.dsl._ object MemberRepo { + // dirty + private val coll = Env.current.colls.member + + import BSONHandlers._ + type ID = String def userIdsByTeam(teamId: ID): Fu[Set[ID]] = - memberTube.coll.distinct("user", BSONDocument("team" -> teamId).some) map lila.db.BSON.asStringSet + coll.distinct("user", $doc("team" -> teamId).some) map lila.db.BSON.asStringSet def teamIdsByUser(userId: ID): Fu[Set[ID]] = - memberTube.coll.distinct("team", BSONDocument("user" -> userId).some) map lila.db.BSON.asStringSet + coll.distinct("team", $doc("user" -> userId).some) map lila.db.BSON.asStringSet def removeByteam(teamId: ID): Funit = - $remove(teamQuery(teamId)) + coll.remove(teamQuery(teamId)).void def removeByUser(userId: ID): Funit = - $remove(userQuery(userId)) + coll.remove(userQuery(userId)).void def exists(teamId: ID, userId: ID): Fu[Boolean] = - $count.exists(selectId(teamId, userId)) + coll.exists(selectId(teamId, userId)) def add(teamId: String, userId: String): Funit = - $insert(Member.make(team = teamId, user = userId)) + coll.insert(Member.make(team = teamId, user = userId)).void def remove(teamId: String, userId: String): Funit = - $remove(selectId(teamId, userId)) + coll.remove(selectId(teamId, userId)).void def countByTeam(teamId: String): Fu[Int] = - $count(teamQuery(teamId)) + coll.countSel(teamQuery(teamId)) - def selectId(teamId: ID, userId: ID) = $select(Member.makeId(teamId, userId)) - def teamQuery(teamId: ID) = Json.obj("team" -> teamId) - def userQuery(userId: ID) = Json.obj("user" -> userId) + def selectId(teamId: ID, userId: ID) = $id(Member.makeId(teamId, userId)) + def teamQuery(teamId: ID) = $doc("team" -> teamId) + def userQuery(userId: ID) = $doc("user" -> userId) } diff --git a/modules/team/src/main/PaginatorBuilder.scala b/modules/team/src/main/PaginatorBuilder.scala index 35bb390e23..5e0fbb182c 100644 --- a/modules/team/src/main/PaginatorBuilder.scala +++ b/modules/team/src/main/PaginatorBuilder.scala @@ -1,21 +1,23 @@ package lila.team import lila.common.paginator._ -import lila.db.api._ -import lila.db.Implicits._ +import lila.db.dsl._ import lila.db.paginator._ -import lila.user.tube.userTube -import lila.user.User -import tube._ +import lila.user.{ User, UserRepo } private[team] final class PaginatorBuilder( + coll: Colls, maxPerPage: Int, maxUserPerPage: Int) { + import BSONHandlers._ + def popularTeams(page: Int): Fu[Paginator[Team]] = Paginator( - adapter = new Adapter[Team]( + adapter = new Adapter( + collection = coll.team, selector = TeamRepo.enabledQuery, - sort = Seq(TeamRepo.sortPopular)), + projection = $empty, + sort = TeamRepo.sortPopular), page, maxPerPage) @@ -29,8 +31,9 @@ private[team] final class PaginatorBuilder( val nbResults = fuccess(team.nbMembers) def slice(offset: Int, length: Int): Fu[Seq[MemberWithUser]] = for { - members ← $find[Member]($query[Member](selector) sort sorting skip offset, length) - users ← $find.byOrderedIds[User](members.map(_.user)) + members ← coll.member.find(selector) + .sort(sorting).skip(offset).cursor[Member]().gather[List](length) + users ← UserRepo byOrderedIds members.map(_.user) } yield members zip users map { case (member, user) => MemberWithUser(member, user) } diff --git a/modules/team/src/main/Request.scala b/modules/team/src/main/Request.scala index 39958f1048..e893d909dd 100644 --- a/modules/team/src/main/Request.scala +++ b/modules/team/src/main/Request.scala @@ -5,11 +5,13 @@ import org.joda.time.DateTime import lila.user.User case class Request( - id: String, + _id: String, team: String, user: String, message: String, date: DateTime) { + + def id = _id } object Request { @@ -17,19 +19,11 @@ object Request { def makeId(team: String, user: String) = user + "@" + team def make(team: String, user: String, message: String): Request = new Request( - id = makeId(team, user), + _id = makeId(team, user), user = user, team = team, message = message.trim, date = DateTime.now) - - import lila.db.JsTube, JsTube.Helpers._ - import play.api.libs.json._ - - private[team] lazy val tube = JsTube( - (__.json update readDate('date)) andThen Json.reads[Request], - Json.writes[Request] andThen (__.json update writeDate('date)) - ) } case class RequestWithUser(request: Request, user: User) { diff --git a/modules/team/src/main/RequestRepo.scala b/modules/team/src/main/RequestRepo.scala index a60025a584..6dcf5715c0 100644 --- a/modules/team/src/main/RequestRepo.scala +++ b/modules/team/src/main/RequestRepo.scala @@ -1,34 +1,37 @@ package lila.team -import play.api.libs.json.Json import reactivemongo.api._ -import lila.db.api._ -import tube.requestTube +import lila.db.dsl._ object RequestRepo { + // dirty + private val coll = Env.current.colls.request + + import BSONHandlers._ + type ID = String - def exists(teamId: ID, userId: ID): Fu[Boolean] = - $count.exists(selectId(teamId, userId)) + def exists(teamId: ID, userId: ID): Fu[Boolean] = + coll.exists(selectId(teamId, userId)) - def find(teamId: ID, userId: ID): Fu[Option[Request]] = - $find.one(selectId(teamId, userId)) + def find(teamId: ID, userId: ID): Fu[Option[Request]] = + coll.uno[Request](selectId(teamId, userId)) - def countByTeam(teamId: ID): Fu[Int] = - $count(teamQuery(teamId)) + def countByTeam(teamId: ID): Fu[Int] = + coll.countSel(teamQuery(teamId)) - def countByTeams(teamIds: List[ID]): Fu[Int] = - $count(teamsQuery(teamIds)) + def countByTeams(teamIds: List[ID]): Fu[Int] = + coll.countSel(teamsQuery(teamIds)) - def findByTeam(teamId: ID): Fu[List[Request]] = - $find(teamQuery(teamId)) + def findByTeam(teamId: ID): Fu[List[Request]] = + coll.list[Request](teamQuery(teamId)) - def findByTeams(teamIds: List[ID]): Fu[List[Request]] = - $find(teamsQuery(teamIds)) + def findByTeams(teamIds: List[ID]): Fu[List[Request]] = + coll.list[Request](teamsQuery(teamIds)) - def selectId(teamId: ID, userId: ID) = $select(Request.makeId(teamId, userId)) - def teamQuery(teamId: ID) = Json.obj("team" -> teamId) - def teamsQuery(teamIds: List[ID]) = Json.obj("team" -> $in(teamIds)) + def selectId(teamId: ID, userId: ID) = $id(Request.makeId(teamId, userId)) + def teamQuery(teamId: ID) = $doc("team" -> teamId) + def teamsQuery(teamIds: List[ID]) = $doc("team".$in(teamIds:_*)) } diff --git a/modules/team/src/main/Team.scala b/modules/team/src/main/Team.scala index d566767a15..4edfbaf5c1 100644 --- a/modules/team/src/main/Team.scala +++ b/modules/team/src/main/Team.scala @@ -6,7 +6,7 @@ import ornicar.scalalib.Random import lila.user.User case class Team( - id: String, // also the url slug + _id: String, // also the url slug name: String, location: Option[String], description: String, @@ -16,6 +16,8 @@ case class Team( createdAt: DateTime, createdBy: String) { + def id = _id + def slug = id def disabled = !enabled @@ -31,7 +33,7 @@ object Team { description: String, open: Boolean, createdBy: User): Team = new Team( - id = nameToId(name), + _id = nameToId(name), name = name, location = location, description = description, @@ -45,12 +47,4 @@ object Team { // if most chars are not latin, go for random slug (slug.size > (name.size / 2)).fold(slug, Random nextStringUppercase 8) } - - import lila.db.JsTube, JsTube.Helpers._ - import play.api.libs.json._ - - private[team] lazy val tube = JsTube( - (__.json update readDate('createdAt)) andThen Json.reads[Team], - Json.writes[Team] andThen (__.json update writeDate('createdAt)) - ) } diff --git a/modules/team/src/main/TeamApi.scala b/modules/team/src/main/TeamApi.scala index f69822406e..9f6b2c5bf2 100644 --- a/modules/team/src/main/TeamApi.scala +++ b/modules/team/src/main/TeamApi.scala @@ -2,26 +2,27 @@ package lila.team import actorApi._ import akka.actor.ActorSelection -import lila.db.api._ +import lila.db.dsl._ import lila.hub.actorApi.forum.MakeTeam import lila.hub.actorApi.timeline.{ Propagate, TeamJoin, TeamCreate } -import lila.user.tube.userTube -import lila.user.{ User, UserContext } +import lila.user.{ User, UserRepo, UserContext } import org.joda.time.Period -import tube._ final class TeamApi( + coll: Colls, cached: Cached, notifier: Notifier, forum: ActorSelection, indexer: ActorSelection, timeline: ActorSelection) { + import BSONHandlers._ + val creationPeriod = Period weeks 1 - def team(id: String) = $find.byId[Team](id) + def team(id: String) = coll.team.byId[Team](id) - def request(id: String) = $find.byId[Request](id) + def request(id: String) = coll.request.byId[Request](id) def create(setup: TeamSetup, me: User): Option[Fu[Team]] = me.canTeam option { val s = setup.trim @@ -31,7 +32,7 @@ final class TeamApi( description = s.description, open = s.isOpen, createdBy = me) - $insert(team) >> + coll.team.insert(team) >> MemberRepo.add(team.id, me.id) >>- { (cached.teamIdsCache invalidate me.id) (forum ! MakeTeam(team.id, team.name)) @@ -46,10 +47,12 @@ final class TeamApi( team.copy( location = e.location, description = e.description, - open = e.isOpen) |> { team => $update(team) >>- (indexer ! InsertTeam(team)) } + open = e.isOpen) |> { team => + coll.team.update($id(team.id), team).void >>- (indexer ! InsertTeam(team)) + } } - def mine(me: User): Fu[List[Team]] = $find.byIds[Team](cached teamIds me.id) + def mine(me: User): Fu[List[Team]] = coll.team.byIds[Team](cached teamIds me.id) def hasTeams(me: User): Boolean = cached.teamIds(me.id).nonEmpty @@ -58,7 +61,7 @@ final class TeamApi( def requestsWithUsers(team: Team): Fu[List[RequestWithUser]] = for { requests ← RequestRepo findByTeam team.id - users ← $find.byOrderedIds[User](requests map (_.user)) + users ← UserRepo byOrderedIds requests.map(_.user) } yield requests zip users map { case (request, user) => RequestWithUser(request, user) } @@ -66,13 +69,13 @@ final class TeamApi( def requestsWithUsers(user: User): Fu[List[RequestWithUser]] = for { teamIds ← TeamRepo teamIdsByCreator user.id requests ← RequestRepo findByTeams teamIds - users ← $find.byOrderedIds[User](requests map (_.user)) + users ← UserRepo byOrderedIds requests.map(_.user) } yield requests zip users map { case (request, user) => RequestWithUser(request, user) } def join(teamId: String)(implicit ctx: UserContext): Fu[Option[Requesting]] = for { - teamOption ← $find.byId[Team](teamId) + teamOption ← coll.team.byId[Team](teamId) result ← ~(teamOption |@| ctx.me.filter(_.canTeam))({ case (team, user) if team.open => (doJoin(team, user.id) inject Joined(team).some): Fu[Option[Requesting]] @@ -82,7 +85,7 @@ final class TeamApi( } yield result def requestable(teamId: String, user: User): Fu[Option[Team]] = for { - teamOption ← $find.byId[Team](teamId) + teamOption ← coll.team.byId[Team](teamId) able ← teamOption.??(requestable(_, user)) } yield teamOption filter (_ => able) @@ -97,14 +100,14 @@ final class TeamApi( _ ?? { val request = Request.make(team = team.id, user = user.id, message = setup.message) val rwu = RequestWithUser(request, user) - $insert(request) >> (cached.nbRequests remove team.createdBy) + coll.request.insert(request).void >> (cached.nbRequests remove team.createdBy) } } def processRequest(team: Team, request: Request, accept: Boolean): Funit = for { - _ ← $remove(request) + _ ← coll.request.remove(request) _ ← cached.nbRequests remove team.createdBy - userOption ← $find.byId[User](request.user) + userOption ← UserRepo byId request.user _ ← userOption.filter(_ => accept).??(user => doJoin(team, user.id) >>- notifier.acceptRequest(team, request) ) @@ -119,7 +122,7 @@ final class TeamApi( } recover lila.db.recoverDuplicateKey(e => ()) def quit(teamId: String)(implicit ctx: UserContext): Fu[Option[Team]] = for { - teamOption ← $find.byId[Team](teamId) + teamOption ← coll.team.byId[Team](teamId) result ← ~(teamOption |@| ctx.me)({ case (team, user) => doQuit(team, user.id) inject team.some }) @@ -136,14 +139,14 @@ final class TeamApi( def kick(team: Team, userId: String): Funit = doQuit(team, userId) def enable(team: Team): Funit = - TeamRepo.enable(team) >>- (indexer ! InsertTeam(team)) + TeamRepo.enable(team).void >>- (indexer ! InsertTeam(team)) def disable(team: Team): Funit = - TeamRepo.disable(team) >>- (indexer ! RemoveTeam(team.id)) + TeamRepo.disable(team).void >>- (indexer ! RemoveTeam(team.id)) // delete for ever, with members but not forums def delete(team: Team): Funit = - $remove(team) >> + coll.team.remove($id(team.id)) >> MemberRepo.removeByteam(team.id) >>- (indexer ! RemoveTeam(team.id)) @@ -159,10 +162,13 @@ final class TeamApi( def nbRequests(teamId: String) = cached nbRequests teamId + import play.api.libs.iteratee._ def recomputeNbMembers = - $enumerate.over[Team]($query.all[Team]) { team => - MemberRepo.countByTeam(team.id) flatMap { nb => - $update.field[String, Team, Int](team.id, "nbMembers", nb) + coll.team.find($empty).cursor[Team]() + .enumerate(Int.MaxValue, stopOnError = true) |>>> + Iteratee.foldM[Team, Unit](()) { + case (_, team) => MemberRepo.countByTeam(team.id) flatMap { nb => + coll.team.updateField($id(team.id), "nbMembers", nb).void + } } - } } diff --git a/modules/team/src/main/TeamRepo.scala b/modules/team/src/main/TeamRepo.scala index 73f7571091..2cfa2c939b 100644 --- a/modules/team/src/main/TeamRepo.scala +++ b/modules/team/src/main/TeamRepo.scala @@ -1,50 +1,56 @@ package lila.team import org.joda.time.{ DateTime, Period } -import play.api.libs.json.Json -import play.modules.reactivemongo.json.ImplicitBSONHandlers.JsObjectWriter import reactivemongo.api._ import reactivemongo.bson._ -import lila.db.api._ -import lila.user.User -import tube.teamTube +import lila.db.dsl._ +import lila.user.{ User, UserRepo } object TeamRepo { + // dirty + private val coll = Env.current.colls.team + + import BSONHandlers._ + type ID = String + def byOrderedIds(ids: Seq[String]) = coll.byOrderedIds[Team](ids)(_.id) + + def cursor(selector: Bdoc) = coll.find(selector).cursor[Team]() + def owned(id: String, createdBy: String): Fu[Option[Team]] = - $find.one($select(id) ++ Json.obj("createdBy" -> createdBy)) + coll.uno[Team]($id(id) ++ $doc("createdBy" -> createdBy)) def teamIdsByCreator(userId: String): Fu[List[String]] = - teamTube.coll.distinct("_id", BSONDocument("createdBy" -> userId).some) map lila.db.BSON.asStrings + coll.distinct("_id", BSONDocument("createdBy" -> userId).some) map lila.db.BSON.asStrings def name(id: String): Fu[Option[String]] = - $primitive.one($select(id), "name")(_.asOpt[String]) + coll.primitiveOne[String]($id(id), "name") def userHasCreatedSince(userId: String, duration: Period): Fu[Boolean] = - $count.exists(Json.obj( - "createdAt" -> $gt($date(DateTime.now minus duration)), + coll.exists($doc( + "createdAt" $gt DateTime.now.minus(duration), "createdBy" -> userId )) def ownerOf(teamId: String): Fu[Option[String]] = - $primitive.one($select(teamId), "createdBy")(_.asOpt[String]) + coll.primitiveOne[String]($id(teamId), "createdBy") def incMembers(teamId: String, by: Int): Funit = - $update($select(teamId), $inc("nbMembers" -> by)) + coll.update($id(teamId), $inc("nbMembers" -> by)).void - def enable(team: Team) = $update.field(team.id, "enabled", true) + def enable(team: Team) = coll.updateField($id(team.id), "enabled", true) - def disable(team: Team) = $update.field(team.id, "enabled", false) + def disable(team: Team) = coll.updateField($id(team.id), "enabled", false) def addRequest(teamId: String, request: Request): Funit = - $update( - $select(teamId) ++ Json.obj("requests.user" -> $ne(request.user)), - $push("requests", request.user)) + coll.update( + $id(teamId) ++ $doc("requests.user" $ne request.user), + $push("requests", request.user)).void - val enabledQuery = Json.obj("enabled" -> true) + val enabledQuery = $doc("enabled" -> true) val sortPopular = $sort desc "nbMembers" } diff --git a/modules/team/src/main/package.scala b/modules/team/src/main/package.scala index 95584b460f..6d2b3c0891 100644 --- a/modules/team/src/main/package.scala +++ b/modules/team/src/main/package.scala @@ -2,14 +2,5 @@ package lila package object team extends PackageObject with WithPlay { - object tube { - - implicit lazy val teamTube = Team.tube inColl Env.current.teamColl - - private[team] implicit lazy val requestTube = Request.tube inColl Env.current.requestColl - - private[team] implicit lazy val memberTube = Member.tube inColl Env.current.memberColl - } - private[team] def logger = lila.log("team") } diff --git a/modules/teamSearch/src/main/Env.scala b/modules/teamSearch/src/main/Env.scala index fd07ac2650..0c47917bda 100644 --- a/modules/teamSearch/src/main/Env.scala +++ b/modules/teamSearch/src/main/Env.scala @@ -3,9 +3,8 @@ package lila.teamSearch import akka.actor._ import com.typesafe.config.Config -import lila.db.api.{ $find, $cursor } +import lila.db.dsl._ import lila.search._ -import lila.team.tube.teamTube final class Env( config: Config, @@ -18,7 +17,7 @@ final class Env( private val client = makeClient(Index(IndexName)) - val api = new TeamSearchApi(client, $find.byOrderedIds[lila.team.Team] _) + val api = new TeamSearchApi(client) def apply(text: String, page: Int) = paginatorBuilder(Query(text), page) diff --git a/modules/teamSearch/src/main/TeamSearchApi.scala b/modules/teamSearch/src/main/TeamSearchApi.scala index d7327236f6..9f7fe5becc 100644 --- a/modules/teamSearch/src/main/TeamSearchApi.scala +++ b/modules/teamSearch/src/main/TeamSearchApi.scala @@ -2,17 +2,16 @@ package lila.teamSearch import lila.search._ import lila.team.actorApi._ -import lila.team.Team +import lila.team.{ Team, TeamRepo } import play.api.libs.json._ +import play.api.libs.iteratee._ -final class TeamSearchApi( - client: ESClient, - fetcher: Seq[String] => Fu[List[Team]]) extends SearchReadApi[Team, Query] { +final class TeamSearchApi(client: ESClient) extends SearchReadApi[Team, Query] { def search(query: Query, from: From, size: Size) = client.search(query, from, size) flatMap { res => - fetcher(res.ids) + TeamRepo byOrderedIds res.ids } def count(query: Query) = client.count(query) map (_.count) @@ -28,11 +27,13 @@ final class TeamSearchApi( def reset = client match { case c: ESClientHttp => c.putMapping >> { lila.log("teamSearch").info(s"Index to ${c.index.name}") - import lila.db.api._ - import lila.team.tube.teamTube - $enumerate.bulk[Option[Team]]($query[Team](Json.obj("enabled" -> true)), 300) { teamOptions => - c.storeBulk(teamOptions.flatten map (t => Id(t.id) -> toDoc(t))) - } + import lila.db.dsl._ + TeamRepo.cursor($doc("enabled" -> true)) + .enumerateBulks(Int.MaxValue) |>>> + Iteratee.foldM[Iterator[Team], Unit](()) { + case (_, teams) => + c.storeBulk(teams.toList map (t => Id(t.id) -> toDoc(t))) + } } case _ => funit } diff --git a/modules/timeline/src/main/Entry.scala b/modules/timeline/src/main/Entry.scala index cc8a9b1f45..9bae94961c 100644 --- a/modules/timeline/src/main/Entry.scala +++ b/modules/timeline/src/main/Entry.scala @@ -3,70 +3,125 @@ package lila.timeline import org.joda.time.DateTime import play.api.libs.json._ import reactivemongo.bson._ +import scala.util.{ Try, Success, Failure } +import lila.db.dsl._ import lila.hub.actorApi.timeline._ -import lila.hub.actorApi.timeline.atomFormat._ case class Entry( _id: BSONObjectID, users: List[String], typ: String, chan: Option[String], - data: JsObject, + data: Bdoc, date: DateTime) { import Entry._ + import atomBsonHandlers._ def similarTo(other: Entry) = typ == other.typ && data == other.data - lazy val decode: Option[Atom] = (typ match { - case "follow" => Json.fromJson[Follow](data) - case "team-join" => Json.fromJson[TeamJoin](data) - case "team-create" => Json.fromJson[TeamCreate](data) - case "forum-post" => Json.fromJson[ForumPost](data) - case "note-create" => Json.fromJson[NoteCreate](data) - case "tour-join" => Json.fromJson[TourJoin](data) - case "qa-question" => Json.fromJson[QaQuestion](data) - case "qa-answer" => Json.fromJson[QaAnswer](data) - case "qa-comment" => Json.fromJson[QaComment](data) - case "game-end" => Json.fromJson[GameEnd](data) - case "simul-create" => Json.fromJson[SimulCreate](data) - case "simul-join" => Json.fromJson[SimulJoin](data) - }).asOpt + lazy val decode: Option[Atom] = Try(typ match { + case "follow" => followHandler.read(data) + case "team-join" => teamJoinHandler.read(data) + case "team-create" => teamCreateHandler.read(data) + case "forum-post" => forumPostHandler.read(data) + case "note-create" => noteCreateHandler.read(data) + case "tour-join" => tourJoinHandler.read(data) + case "qa-question" => qaQuestionHandler.read(data) + case "qa-answer" => qaAnswerHandler.read(data) + case "qa-comment" => qaCommentHandler.read(data) + case "game-end" => gameEndHandler.read(data) + case "simul-create" => simulCreateHandler.read(data) + case "simul-join" => simulJoinHandler.read(data) + case _ => sys error s"Unhandled atom type: $typ" + }) match { + case Success(atom) => Some(atom) + case Failure(err) => + lila.log("timeline").warn(err.getMessage) + none + } def okForKid = decode ?? (_.okForKid) } object Entry { - private[timeline] def make(users: List[String], data: Atom): Option[Entry] = (data match { - case d: Follow => "follow" -> Json.toJson(d) - case d: TeamJoin => "team-join" -> Json.toJson(d) - case d: TeamCreate => "team-create" -> Json.toJson(d) - case d: ForumPost => "forum-post" -> Json.toJson(d) - case d: NoteCreate => "note-create" -> Json.toJson(d) - case d: TourJoin => "tour-join" -> Json.toJson(d) - case d: QaQuestion => "qa-question" -> Json.toJson(d) - case d: QaAnswer => "qa-answer" -> Json.toJson(d) - case d: QaComment => "qa-comment" -> Json.toJson(d) - case d: GameEnd => "game-end" -> Json.toJson(d) - case d: SimulCreate => "simul-create" -> Json.toJson(d) - case d: SimulJoin => "simul-join" -> Json.toJson(d) - }) match { - case (typ, json) => json.asOpt[JsObject] map { - new Entry(BSONObjectID.generate, users, typ, data.channel.some, _, DateTime.now) + private def toBson[A](data: A)(implicit writer: BSONDocumentWriter[A]) = writer write data + private def fromBson[A](bson: Bdoc)(implicit reader: BSONDocumentReader[A]) = reader read bson + + private[timeline] def make(users: List[String], data: Atom): Entry = { + import atomBsonHandlers._ + data match { + case d: Follow => "follow" -> toBson(d) + case d: TeamJoin => "team-join" -> toBson(d) + case d: TeamCreate => "team-create" -> toBson(d) + case d: ForumPost => "forum-post" -> toBson(d) + case d: NoteCreate => "note-create" -> toBson(d) + case d: TourJoin => "tour-join" -> toBson(d) + case d: QaQuestion => "qa-question" -> toBson(d) + case d: QaAnswer => "qa-answer" -> toBson(d) + case d: QaComment => "qa-comment" -> toBson(d) + case d: GameEnd => "game-end" -> toBson(d) + case d: SimulCreate => "simul-create" -> toBson(d) + case d: SimulJoin => "simul-join" -> toBson(d) + } + } match { + case (typ, bson) => + new Entry(BSONObjectID.generate, users, typ, data.channel.some, bson, DateTime.now) + } + + object atomBsonHandlers { + implicit val followHandler = Macros.handler[Follow] + implicit val teamJoinHandler = Macros.handler[TeamJoin] + implicit val teamCreateHandler = Macros.handler[TeamCreate] + implicit val forumPostHandler = Macros.handler[ForumPost] + implicit val noteCreateHandler = Macros.handler[NoteCreate] + implicit val tourJoinHandler = Macros.handler[TourJoin] + implicit val qaQuestionHandler = Macros.handler[QaQuestion] + implicit val qaAnswerHandler = Macros.handler[QaAnswer] + implicit val qaCommentHandler = Macros.handler[QaComment] + implicit val gameEndHandler = Macros.handler[GameEnd] + implicit val simulCreateHandler = Macros.handler[SimulCreate] + implicit val simulJoinHandler = Macros.handler[SimulJoin] + } + + object atomJsonWrite { + implicit val followWrite = Json.writes[Follow] + implicit val teamJoinWrite = Json.writes[TeamJoin] + implicit val teamCreateWrite = Json.writes[TeamCreate] + implicit val forumPostWrite = Json.writes[ForumPost] + implicit val noteCreateWrite = Json.writes[NoteCreate] + implicit val tourJoinWrite = Json.writes[TourJoin] + implicit val qaQuestionWrite = Json.writes[QaQuestion] + implicit val qaAnswerWrite = Json.writes[QaAnswer] + implicit val qaCommentWrite = Json.writes[QaComment] + implicit val gameEndWrite = Json.writes[GameEnd] + implicit val simulCreateWrite = Json.writes[SimulCreate] + implicit val simulJoinWrite = Json.writes[SimulJoin] + implicit val atomWrite = Writes[Atom] { + case d: Follow => followWrite writes d + case d: TeamJoin => teamJoinWrite writes d + case d: TeamCreate => teamCreateWrite writes d + case d: ForumPost => forumPostWrite writes d + case d: NoteCreate => noteCreateWrite writes d + case d: TourJoin => tourJoinWrite writes d + case d: QaQuestion => qaQuestionWrite writes d + case d: QaAnswer => qaAnswerWrite writes d + case d: QaComment => qaCommentWrite writes d + case d: GameEnd => gameEndWrite writes d + case d: SimulCreate => simulCreateWrite writes d + case d: SimulJoin => simulJoinWrite writes d } } - import play.modules.reactivemongo.json.ImplicitBSONHandlers._ - import lila.db.BSON.BSONJodaDateTimeHandler - import reactivemongo.bson.Macros implicit val EntryBSONHandler = Macros.handler[Entry] implicit val entryWrites = OWrites[Entry] { e => + import atomJsonWrite._ Json.obj( "type" -> e.typ, - "data" -> e.data, + "data" -> e.decode, "date" -> e.date) } } diff --git a/modules/timeline/src/main/EntryRepo.scala b/modules/timeline/src/main/EntryRepo.scala index 7d7304c2ec..1ff54f3ca9 100644 --- a/modules/timeline/src/main/EntryRepo.scala +++ b/modules/timeline/src/main/EntryRepo.scala @@ -1,7 +1,6 @@ package lila.timeline -import lila.db.BSON.BSONJodaDateTimeHandler -import lila.db.Types.Coll +import lila.db.dsl._ import org.joda.time.DateTime import reactivemongo.bson._ @@ -16,23 +15,23 @@ private[timeline] final class EntryRepo(coll: Coll, userMax: Int) { userEntries(userId, nb) private def userEntries(userId: String, max: Int): Fu[List[Entry]] = - coll.find(BSONDocument("users" -> userId)) - .sort(BSONDocument("date" -> -1)) + coll.find($doc("users" -> userId)) + .sort($doc("date" -> -1)) .cursor[Entry]() - .collect[List](max) + .gather[List](max) def findRecent(typ: String, since: DateTime) = - coll.find(BSONDocument( + coll.find($doc( "typ" -> typ, - "date" -> BSONDocument("$gt" -> since) + "date" -> $doc("$gt" -> since) )).cursor[Entry]() - .collect[List]() + .gather[List]() def channelUserIdRecentExists(channel: String, userId: String): Fu[Boolean] = - coll.count(BSONDocument( + coll.count($doc( "users" -> userId, "chan" -> channel, - "date" -> BSONDocument("$gt" -> DateTime.now.minusDays(7)) + "date" $gt DateTime.now.minusDays(7) ).some) map (0 !=) def insert(entry: Entry) = coll insert entry void diff --git a/modules/timeline/src/main/Push.scala b/modules/timeline/src/main/Push.scala index 0b5a49e4e0..4b26ff5fb3 100644 --- a/modules/timeline/src/main/Push.scala +++ b/modules/timeline/src/main/Push.scala @@ -55,15 +55,13 @@ private[timeline] final class Push( } } - private def makeEntry(users: List[String], data: Atom): Fu[Entry] = - Entry.make(users, data).fold( - fufail[Entry]("[timeline] invalid entry data " + data) - ) { entry => - entryRepo.findRecent(entry.typ, DateTime.now minusMinutes 50) flatMap { entries => - entries.exists(_ similarTo entry) fold ( - fufail[Entry]("[timeline] a similar entry already exists"), - entryRepo insert entry inject entry - ) - } - } + private def makeEntry(users: List[String], data: Atom): Fu[Entry] = { + val entry = Entry.make(users, data) + entryRepo.findRecent(entry.typ, DateTime.now minusMinutes 50) flatMap { entries => + entries.exists(_ similarTo entry) fold ( + fufail[Entry]("[timeline] a similar entry already exists"), + entryRepo insert entry inject entry + ) + } + } } diff --git a/modules/timeline/src/main/UnsubApi.scala b/modules/timeline/src/main/UnsubApi.scala index 3734ee33be..ef1940027d 100644 --- a/modules/timeline/src/main/UnsubApi.scala +++ b/modules/timeline/src/main/UnsubApi.scala @@ -2,14 +2,13 @@ package lila.timeline import reactivemongo.bson._ -import lila.db.Types.Coll +import lila.db.dsl._ private[timeline] final class UnsubApi(coll: Coll) { private def makeId(channel: String, userId: String) = s"$userId@$channel" - private def select(channel: String, userId: String) = - BSONDocument("_id" -> makeId(channel, userId)) + private def select(channel: String, userId: String) = $id(makeId(channel, userId)) def set(channel: String, userId: String, v: Boolean): Funit = { if (v) coll.insert(select(channel, userId)).void @@ -22,8 +21,8 @@ private[timeline] final class UnsubApi(coll: Coll) { coll.count(select(channel, userId).some) map (0 !=) def filterUnsub(channel: String, userIds: List[String]): Fu[List[String]] = - coll.distinct("_id", BSONDocument( - "_id" -> BSONDocument("$in" -> userIds.map { makeId(channel, _) }) + coll.distinct("_id", $doc( + "_id" $in (userIds.map { makeId(channel, _) }: _*) ).some) map lila.db.BSON.asStrings map { unsubs => userIds diff unsubs.map(_ takeWhile ('@' !=)) } diff --git a/modules/tournament/src/main/BSONHandlers.scala b/modules/tournament/src/main/BSONHandlers.scala index c646e07d8a..3ed4b6e686 100644 --- a/modules/tournament/src/main/BSONHandlers.scala +++ b/modules/tournament/src/main/BSONHandlers.scala @@ -165,5 +165,6 @@ object BSONHandlers { } import LeaderboardApi.ChartData.AggregationResult - implicit val leaderboardAggregationResultBSONHandler = Macros.handler[AggregationResult] + implicit val leaderboardAggregationResultBSONHandler = + BSON.LoggingHandler(logger)(Macros.handler[AggregationResult]) } diff --git a/modules/tournament/src/main/Env.scala b/modules/tournament/src/main/Env.scala index 9f0c879062..282236e26d 100644 --- a/modules/tournament/src/main/Env.scala +++ b/modules/tournament/src/main/Env.scala @@ -17,7 +17,6 @@ final class Env( system: ActorSystem, db: lila.db.Env, mongoCache: lila.memo.MongoCache.Builder, - flood: lila.security.Flood, hub: lila.hub.Env, roundMap: ActorRef, roundSocketHub: ActorSelection, @@ -80,8 +79,7 @@ final class Env( lazy val socketHandler = new SocketHandler( hub = hub, socketHub = socketHub, - chat = hub.actor.chat, - flood = flood) + chat = hub.actor.chat)(system) lazy val winners = new Winners( mongoCache = mongoCache, @@ -171,7 +169,6 @@ object Env { system = lila.common.PlayApp.system, db = lila.db.Env.current, mongoCache = lila.memo.Env.current.mongoCache, - flood = lila.security.Env.current.flood, hub = lila.hub.Env.current, roundMap = lila.round.Env.current.roundMap, roundSocketHub = lila.hub.Env.current.socket.round, diff --git a/modules/tournament/src/main/LeaderboardApi.scala b/modules/tournament/src/main/LeaderboardApi.scala index abb6142ec0..18c0d9cc59 100644 --- a/modules/tournament/src/main/LeaderboardApi.scala +++ b/modules/tournament/src/main/LeaderboardApi.scala @@ -8,8 +8,8 @@ import scala.concurrent.duration._ import lila.common.Maths import lila.common.paginator.Paginator import lila.db.BSON._ -import lila.db.paginator.BSONAdapter -import lila.db.Types.Coll +import lila.db.dsl._ +import lila.db.paginator.Adapter import lila.rating.PerfType import lila.user.User @@ -20,15 +20,15 @@ final class LeaderboardApi( import LeaderboardApi._ import BSONHandlers._ - def recentByUser(user: User, page: Int) = paginator(user, page, BSONDocument("d" -> -1)) + def recentByUser(user: User, page: Int) = paginator(user, page, $doc("d" -> -1)) - def bestByUser(user: User, page: Int) = paginator(user, page, BSONDocument("w" -> 1)) + def bestByUser(user: User, page: Int) = paginator(user, page, $doc("w" -> 1)) def chart(user: User): Fu[ChartData] = { import reactivemongo.bson._ import reactivemongo.api.collections.bson.BSONBatchCommands.AggregationFramework._ coll.aggregate( - Match(BSONDocument("u" -> user.id)), + Match($doc("u" -> user.id)), List(GroupField("v")("nb" -> SumValue(1), "points" -> Push("s"), "ratios" -> Push("w"))) ).map { _.documents map leaderboardAggregationResultBSONHandler.read @@ -46,11 +46,11 @@ final class LeaderboardApi( } } - private def paginator(user: User, page: Int, sort: BSONDocument): Fu[Paginator[TourEntry]] = Paginator( - adapter = new BSONAdapter[Entry]( + private def paginator(user: User, page: Int, sort: Bdoc): Fu[Paginator[TourEntry]] = Paginator( + adapter = new Adapter[Entry]( collection = coll, - selector = BSONDocument("u" -> user.id), - projection = BSONDocument(), + selector = $doc("u" -> user.id), + projection = $empty, sort = sort ) mapFutureList withTournaments, currentPage = page, diff --git a/modules/tournament/src/main/LeaderboardIndexer.scala b/modules/tournament/src/main/LeaderboardIndexer.scala index 69a8a4f22e..f16fd911cd 100644 --- a/modules/tournament/src/main/LeaderboardIndexer.scala +++ b/modules/tournament/src/main/LeaderboardIndexer.scala @@ -6,7 +6,7 @@ import reactivemongo.bson._ import scala.concurrent.duration._ import lila.db.BSON._ -import lila.db.Types.Coll +import lila.db.dsl.Coll private final class LeaderboardIndexer( tournamentColl: Coll, diff --git a/modules/tournament/src/main/PairingRepo.scala b/modules/tournament/src/main/PairingRepo.scala index 94d2c52bc6..b078278c03 100644 --- a/modules/tournament/src/main/PairingRepo.scala +++ b/modules/tournament/src/main/PairingRepo.scala @@ -6,34 +6,33 @@ import reactivemongo.bson._ import reactivemongo.core.commands._ import BSONHandlers._ -import lila.db.BSON._ -import lila.db.Implicits._ +import lila.db.dsl._ object PairingRepo { private lazy val coll = Env.current.pairingColl - private def selectId(id: String) = BSONDocument("_id" -> id) - def selectTour(tourId: String) = BSONDocument("tid" -> tourId) - def selectUser(userId: String) = BSONDocument("u" -> userId) - private def selectTourUser(tourId: String, userId: String) = BSONDocument( + private def selectId(id: String) = $doc("_id" -> id) + def selectTour(tourId: String) = $doc("tid" -> tourId) + def selectUser(userId: String) = $doc("u" -> userId) + private def selectTourUser(tourId: String, userId: String) = $doc( "tid" -> tourId, "u" -> userId) - private val selectPlaying = BSONDocument("s" -> BSONDocument("$lt" -> chess.Status.Mate.id)) - private val selectFinished = BSONDocument("s" -> BSONDocument("$gte" -> chess.Status.Mate.id)) - private val recentSort = BSONDocument("d" -> -1) - private val chronoSort = BSONDocument("d" -> 1) + private val selectPlaying = $doc("s" -> $doc("$lt" -> chess.Status.Mate.id)) + private val selectFinished = $doc("s" -> $doc("$gte" -> chess.Status.Mate.id)) + private val recentSort = $doc("d" -> -1) + private val chronoSort = $doc("d" -> 1) - def byId(id: String): Fu[Option[Pairing]] = coll.find(selectId(id)).one[Pairing] + def byId(id: String): Fu[Option[Pairing]] = coll.find(selectId(id)).uno[Pairing] def recentByTour(tourId: String, nb: Int): Fu[Pairings] = - coll.find(selectTour(tourId)).sort(recentSort).cursor[Pairing]().collect[List](nb) + coll.find(selectTour(tourId)).sort(recentSort).cursor[Pairing]().gather[List](nb) def lastOpponents(tourId: String, userIds: Iterable[String], nb: Int): Fu[Pairing.LastOpponents] = coll.find( - selectTour(tourId) ++ BSONDocument("u" -> BSONDocument("$in" -> userIds)), - BSONDocument("_id" -> false, "u" -> true) - ).sort(recentSort).cursor[BSONDocument]().enumerate(nb) |>>> + selectTour(tourId) ++ $doc("u" -> $doc("$in" -> userIds)), + $doc("_id" -> false, "u" -> true) + ).sort(recentSort).cursor[Bdoc]().enumerate(nb) |>>> Iteratee.fold(scala.collection.immutable.Map.empty[String, String]) { (acc, doc) => ~doc.getAs[List[String]]("u") match { case List(u1, u2) => @@ -45,8 +44,8 @@ object PairingRepo { def opponentsOf(tourId: String, userId: String): Fu[Set[String]] = coll.find( selectTourUser(tourId, userId), - BSONDocument("_id" -> false, "u" -> true) - ).cursor[BSONDocument]().collect[List]().map { + $doc("_id" -> false, "u" -> true) + ).cursor[Bdoc]().gather[List]().map { _.flatMap { doc => ~doc.getAs[List[String]]("u").filter(userId!=) }.toSet @@ -55,15 +54,15 @@ object PairingRepo { def recentIdsByTourAndUserId(tourId: String, userId: String, nb: Int): Fu[List[String]] = coll.find( selectTourUser(tourId, userId), - BSONDocument("_id" -> true) - ).sort(recentSort).cursor[BSONDocument]().collect[List](nb).map { + $doc("_id" -> true) + ).sort(recentSort).cursor[Bdoc]().gather[List](nb).map { _.flatMap(_.getAs[String]("_id")) } def byTourUserNb(tourId: String, userId: String, nb: Int): Fu[Option[Pairing]] = (nb > 0) ?? coll.find( selectTourUser(tourId, userId) - ).sort(chronoSort).skip(nb - 1).one[Pairing] + ).sort(chronoSort).skip(nb - 1).uno[Pairing] def removeByTour(tourId: String) = coll.remove(selectTour(tourId)).void @@ -78,7 +77,7 @@ object PairingRepo { coll.aggregate( Match(selectTour(tourId)), List( - Project(BSONDocument("u" -> true, "_id" -> false)), + Project($doc("u" -> true, "_id" -> false)), Unwind("u"), GroupField("u")("nb" -> SumValue(1)) )).map { @@ -93,25 +92,25 @@ object PairingRepo { def removePlaying(tourId: String) = coll.remove(selectTour(tourId) ++ selectPlaying).void def findPlaying(tourId: String): Fu[Pairings] = - coll.find(selectTour(tourId) ++ selectPlaying).cursor[Pairing]().collect[List]() + coll.find(selectTour(tourId) ++ selectPlaying).cursor[Pairing]().gather[List]() def findPlaying(tourId: String, userId: String): Fu[Option[Pairing]] = - coll.find(selectTourUser(tourId, userId) ++ selectPlaying).one[Pairing] + coll.find(selectTourUser(tourId, userId) ++ selectPlaying).uno[Pairing] def finishedByPlayerChronological(tourId: String, userId: String): Fu[Pairings] = coll.find( selectTourUser(tourId, userId) ++ selectFinished - ).sort(chronoSort).cursor[Pairing]().collect[List]() + ).sort(chronoSort).cursor[Pairing]().gather[List]() def insert(pairing: Pairing) = coll.insert { - pairingHandler.write(pairing) ++ BSONDocument("d" -> DateTime.now) + pairingHandler.write(pairing) ++ $doc("d" -> DateTime.now) }.void def finish(g: lila.game.Game) = if (g.aborted) coll.remove(selectId(g.id)) else coll.update( selectId(g.id), - BSONDocument("$set" -> BSONDocument( + $doc("$set" -> $doc( "s" -> g.status.id, "w" -> g.winnerColor.map(_.white), "t" -> g.turns))).void @@ -123,14 +122,14 @@ object PairingRepo { }) ?? { field => coll.update( selectId(pairing.id), - BSONDocument("$set" -> BSONDocument(field -> value))).void + $doc("$set" -> $doc(field -> value))).void } import reactivemongo.api.collections.bson.BSONBatchCommands.AggregationFramework, AggregationFramework.{ AddToSet, Group, Match, Project, Push, Unwind } def playingUserIds(tour: Tournament): Fu[Set[String]] = coll.aggregate(Match(selectTour(tour.id) ++ selectPlaying), List( - Project(BSONDocument( + Project($doc( "u" -> BSONBoolean(true), "_id" -> BSONBoolean(false))), Unwind("u"), Group(BSONBoolean(true))("ids" -> AddToSet("u")))).map( _.documents.headOption.flatMap(_.getAs[Set[String]]("ids")). diff --git a/modules/tournament/src/main/PlayerRepo.scala b/modules/tournament/src/main/PlayerRepo.scala index b61b638985..52e4ad3aa1 100644 --- a/modules/tournament/src/main/PlayerRepo.scala +++ b/modules/tournament/src/main/PlayerRepo.scala @@ -6,7 +6,7 @@ import reactivemongo.core.commands._ import BSONHandlers._ import lila.db.BSON._ -import lila.db.Implicits._ +import lila.db.dsl._ import lila.rating.Perf import lila.user.{ User, Perfs } @@ -24,10 +24,10 @@ object PlayerRepo { private val selectWithdraw = BSONDocument("w" -> true) private val bestSort = BSONDocument("m" -> -1) - def byId(id: String): Fu[Option[Player]] = coll.find(selectId(id)).one[Player] + def byId(id: String): Fu[Option[Player]] = coll.uno[Player](selectId(id)) def bestByTour(tourId: String, nb: Int, skip: Int = 0): Fu[List[Player]] = - coll.find(selectTour(tourId)).sort(bestSort).skip(skip).cursor[Player]().collect[List](nb) + coll.find(selectTour(tourId)).sort(bestSort).skip(skip).cursor[Player]().gather[List](nb) def bestByTourWithRank(tourId: String, nb: Int, skip: Int = 0): Fu[RankedPlayers] = bestByTour(tourId, nb, skip).map { res => @@ -63,7 +63,7 @@ object PlayerRepo { multi = true).void def find(tourId: String, userId: String): Fu[Option[Player]] = - coll.find(selectTourUser(tourId, userId)).one[Player] + coll.find(selectTourUser(tourId, userId)).uno[Player] def update(tourId: String, userId: String)(f: Player => Fu[Player]) = find(tourId, userId) flatten s"No such player: $tourId/$userId" flatMap f flatMap { player => @@ -94,7 +94,7 @@ object PlayerRepo { def withPoints(tourId: String): Fu[List[Player]] = coll.find( selectTour(tourId) ++ BSONDocument("m" -> BSONDocument("$gt" -> 0)) - ).cursor[Player]().collect[List]() + ).cursor[Player]().gather[List]() private def aggregationUserIdList(res: Stream[BSONDocument]): List[String] = res.headOption flatMap { _.getAs[List[String]]("uids") } getOrElse Nil @@ -108,7 +108,7 @@ object PlayerRepo { coll.distinct("uid", (selectTour(tourId) ++ selectActive).some) map lila.db.BSON.asStrings def winner(tourId: String): Fu[Option[Player]] = - coll.find(selectTour(tourId)).sort(bestSort).one[Player] + coll.find(selectTour(tourId)).sort(bestSort).uno[Player] // freaking expensive (marathons) private[tournament] def computeRanking(tourId: String): Fu[Ranking] = @@ -131,9 +131,8 @@ object PlayerRepo { } def byTourAndUserIds(tourId: String, userIds: Iterable[String]): Fu[List[Player]] = - coll.find(selectTour(tourId) ++ BSONDocument( - "uid" -> BSONDocument("$in" -> userIds) - )).cursor[Player]().collect[List]() + coll.find(selectTour(tourId) ++ $doc("uid" -> $doc("$in" -> userIds))) + .list[Player]() .chronometer.logIfSlow(200, logger) { players => s"PlayerRepo.byTourAndUserIds $tourId ${userIds.size} user IDs, ${players.size} players" }.result diff --git a/modules/tournament/src/main/Socket.scala b/modules/tournament/src/main/Socket.scala index 94d0240e9e..782966e25f 100644 --- a/modules/tournament/src/main/Socket.scala +++ b/modules/tournament/src/main/Socket.scala @@ -2,7 +2,6 @@ package lila.tournament import akka.actor._ import akka.pattern.pipe -import play.api.libs.iteratee._ import play.api.libs.json._ import scala.concurrent.duration._ @@ -11,7 +10,7 @@ import lila.common.LightUser import lila.hub.actorApi.WithUserIds import lila.hub.TimeBomb import lila.memo.ExpireSetMemo -import lila.socket.actorApi.{ Connected => _, _ } +import lila.socket.actorApi._ import lila.socket.{ SocketActor, History, Historical } private[tournament] final class Socket( @@ -78,12 +77,9 @@ private[tournament] final class Socket( case GetVersion => sender ! history.version - case Join(uid, user) => - val (enumerator, channel) = Concurrent.broadcast[JsValue] - val member = Member(channel, user) + case AddMember(uid, member) => addMember(uid, member) notifyCrowd - sender ! Connected(enumerator, member) case Quit(uid) => quit(uid) diff --git a/modules/tournament/src/main/SocketHandler.scala b/modules/tournament/src/main/SocketHandler.scala index c1eb7f1ba7..6e8c1a2e66 100644 --- a/modules/tournament/src/main/SocketHandler.scala +++ b/modules/tournament/src/main/SocketHandler.scala @@ -1,16 +1,12 @@ package lila.tournament -import scala.concurrent.duration._ - import akka.actor._ import akka.pattern.ask import actorApi._ import lila.common.PimpedJson._ import lila.hub.actorApi.map._ -import lila.security.Flood -import akka.actor.ActorSelection -import lila.socket.actorApi.{ Connected => _, _ } +import lila.socket.actorApi._ import lila.socket.Handler import lila.user.User import makeTimeout.short @@ -18,36 +14,25 @@ import makeTimeout.short private[tournament] final class SocketHandler( hub: lila.hub.Env, socketHub: ActorRef, - chat: ActorSelection, - flood: Flood) { + chat: ActorSelection)(implicit system: ActorSystem) { - def join( - tourId: String, - uid: String, - user: Option[User]): Fu[Option[JsSocketHandler]] = + def join(tourId: String, uid: String, user: Option[User]): Fu[Option[JsFlow]] = TournamentRepo.exists(tourId) flatMap { _ ?? { - for { - socket ← socketHub ? Get(tourId) mapTo manifest[ActorRef] - join = Join(uid = uid, user = user) - handler ← Handler(hub, socket, uid, join, user map (_.id)) { - case Connected(enum, member) => - (controller(socket, tourId, uid, member), enum, member) - } - } yield handler.some - } - } - - private def controller( - socket: ActorRef, - tourId: String, - uid: String, - member: Member): Handler.Controller = { - case ("p", o) => o int "v" foreach { v => socket ! PingVersion(uid, v) } - case ("talk", o) => o str "d" foreach { text => - member.userId foreach { userId => - chat ! lila.chat.actorApi.UserTalk(tourId, userId, text, socket) + socketHub ? Get(tourId) mapTo manifest[ActorRef] map { socket => + Handler.actorRef { out => + val member = Member(out, user) + socket ! AddMember(uid, member) + Handler.props(hub, socket, member, uid, user.map(_.id)) { + case ("p", o) => o int "v" foreach { v => socket ! PingVersion(uid, v) } + case ("talk", o) => o str "d" foreach { text => + member.userId foreach { userId => + chat ! lila.chat.actorApi.UserTalk(tourId, userId, text, socket) + } + } + } + }.some + } } } - } } diff --git a/modules/tournament/src/main/TournamentApi.scala b/modules/tournament/src/main/TournamentApi.scala index c6b14edfe5..1aa8f409d7 100644 --- a/modules/tournament/src/main/TournamentApi.scala +++ b/modules/tournament/src/main/TournamentApi.scala @@ -10,7 +10,7 @@ import scalaz.NonEmptyList import actorApi._ import lila.common.Debouncer -import lila.db.api._ +import lila.db.dsl._ import lila.game.{ Game, GameRepo, Pov } import lila.hub.actorApi.lobby.ReloadTournaments import lila.hub.actorApi.map.{ Tell, TellIds } diff --git a/modules/tournament/src/main/TournamentRepo.scala b/modules/tournament/src/main/TournamentRepo.scala index fae5e7322b..b7a219207f 100644 --- a/modules/tournament/src/main/TournamentRepo.scala +++ b/modules/tournament/src/main/TournamentRepo.scala @@ -2,65 +2,64 @@ package lila.tournament import chess.variant.Variant import org.joda.time.DateTime -import reactivemongo.bson.{ BSONDocument, BSONArray, BSONInteger } import BSONHandlers._ import lila.common.paginator.Paginator import lila.db.BSON.BSONJodaDateTimeHandler -import lila.db.Implicits._ -import lila.db.paginator.BSONAdapter +import lila.db.dsl._ +import lila.db.paginator.Adapter object TournamentRepo { private lazy val coll = Env.current.tournamentColl - private def selectId(id: String) = BSONDocument("_id" -> id) + private def $id(id: String) = $doc("_id" -> id) - private val enterableSelect = BSONDocument( - "status" -> BSONDocument("$in" -> List(Status.Created.id, Status.Started.id))) + private val enterableSelect = $doc( + "status" $in (Status.Created.id, Status.Started.id)) - private val createdSelect = BSONDocument("status" -> Status.Created.id) - private val startedSelect = BSONDocument("status" -> Status.Started.id) - private[tournament] val finishedSelect = BSONDocument("status" -> Status.Finished.id) - private val startedOrFinishedSelect = BSONDocument("status" -> BSONDocument("$gte" -> Status.Started.id)) - private val unfinishedSelect = BSONDocument("status" -> BSONDocument("$ne" -> Status.Finished.id)) - private[tournament] val scheduledSelect = BSONDocument("schedule" -> BSONDocument("$exists" -> true)) - private def sinceSelect(date: DateTime) = BSONDocument("startsAt" -> BSONDocument("$gt" -> date)) + private val createdSelect = $doc("status" -> Status.Created.id) + private val startedSelect = $doc("status" -> Status.Started.id) + private[tournament] val finishedSelect = $doc("status" -> Status.Finished.id) + private val startedOrFinishedSelect = $doc("status" -> $doc("$gte" -> Status.Started.id)) + private val unfinishedSelect = $doc("status" -> $doc("$ne" -> Status.Finished.id)) + private[tournament] val scheduledSelect = $doc("schedule" -> $doc("$exists" -> true)) + private def sinceSelect(date: DateTime) = $doc("startsAt" -> $doc("$gt" -> date)) private def variantSelect(variant: Variant) = - if (variant.standard) BSONDocument("variant" -> BSONDocument("$exists" -> false)) - else BSONDocument("variant" -> variant.id) - private val nonEmptySelect = BSONDocument("nbPlayers" -> BSONDocument("$ne" -> 0)) - private val selectUnique = BSONDocument("schedule.freq" -> "unique") + if (variant.standard) $doc("variant" -> $doc("$exists" -> false)) + else $doc("variant" -> variant.id) + private val nonEmptySelect = $doc("nbPlayers" -> $doc("$ne" -> 0)) + private val selectUnique = $doc("schedule.freq" -> "unique") - def byId(id: String): Fu[Option[Tournament]] = coll.find(selectId(id)).one[Tournament] + def byId(id: String): Fu[Option[Tournament]] = coll.find($id(id)).uno[Tournament] def byIds(ids: Iterable[String]): Fu[List[Tournament]] = - coll.find(BSONDocument("_id" -> BSONDocument("$in" -> ids))) - .cursor[Tournament]().collect[List]() + coll.find($inIds(ids)) + .cursor[Tournament]().gather[List]() def uniqueById(id: String): Fu[Option[Tournament]] = - coll.find(selectId(id) ++ selectUnique).one[Tournament] + coll.find($id(id) ++ selectUnique).uno[Tournament] def recentAndNext: Fu[List[Tournament]] = coll.find(sinceSelect(DateTime.now minusDays 1)) - .cursor[Tournament]().collect[List]() + .cursor[Tournament]().gather[List]() def byIdAndPlayerId(id: String, userId: String): Fu[Option[Tournament]] = coll.find( - selectId(id) ++ BSONDocument("players.id" -> userId) - ).one[Tournament] + $id(id) ++ $doc("players.id" -> userId) + ).uno[Tournament] def createdById(id: String): Fu[Option[Tournament]] = - coll.find(selectId(id) ++ createdSelect).one[Tournament] + coll.find($id(id) ++ createdSelect).uno[Tournament] def enterableById(id: String): Fu[Option[Tournament]] = - coll.find(selectId(id) ++ enterableSelect).one[Tournament] + coll.find($id(id) ++ enterableSelect).uno[Tournament] def startedById(id: String): Fu[Option[Tournament]] = - coll.find(selectId(id) ++ startedSelect).one[Tournament] + coll.find($id(id) ++ startedSelect).uno[Tournament] def finishedById(id: String): Fu[Option[Tournament]] = - coll.find(selectId(id) ++ finishedSelect).one[Tournament] + coll.find($id(id) ++ finishedSelect).uno[Tournament] def startedOrFinishedById(id: String): Fu[Option[Tournament]] = byId(id) map { _ filterNot (_.isCreated) } @@ -69,88 +68,85 @@ object TournamentRepo { createdById(id) map (_ filter (_.createdBy == userId)) def allEnterable: Fu[List[Tournament]] = - coll.find(enterableSelect).cursor[Tournament]().collect[List]() + coll.find(enterableSelect).cursor[Tournament]().gather[List]() def nonEmptyEnterable: Fu[List[Tournament]] = - coll.find(enterableSelect ++ nonEmptySelect).cursor[Tournament]().collect[List]() + coll.find(enterableSelect ++ nonEmptySelect).cursor[Tournament]().gather[List]() - def createdIncludingScheduled: Fu[List[Tournament]] = coll.find(createdSelect).toList[Tournament](None) + def createdIncludingScheduled: Fu[List[Tournament]] = coll.find(createdSelect).list[Tournament](None) def started: Fu[List[Tournament]] = - coll.find(startedSelect).sort(BSONDocument("createdAt" -> -1)).toList[Tournament](None) + coll.find(startedSelect).sort($doc("createdAt" -> -1)).list[Tournament](None) def publicStarted: Fu[List[Tournament]] = - coll.find(startedSelect ++ BSONDocument("private" -> BSONDocument("$exists" -> false))) - .sort(BSONDocument("createdAt" -> -1)) - .cursor[Tournament]().collect[List]() + coll.find(startedSelect ++ $doc("private" -> $doc("$exists" -> false))) + .sort($doc("createdAt" -> -1)) + .list[Tournament]() def finished(limit: Int): Fu[List[Tournament]] = coll.find(finishedSelect) - .sort(BSONDocument("startsAt" -> -1)) - .cursor[Tournament]().collect[List](limit) + .sort($doc("startsAt" -> -1)) + .list[Tournament](limit) def finishedNotable(limit: Int): Fu[List[Tournament]] = - coll.find(finishedSelect ++ BSONDocument( - "$or" -> BSONArray( - BSONDocument("nbPlayers" -> BSONDocument("$gte" -> 15)), + coll.find(finishedSelect ++ $doc( + "$or" -> $arr( + $doc("nbPlayers" -> $doc("$gte" -> 15)), scheduledSelect ))) - .sort(BSONDocument("startsAt" -> -1)) - .cursor[Tournament]().collect[List](limit) + .sort($doc("startsAt" -> -1)) + .list[Tournament](limit) def finishedPaginator(maxPerPage: Int, page: Int) = Paginator( - adapter = new BSONAdapter[Tournament]( + adapter = new Adapter[Tournament]( collection = coll, selector = finishedSelect, - projection = BSONDocument(), - sort = BSONDocument("startsAt" -> -1) + projection = $empty, + sort = $doc("startsAt" -> -1) ), currentPage = page, maxPerPage = maxPerPage) def setStatus(tourId: String, status: Status) = coll.update( - selectId(tourId), - BSONDocument("$set" -> BSONDocument("status" -> status.id)) + $id(tourId), + $doc("$set" -> $doc("status" -> status.id)) ).void def setNbPlayers(tourId: String, nb: Int) = coll.update( - selectId(tourId), - BSONDocument("$set" -> BSONDocument("nbPlayers" -> nb)) + $id(tourId), + $doc("$set" -> $doc("nbPlayers" -> nb)) ).void def setWinnerId(tourId: String, userId: String) = coll.update( - selectId(tourId), - BSONDocument("$set" -> BSONDocument("winner" -> userId)) + $id(tourId), + $doc("$set" -> $doc("winner" -> userId)) ).void def setFeaturedGameId(tourId: String, gameId: String) = coll.update( - selectId(tourId), - BSONDocument("$set" -> BSONDocument("featured" -> gameId)) + $id(tourId), + $doc("$set" -> $doc("featured" -> gameId)) ).void - def featuredGameId(tourId: String) = coll.find( - selectId(tourId), - BSONDocument("featured" -> true) - ).one[BSONDocument].map(_.flatMap(_.getAs[String]("featured"))) + def featuredGameId(tourId: String) = coll.primitiveOne[String]($id(tourId), "featured") - private def allCreatedSelect(aheadMinutes: Int) = createdSelect ++ BSONDocument( - "$or" -> BSONArray( - BSONDocument("schedule" -> BSONDocument("$exists" -> false)), - BSONDocument("startsAt" -> BSONDocument("$lt" -> (DateTime.now plusMinutes aheadMinutes))) + private def allCreatedSelect(aheadMinutes: Int) = createdSelect ++ $doc( + "$or" -> $arr( + $doc("schedule" -> $doc("$exists" -> false)), + $doc("startsAt" -> $doc("$lt" -> (DateTime.now plusMinutes aheadMinutes))) ) ) def publicCreatedSorted(aheadMinutes: Int): Fu[List[Tournament]] = coll.find( - allCreatedSelect(aheadMinutes) ++ BSONDocument("private" -> BSONDocument("$exists" -> false)) - ).sort(BSONDocument("startsAt" -> 1)).cursor[Tournament]().collect[List]() + allCreatedSelect(aheadMinutes) ++ $doc("private" -> $doc("$exists" -> false)) + ).sort($doc("startsAt" -> 1)).list[Tournament](none) def allCreated(aheadMinutes: Int): Fu[List[Tournament]] = - coll.find(allCreatedSelect(aheadMinutes)).cursor[Tournament]().collect[List]() + coll.find(allCreatedSelect(aheadMinutes)).cursor[Tournament]().gather[List]() private def stillWorthEntering: Fu[List[Tournament]] = - coll.find(startedSelect ++ BSONDocument( - "private" -> BSONDocument("$exists" -> false) - )).sort(BSONDocument("startsAt" -> 1)).toList[Tournament](none) map { + coll.find(startedSelect ++ $doc( + "private" -> $doc("$exists" -> false) + )).sort($doc("startsAt" -> 1)).list[Tournament](none) map { _.filter(_.isStillWorthEntering) } @@ -176,17 +172,17 @@ object TournamentRepo { def uniques(max: Int): Fu[List[Tournament]] = coll.find(selectUnique) - .sort(BSONDocument("startsAt" -> -1)) - .hint(BSONDocument("startsAt" -> -1)) - .cursor[Tournament]().collect[List]() + .sort($doc("startsAt" -> -1)) + .hint($doc("startsAt" -> -1)) + .list[Tournament]() def scheduledUnfinished: Fu[List[Tournament]] = coll.find(scheduledSelect ++ unfinishedSelect) - .sort(BSONDocument("startsAt" -> 1)).cursor[Tournament]().collect[List]() + .sort($doc("startsAt" -> 1)).list[Tournament]() def scheduledCreated: Fu[List[Tournament]] = coll.find(createdSelect ++ scheduledSelect) - .sort(BSONDocument("startsAt" -> 1)).cursor[Tournament]().collect[List]() + .sort($doc("startsAt" -> 1)).list[Tournament]() def scheduledDedup: Fu[List[Tournament]] = scheduledCreated map { import Schedule.Freq @@ -203,35 +199,35 @@ object TournamentRepo { } def lastFinishedScheduledByFreq(freq: Schedule.Freq, since: DateTime): Fu[List[Tournament]] = coll.find( - finishedSelect ++ sinceSelect(since) ++ variantSelect(chess.variant.Standard) ++ BSONDocument( + finishedSelect ++ sinceSelect(since) ++ variantSelect(chess.variant.Standard) ++ $doc( "schedule.freq" -> freq.name, - "schedule.speed" -> BSONDocument("$in" -> Schedule.Speed.mostPopular.map(_.name)) + "schedule.speed".$in(Schedule.Speed.mostPopular.map(_.name): _*) ) - ).sort(BSONDocument("startsAt" -> -1)) - .toList[Tournament](Schedule.Speed.mostPopular.size.some) + ).sort($doc("startsAt" -> -1)) + .list[Tournament](Schedule.Speed.mostPopular.size.some) def lastFinishedDaily(variant: Variant): Fu[Option[Tournament]] = coll.find( finishedSelect ++ sinceSelect(DateTime.now minusDays 1) ++ variantSelect(variant) ++ - BSONDocument("schedule.freq" -> Schedule.Freq.Daily.name) - ).sort(BSONDocument("startsAt" -> -1)).one[Tournament] + $doc("schedule.freq" -> Schedule.Freq.Daily.name) + ).sort($doc("startsAt" -> -1)).uno[Tournament] - def update(tour: Tournament) = coll.update(BSONDocument("_id" -> tour.id), tour) + def update(tour: Tournament) = coll.update($doc("_id" -> tour.id), tour) def insert(tour: Tournament) = coll.insert(tour) - def remove(tour: Tournament) = coll.remove(BSONDocument("_id" -> tour.id)) + def remove(tour: Tournament) = coll.remove($doc("_id" -> tour.id)) - def exists(id: String) = coll.count(BSONDocument("_id" -> id).some) map (0 !=) + def exists(id: String) = coll.count($doc("_id" -> id).some) map (0 !=) def isFinished(id: String): Fu[Boolean] = - coll.count(BSONDocument("_id" -> id, "status" -> Status.Finished.id).some) map (0 !=) + coll.count($doc("_id" -> id, "status" -> Status.Finished.id).some) map (0 !=) def toursToWithdrawWhenEntering(tourId: String): Fu[List[Tournament]] = - coll.find(enterableSelect ++ BSONDocument( - "_id" -> BSONDocument("$ne" -> tourId), - "schedule.freq" -> BSONDocument("$nin" -> List( + coll.find(enterableSelect ++ $doc( + "_id" -> $doc("$ne" -> tourId), + "schedule.freq" $nin ( Schedule.Freq.Marathon.name, Schedule.Freq.Unique.name - )) - ) ++ nonEmptySelect).cursor[Tournament]().collect[List]() + ) + ) ++ nonEmptySelect).cursor[Tournament]().gather[List]() } diff --git a/modules/tournament/src/main/actorApi.scala b/modules/tournament/src/main/actorApi.scala index acc8fe5a03..0b0fd1bc9e 100644 --- a/modules/tournament/src/main/actorApi.scala +++ b/modules/tournament/src/main/actorApi.scala @@ -1,31 +1,30 @@ package lila.tournament package actorApi +import akka.actor.ActorRef + import lila.game.Game import lila.socket.SocketMember import lila.user.User private[tournament] case class Member( - channel: JsChannel, + out: ActorRef, userId: Option[String], troll: Boolean) extends SocketMember private[tournament] object Member { - def apply(channel: JsChannel, user: Option[User]): Member = Member( - channel = channel, + def apply(out: ActorRef, user: Option[User]): Member = Member( + out = out, userId = user map (_.id), troll = user.??(_.troll)) } private[tournament] case class Messadata(trollish: Boolean = false) -private[tournament] case class Join( - uid: String, - user: Option[User]) +private[tournament] case class AddMember(uid: String, member: Member) private[tournament] case class Talk(tourId: String, u: String, t: String, troll: Boolean) private[tournament] case object Reload private[tournament] case class StartGame(game: Game) -private[tournament] case class Connected(enumerator: JsEnumerator, member: Member) case class RemindTournament(tour: Tournament, activeUserIds: List[String]) case class TournamentTable(tours: List[Tournament]) diff --git a/modules/tv/src/main/Env.scala b/modules/tv/src/main/Env.scala index 99284f34b5..b664aaae82 100644 --- a/modules/tv/src/main/Env.scala +++ b/modules/tv/src/main/Env.scala @@ -4,6 +4,7 @@ import akka.actor._ import com.typesafe.config.Config import lila.common.PimpedConfig._ +import lila.db.dsl._ import scala.collection.JavaConversions._ import scala.concurrent.duration._ @@ -39,11 +40,9 @@ final class Env( lazy val streamerList = new StreamerList(new { import reactivemongo.bson._ private val coll = db("flag") - def get = coll.find(BSONDocument("_id" -> "streamer")).one[BSONDocument].map { - ~_.flatMap(_.getAs[String]("text")) - } + def get = coll.primitiveOne[String]($id("streamer"), "text") map (~_) def set(text: String) = - coll.update(BSONDocument("_id" -> "streamer"), BSONDocument("text" -> text), upsert = true).void + coll.update($id("streamer"), $doc("text" -> text), upsert = true).void }) object isStreamer { diff --git a/modules/user/src/main/Cached.scala b/modules/user/src/main/Cached.scala index 1141db9662..6b1c6c963c 100644 --- a/modules/user/src/main/Cached.scala +++ b/modules/user/src/main/Cached.scala @@ -7,15 +7,14 @@ import play.api.libs.json._ import reactivemongo.bson._ import lila.common.LightUser -import lila.db.api.{ $count, $primitive, $gt } -import lila.db.BSON._ -import lila.db.Implicits._ +import lila.db.BSON +import lila.db.dsl._ import lila.memo.{ ExpireSetMemo, MongoCache } import lila.rating.{ Perf, PerfType } -import tube.userTube import User.{ LightPerf, LightCount } final class Cached( + userColl: Coll, nbTtl: FiniteDuration, onlineUserIdMemo: ExpireSetMemo, mongoCache: MongoCache.Builder, @@ -26,7 +25,7 @@ final class Cached( private val countCache = mongoCache.single[Int]( prefix = "user:nb", - f = $count(UserRepo.enabledSelect), + f = userColl.count(UserRepo.enabledSelect.some), timeToLive = nbTtl) def countEnabled: Fu[Int] = countCache(true) diff --git a/modules/user/src/main/Env.scala b/modules/user/src/main/Env.scala index 45c9e12ff3..75056fc0a5 100644 --- a/modules/user/src/main/Env.scala +++ b/modules/user/src/main/Env.scala @@ -48,7 +48,6 @@ final class Env( def countEnabled = cached.countEnabled def cli = new lila.common.Cli { - import tube.userTube def process = { case "user" :: "email" :: userId :: email :: Nil => UserRepo.email(User normalize userId, email) inject "done" @@ -75,6 +74,7 @@ final class Env( } lazy val cached = new Cached( + userColl = userColl, nbTtl = CachedNbTtl, onlineUserIdMemo = onlineUserIdMemo, mongoCache = mongoCache, diff --git a/modules/user/src/main/LightUserApi.scala b/modules/user/src/main/LightUserApi.scala index d591b07242..9159a63550 100644 --- a/modules/user/src/main/LightUserApi.scala +++ b/modules/user/src/main/LightUserApi.scala @@ -2,7 +2,7 @@ package lila.user import lila.common.LightUser -import lila.db.Types._ +import lila.db.dsl._ import reactivemongo.bson._ import scala.concurrent.duration._ import User.{ BSONFields => F } @@ -25,7 +25,7 @@ final class LightUserApi(coll: Coll) { id => coll.find( BSONDocument(F.id -> id), BSONDocument(F.username -> true, F.title -> true) - ).one[LightUser], + ).uno[LightUser], timeToLive = 20 minutes, default = id => LightUser(id, id, None).some, logger = logger branch "LightUserApi") diff --git a/modules/user/src/main/NoteApi.scala b/modules/user/src/main/NoteApi.scala index 2619c598fd..085bdde470 100644 --- a/modules/user/src/main/NoteApi.scala +++ b/modules/user/src/main/NoteApi.scala @@ -1,5 +1,6 @@ package lila.user +import lila.db.dsl._ import org.joda.time.DateTime case class Note( @@ -11,7 +12,7 @@ case class Note( date: DateTime) final class NoteApi( - coll: lila.db.Types.Coll, + coll: Coll, timeline: akka.actor.ActorSelection) { import reactivemongo.bson._ @@ -20,11 +21,11 @@ final class NoteApi( def get(user: User, me: User, myFriendIds: Set[String]): Fu[List[Note]] = coll.find( - BSONDocument( + $doc( "to" -> user.id, - "from" -> BSONDocument("$in" -> (myFriendIds + me.id)) - ) ++ me.troll.fold(BSONDocument(), BSONDocument("troll" -> false)) - ).sort(BSONDocument("date" -> -1)).cursor[Note]().collect[List](100) + "from" -> $doc("$in" -> (myFriendIds + me.id)) + ) ++ me.troll.fold($doc(), $doc("troll" -> false)) + ).sort($doc("date" -> -1)).cursor[Note]().gather[List](100) def write(to: User, text: String, from: User) = { diff --git a/modules/user/src/main/RankingApi.scala b/modules/user/src/main/RankingApi.scala index b6b26b3ae9..da1650b1bd 100644 --- a/modules/user/src/main/RankingApi.scala +++ b/modules/user/src/main/RankingApi.scala @@ -6,13 +6,13 @@ import reactivemongo.api.collections.bson.BSONBatchCommands.AggregationFramework import reactivemongo.bson._ import scala.concurrent.duration._ -import lila.db.BSON._ +import lila.db.dsl._ import lila.db.BSON.MapValue.MapHandler import lila.memo.{ AsyncCache, MongoCache } import lila.rating.{ Perf, PerfType } final class RankingApi( - coll: lila.db.Types.Coll, + coll: Coll, mongoCache: MongoCache.Builder, lightUser: String => Option[lila.common.LightUser]) { @@ -25,9 +25,9 @@ final class RankingApi( } def save(userId: User.ID, perfType: PerfType, perf: Perf): Funit = - (perf.nb >= 2) ?? coll.update(BSONDocument( + (perf.nb >= 2) ?? coll.update($doc( "_id" -> s"$userId:${perfType.id}" - ), BSONDocument( + ), $doc( "user" -> userId, "perf" -> perfType.id, "rating" -> perf.intRating, @@ -38,8 +38,8 @@ final class RankingApi( def remove(userId: User.ID): Funit = UserRepo byId userId flatMap { _ ?? { user => - coll.remove(BSONDocument( - "_id" -> BSONDocument("$in" -> PerfType.leaderboardable.filter { pt => + coll.remove($doc( + "_id" -> $doc("$in" -> PerfType.leaderboardable.filter { pt => user.perfs(pt).nonEmpty }.map { pt => s"${user.id}:${pt.id}" @@ -50,9 +50,9 @@ final class RankingApi( def topPerf(perfId: Perf.ID, nb: Int): Fu[List[User.LightPerf]] = PerfType.id2key(perfId) ?? { perfKey => - coll.find(BSONDocument("perf" -> perfId, "stable" -> true)) - .sort(BSONDocument("rating" -> -1)) - .cursor[Ranking]().collect[List](nb) map { + coll.find($doc("perf" -> perfId, "stable" -> true)) + .sort($doc("rating" -> -1)) + .cursor[Ranking]().gather[List](nb) map { _.flatMap { r => lightUser(r.user).map { light => User.LightPerf( @@ -80,9 +80,9 @@ final class RankingApi( private def compute(perfId: Perf.ID): Fu[Map[User.ID, Rank]] = { val enumerator = coll.find( - BSONDocument("perf" -> perfId, "stable" -> true), - BSONDocument("user" -> true, "_id" -> false) - ).sort(BSONDocument("rating" -> -1)).cursor[BSONDocument]().enumerate() + $doc("perf" -> perfId, "stable" -> true), + $doc("user" -> true, "_id" -> false) + ).sort($doc("rating" -> -1)).cursor[BSONDocument]().enumerate() var rank = 1 val b = Map.newBuilder[User.ID, Rank] val mapBuilder: Iteratee[BSONDocument, Unit] = Iteratee.foreach { doc => @@ -110,13 +110,13 @@ final class RankingApi( private def compute(perfId: Perf.ID): Fu[List[NbUsers]] = lila.rating.PerfType(perfId).exists(lila.rating.PerfType.leaderboardable.contains) ?? { coll.aggregate( - Match(BSONDocument("perf" -> perfId)), - List(Project(BSONDocument( + Match($doc("perf" -> perfId)), + List(Project($doc( "_id" -> false, - "r" -> BSONDocument( + "r" -> $doc( "$subtract" -> BSONArray( "$rating", - BSONDocument("$mod" -> BSONArray("$rating", Stat.group)) + $doc("$mod" -> BSONArray("$rating", Stat.group)) ) ) )), diff --git a/modules/user/src/main/TrophyApi.scala b/modules/user/src/main/TrophyApi.scala index 7c4b34e3fa..0b041b0551 100644 --- a/modules/user/src/main/TrophyApi.scala +++ b/modules/user/src/main/TrophyApi.scala @@ -2,10 +2,11 @@ package lila.user import org.joda.time.DateTime +import lila.db.dsl._ import lila.db.BSON.BSONJodaDateTimeHandler import reactivemongo.bson._ -final class TrophyApi(coll: lila.db.Types.Coll) { +final class TrophyApi(coll: Coll) { private implicit val trophyKindBSONHandler = new BSONHandler[BSONString, Trophy.Kind] { def read(bsonString: BSONString): Trophy.Kind = @@ -23,5 +24,5 @@ final class TrophyApi(coll: lila.db.Types.Coll) { def awardMarathonWinner(userId: String): Funit = award(userId, Trophy.Kind.MarathonWinner) def findByUser(user: User, max: Int = 12): Fu[List[Trophy]] = - coll.find(BSONDocument("user" -> user.id)).cursor[Trophy]().collect[List](max) + coll.find(BSONDocument("user" -> user.id)).cursor[Trophy]().gather[List](max) } diff --git a/modules/user/src/main/User.scala b/modules/user/src/main/User.scala index 5fc8a58532..30f406992c 100644 --- a/modules/user/src/main/User.scala +++ b/modules/user/src/main/User.scala @@ -192,6 +192,4 @@ object User { lang -> o.lang, title -> o.title) } - - private[user] lazy val tube = lila.db.BsTube(userBSONHandler) } diff --git a/modules/user/src/main/UserRepo.scala b/modules/user/src/main/UserRepo.scala index 70dde6d790..ed95ed5216 100644 --- a/modules/user/src/main/UserRepo.scala +++ b/modules/user/src/main/UserRepo.scala @@ -2,95 +2,93 @@ package lila.user import com.roundeights.hasher.Implicits._ import org.joda.time.DateTime -import play.api.libs.json._ -import play.modules.reactivemongo.json.ImplicitBSONHandlers.JsObjectWriter import reactivemongo.api._ import reactivemongo.bson._ -import lila.common.PimpedJson._ -import lila.db.api._ import lila.db.BSON.BSONJodaDateTimeHandler -import lila.db.Implicits._ +import lila.db.dsl._ import lila.rating.{ Glicko, Perf, PerfType } object UserRepo { - import tube.userTube import User.userBSONHandler import User.ID import User.{ BSONFields => F } - private val coll = userTube.coll + // dirty + private val coll = Env.current.userColl import reactivemongo.api.collections.bson.BSONBatchCommands.AggregationFramework.{ Match, Project, Group, GroupField, SumField, SumValue } val normalize = User normalize _ - def all: Fu[List[User]] = $find.all - def topNbGame(nb: Int): Fu[List[User]] = - $find($query(enabledSelect) sort ($sort desc "count.game"), nb) + coll.find(enabledSelect).sort($sort desc "count.game").cursor[User]().gather[List](nb) - def byId(id: ID): Fu[Option[User]] = $find byId id + def byId(id: ID): Fu[Option[User]] = coll.byId[User](id) - def byIds(ids: Iterable[ID]): Fu[List[User]] = $find byIds ids + def byIds(ids: Iterable[ID]): Fu[List[User]] = coll.byIds[User](ids) - def byEmail(email: String): Fu[Option[User]] = $find one Json.obj(F.email -> email) + def byEmail(email: String): Fu[Option[User]] = coll.uno[User]($doc(F.email -> email)) def idByEmail(email: String): Fu[Option[String]] = - $primitive.one(Json.obj(F.email -> email), "_id")(_.asOpt[String]) + coll.primitiveOne[String]($doc(F.email -> email), "_id") def enabledByEmail(email: String): Fu[Option[User]] = byEmail(email) map (_ filter (_.enabled)) def pair(x: Option[ID], y: Option[ID]): Fu[(Option[User], Option[User])] = - $find byIds List(x, y).flatten map { users => + coll.byIds[User](List(x, y).flatten) map { users => x.??(xx => users.find(_.id == xx)) -> y.??(yy => users.find(_.id == yy)) } - def byOrderedIds(ids: Seq[ID]): Fu[List[User]] = $find byOrderedIds ids + def byOrderedIds(ids: Seq[ID]): Fu[List[User]] = + coll.byOrderedIds[User](ids)(_.id) - def enabledByIds(ids: Iterable[ID]): Fu[List[User]] = $find(enabledSelect ++ $select.byIds(ids)) + def enabledByIds(ids: Iterable[ID]): Fu[List[User]] = + coll.list[User](enabledSelect ++ $inIds(ids)) def enabledById(id: ID): Fu[Option[User]] = - $find.one(enabledSelect ++ $select.byId(id)) + coll.uno[User](enabledSelect ++ $id(id)) - def named(username: String): Fu[Option[User]] = $find byId normalize(username) + def named(username: String): Fu[Option[User]] = coll.byId[User](normalize(username)) - def nameds(usernames: List[String]): Fu[List[User]] = $find byIds usernames.map(normalize) + def nameds(usernames: List[String]): Fu[List[User]] = coll.byIds[User](usernames.map(normalize)) // expensive, send to secondary def byIdsSortRating(ids: Iterable[ID], nb: Int) = - coll.find(BSONDocument("_id" -> BSONDocument("$in" -> ids)) ++ goodLadSelectBson) - .sort(BSONDocument(s"perfs.standard.gl.r" -> -1)) + coll.find($inIds(ids) ++ goodLadSelectBson) + .sort($sort desc "perfs.standard.gl.r") .cursor[User](ReadPreference.secondaryPreferred) - .collect[List](nb) + .gather[List](nb) // expensive, send to secondary def idsByIdsSortRating(ids: Iterable[ID], nb: Int): Fu[List[User.ID]] = coll.find( - BSONDocument("_id" -> BSONDocument("$in" -> ids)) ++ goodLadSelectBson, - BSONDocument("_id" -> true)) - .sort(BSONDocument(s"perfs.standard.gl.r" -> -1)) - .cursor[BSONDocument](ReadPreference.secondaryPreferred) - .collect[List](nb).map { + $inIds(ids) ++ goodLadSelectBson, + $doc("_id" -> true)) + .sort($doc(s"perfs.standard.gl.r" -> -1)) + .cursor[Bdoc](ReadPreference.secondaryPreferred) + .gather[List](nb).map { _.flatMap { _.getAs[String]("_id") } } - def allSortToints(nb: Int) = $find($query.all sort ($sort desc F.toints), nb) + def allSortToints(nb: Int) = + coll.find($empty).sort($sort desc F.toints).cursor[User]().gather[List](nb) - def usernameById(id: ID) = $primitive.one($select(id), F.username)(_.asOpt[String]) + def usernameById(id: ID) = + coll.primitiveOne[String]($id(id), F.username) def usernamesByIds(ids: List[ID]) = - coll.distinct(F.username, BSONDocument("_id" -> BSONDocument("$in" -> ids)).some) map lila.db.BSON.asStrings + coll.distinct(F.username, $inIds(ids).some) map lila.db.BSON.asStrings def orderByGameCount(u1: String, u2: String): Fu[Option[(String, String)]] = { coll.find( - BSONDocument("_id" -> BSONDocument("$in" -> BSONArray(u1, u2))), - BSONDocument(s"${F.count}.game" -> true) - ).cursor[BSONDocument]().collect[List]() map { docs => + $doc("_id".$in(u1, u2)), + $doc(s"${F.count}.game" -> true) + ).cursor[Bdoc]().gather[List]() map { docs => docs.sortBy { - _.getAs[BSONDocument](F.count).flatMap(_.getAs[BSONNumberLike]("game")).??(_.toInt) + _.getAs[Bdoc](F.count).flatMap(_.getAs[BSONNumberLike]("game")).??(_.toInt) }.map(_.getAs[String]("_id")).flatten match { case List(u1, u2) => (u1, u2).some case _ => none @@ -101,15 +99,15 @@ object UserRepo { def firstGetsWhite(u1O: Option[String], u2O: Option[String]): Fu[Boolean] = (u1O |@| u2O).tupled.fold(fuccess(scala.util.Random.nextBoolean)) { case (u1, u2) => coll.find( - BSONDocument("_id" -> BSONDocument("$in" -> BSONArray(u1, u2))), - BSONDocument("_id" -> true) - ).sort(BSONDocument(F.colorIt -> 1)).one[BSONDocument].map { + $doc("_id".$in(u1, u2)), + $doc("_id" -> true) + ).sort($doc(F.colorIt -> 1)).uno[Bdoc].map { _.fold(scala.util.Random.nextBoolean) { doc => doc.getAs[String]("_id") contains u1 } }.addEffect { v => - $update.unchecked($select(u1), $incBson(F.colorIt -> v.fold(1, -1))) - $update.unchecked($select(u2), $incBson(F.colorIt -> v.fold(-1, 1))) + coll.uncheckedUpdate($id(u1), $inc(F.colorIt -> v.fold(1, -1))) + coll.uncheckedUpdate($id(u2), $inc(F.colorIt -> v.fold(-1, 1))) } } @@ -122,47 +120,49 @@ object UserRepo { s"perfs.${pt.key}" -> Perf.perfBSONHandler.write(perfs(pt)) } } - diff.nonEmpty ?? $update( - $select(user.id), - BSONDocument("$set" -> BSONDocument(diff)) - ) + diff.nonEmpty ?? coll.update( + $id(user.id), + $doc("$set" -> $doc(diff)) + ).void } - def setPerf(userId: String, perfName: String, perf: Perf) = $update($select(userId), $setBson( - s"${F.perfs}.$perfName" -> Perf.perfBSONHandler.write(perf) - )) + def setPerf(userId: String, perfName: String, perf: Perf) = + coll.update($id(userId), $set( + s"${F.perfs}.$perfName" -> Perf.perfBSONHandler.write(perf) + )).void def setProfile(id: ID, profile: Profile): Funit = - $update($select(id), $setBson(F.profile -> Profile.profileBSONHandler.write(profile))) + coll.update( + $id(id), + $set(F.profile -> Profile.profileBSONHandler.write(profile)) + ).void def setTitle(id: ID, title: Option[String]): Funit = title match { - case Some(t) => $update.field(id, F.title, t) - case None => $update($select(id), $unset(F.title)) + case Some(t) => coll.updateField($id(id), F.title, t).void + case None => coll.update($id(id), $unset(F.title)).void } def setPlayTime(u: User, playTime: User.PlayTime): Funit = - $update($select(u.id), $setBson(F.playTime -> User.playTimeHandler.write(playTime))) + coll.update($id(u.id), $set(F.playTime -> User.playTimeHandler.write(playTime))).void - val enabledSelect = Json.obj(F.enabled -> true) - def engineSelect(v: Boolean) = Json.obj(F.engine -> v.fold(JsBoolean(true), $ne(true))) - def trollSelect(v: Boolean) = Json.obj(F.troll -> v.fold(JsBoolean(true), $ne(true))) - def boosterSelect(v: Boolean) = Json.obj(F.booster -> v.fold(JsBoolean(true), $ne(true))) - def stablePerfSelect(perf: String) = Json.obj( + val enabledSelect = $doc(F.enabled -> true) + def engineSelect(v: Boolean) = $doc(F.engine -> v.fold[BSONValue]($boolean(true), $ne(true))) + def trollSelect(v: Boolean) = $doc(F.troll -> v.fold[BSONValue]($boolean(true), $ne(true))) + def boosterSelect(v: Boolean) = $doc(F.booster -> v.fold[BSONValue]($boolean(true), $ne(true))) + def stablePerfSelect(perf: String) = $doc( s"perfs.$perf.nb" -> $gte(30), s"perfs.$perf.gl.d" -> $lt(lila.rating.Glicko.provisionalDeviation)) val goodLadSelect = enabledSelect ++ engineSelect(false) ++ boosterSelect(false) - val goodLadSelectBson = BSONDocument( + val goodLadSelectBson = $doc( F.enabled -> true, - F.engine -> BSONDocument("$ne" -> true), - F.booster -> BSONDocument("$ne" -> true)) - - val goodLadQuery = $query(goodLadSelect) + F.engine -> $doc("$ne" -> true), + F.booster -> $doc("$ne" -> true)) def sortPerfDesc(perf: String) = $sort desc s"perfs.$perf.gl.r" val sortCreatedAtDesc = $sort desc F.createdAt def incNbGames(id: ID, rated: Boolean, ai: Boolean, result: Int, totalTime: Option[Int], tvTime: Option[Int]) = { - val incs = List( + val incs: List[(String, BSONInteger)] = List( "count.game".some, rated option "count.rated", ai option "count.ai", @@ -178,166 +178,153 @@ object UserRepo { case 0 => "count.drawH".some case _ => none }) ifFalse ai - ).flatten.map(_ -> 1) ::: List( - totalTime map (s"${F.playTime}.total" -> _), - tvTime map (s"${F.playTime}.tv" -> _) + ).flatten.map(_ -> BSONInteger(1)) ::: List( + totalTime map BSONInteger.apply map (s"${F.playTime}.total" -> _), + tvTime map BSONInteger.apply map (s"${F.playTime}.tv" -> _) ).flatten - $update($select(id), $incBson(incs: _*)) + coll.update($id(id), $inc(incs)) } - def incToints(id: ID, nb: Int) = $update($select(id), $incBson("toints" -> nb)) - def removeAllToints = $update($select.all, $unset("toints"), multi = true) + def incToints(id: ID, nb: Int) = coll.update($id(id), $inc("toints" -> nb)) + def removeAllToints = coll.update($empty, $unset("toints"), multi = true) def authenticateById(id: ID, password: String): Fu[Option[User]] = - checkPasswordById(id, password) flatMap { _ ?? ($find byId id) } + checkPasswordById(id, password) flatMap { _ ?? coll.byId[User](id) } def authenticateByEmail(email: String, password: String): Fu[Option[User]] = checkPasswordByEmail(email, password) flatMap { _ ?? byEmail(email) } - private case class AuthData(password: String, salt: String, enabled: Boolean, sha512: Boolean) { - def compare(p: String) = password == sha512.fold(hash512(p, salt), hash(p, salt)) + private case class AuthData(password: String, salt: String, enabled: Boolean, sha512: Option[Boolean]) { + def compare(p: String) = password == (~sha512).fold(hash512(p, salt), hash(p, salt)) } - private object AuthData { - - import lila.db.JsTube.Helpers._ - import play.api.libs.json._ - - private def defaults = Json.obj("sha512" -> false) - - lazy val reader = (__.json update merge(defaults)) andThen Json.reads[AuthData] - } + private implicit val AuthDataBSONHandler = Macros.handler[AuthData] def checkPasswordById(id: ID, password: String): Fu[Boolean] = - checkPassword($select(id), password) + checkPassword($id(id), password) def checkPasswordByEmail(email: String, password: String): Fu[Boolean] = - checkPassword(Json.obj(F.email -> email), password) + checkPassword($doc(F.email -> email), password) - private def checkPassword(select: JsObject, password: String): Fu[Boolean] = - $projection.one(select, Seq("password", "salt", "enabled", "sha512", "email")) { obj => - (AuthData.reader reads obj).asOpt - } map { + private def checkPassword(select: Bdoc, password: String): Fu[Boolean] = + coll.uno[AuthData](select) map { _ ?? (data => data.enabled && data.compare(password)) } def getPasswordHash(id: ID): Fu[Option[String]] = - $primitive.one($select(id), "password")(_.asOpt[String]) + coll.primitiveOne[String]($id(id), "password") def create(username: String, password: String, email: Option[String], blind: Boolean, mobileApiVersion: Option[Int]): Fu[Option[User]] = !nameExists(username) flatMap { _ ?? { - $insert.bson(newUser(username, password, email, blind, mobileApiVersion)) >> named(normalize(username)) + coll.insert(newUser(username, password, email, blind, mobileApiVersion)) >> + named(normalize(username)) } } def nameExists(username: String): Fu[Boolean] = idExists(normalize(username)) - def idExists(id: String): Fu[Boolean] = $count exists id + def idExists(id: String): Fu[Boolean] = coll exists $id(id) def engineIds: Fu[Set[String]] = - coll.distinct("_id", BSONDocument("engine" -> true).some) map lila.db.BSON.asStringSet + coll.distinct("_id", $doc("engine" -> true).some) map lila.db.BSON.asStringSet def usernamesLike(username: String, max: Int = 10): Fu[List[String]] = { import java.util.regex.Matcher.quoteReplacement val escaped = """^([\w-]*).*$""".r.replaceAllIn(normalize(username), m => quoteReplacement(m group 1)) val regex = "^" + escaped + ".*$" - $primitive( - $select.byId($regex(regex)) ++ enabledSelect, - F.username, - _ sort $sort.desc("_id"), - max.some - )(_.asOpt[String]) + coll.find($doc("_id".$regex(regex, "")), $doc(F.username -> true)) + .sort($sort desc "_id") + .cursor[Bdoc]().gather[List](max) + .map { + _ flatMap { _.getAs[String](F.username) } + } } - def toggleEngine(id: ID): Funit = $update.docBson[ID, User](id) { u => - $setBson("engine" -> BSONBoolean(!u.engine)) - } + def toggleEngine(id: ID): Funit = + coll.fetchUpdate[User]($id(id)) { u => + $set("engine" -> !u.engine) + } - def setEngine(id: ID, v: Boolean): Funit = $update.field(id, "engine", v) + def setEngine(id: ID, v: Boolean): Funit = coll.updateField($id(id), "engine", v).void - def setBooster(id: ID, v: Boolean): Funit = $update.field(id, "booster", v) + def setBooster(id: ID, v: Boolean): Funit = coll.updateField($id(id), "booster", v).void - def toggleIpBan(id: ID) = $update.doc[ID, User](id) { u => $set("ipBan" -> !u.ipBan) } + def toggleIpBan(id: ID) = coll.fetchUpdate[User]($id(id)) { u => $set("ipBan" -> !u.ipBan) } - def toggleKid(user: User) = $update.field(user.id, "kid", !user.kid) + def toggleKid(user: User) = coll.updateField($id(user.id), "kid", !user.kid) - def updateTroll(user: User) = $update.field(user.id, "troll", user.troll) + def updateTroll(user: User) = coll.updateField($id(user.id), "troll", user.troll) - def isEngine(id: ID): Fu[Boolean] = $count.exists($select(id) ++ engineSelect(true)) + def isEngine(id: ID): Fu[Boolean] = coll.exists($id(id) ++ engineSelect(true)) - def isTroll(id: ID): Fu[Boolean] = $count.exists($select(id) ++ trollSelect(true)) + def isTroll(id: ID): Fu[Boolean] = coll.exists($id(id) ++ trollSelect(true)) - def setRoles(id: ID, roles: List[String]) = $update.field(id, "roles", roles) + def setRoles(id: ID, roles: List[String]) = coll.updateField($id(id), "roles", roles) - def enable(id: ID) = $update.field(id, "enabled", true) + def enable(id: ID) = coll.updateField($id(id), "enabled", true) - def disable(user: User) = $update( - $select(user.id), - BSONDocument("$set" -> BSONDocument("enabled" -> false)) ++ + def disable(user: User) = coll.update( + $id(user.id), + $doc("$set" -> $doc("enabled" -> false)) ++ user.lameOrTroll.fold( - BSONDocument(), - BSONDocument("$unset" -> BSONDocument("email" -> true)) + $doc(), + $doc("$unset" -> $doc("email" -> true)) ) ) def passwd(id: ID, password: String): Funit = - $primitive.one($select(id), "salt")(_.asOpt[String]) flatMap { saltOption => + coll.primitiveOne[String]($id(id), "salt") flatMap { saltOption => saltOption ?? { salt => - $update($select(id), $set(Json.obj( + coll.update($id(id), $set( "password" -> hash(password, salt), - "sha512" -> false))) + "sha512" -> false)).void } } - def email(id: ID, email: String): Funit = $update.field(id, F.email, email) + def email(id: ID, email: String): Funit = coll.updateField($id(id), F.email, email).void - def email(id: ID): Fu[Option[String]] = $primitive.one($select(id), F.email)(_.asOpt[String]) + def email(id: ID): Fu[Option[String]] = coll.primitiveOne[String]($id(id), F.email) def hasEmail(id: ID): Fu[Boolean] = email(id).map(_.isDefined) def perfOf(id: ID, perfType: PerfType): Fu[Option[Perf]] = coll.find( - BSONDocument("_id" -> id), - BSONDocument(s"${F.perfs}.${perfType.key}" -> true) - ).one[BSONDocument].map { - _.flatMap(_.getAs[BSONDocument](F.perfs)).flatMap(_.getAs[Perf](perfType.key)) + $doc("_id" -> id), + $doc(s"${F.perfs}.${perfType.key}" -> true) + ).uno[Bdoc].map { + _.flatMap(_.getAs[Bdoc](F.perfs)).flatMap(_.getAs[Perf](perfType.key)) } def setSeenAt(id: ID) { - $update.fieldUnchecked(id, "seenAt", $date(DateTime.now)) + coll.updateFieldUnchecked($id(id), "seenAt", DateTime.now) } def recentlySeenNotKidIdsCursor(since: DateTime) = - coll.find(BSONDocument( + coll.find($doc( F.enabled -> true, - "seenAt" -> BSONDocument("$gt" -> since), - "count.game" -> BSONDocument("$gt" -> 9), - "kid" -> BSONDocument("$ne" -> true) - ), BSONDocument("_id" -> true)).cursor[BSONDocument]() + "seenAt" -> $doc("$gt" -> since), + "count.game" -> $doc("$gt" -> 9), + "kid" -> $doc("$ne" -> true) + ), $doc("_id" -> true)).cursor[Bdoc]() - def setLang(id: ID, lang: String) = $update.field(id, "lang", lang) + def setLang(id: ID, lang: String) = coll.updateField($id(id), "lang", lang).void def idsSumToints(ids: Iterable[String]): Fu[Int] = - ids.nonEmpty ?? coll.aggregate(Match(BSONDocument("_id" -> BSONDocument("$in" -> ids))), + ids.nonEmpty ?? coll.aggregate(Match($inIds(ids)), List(Group(BSONNull)(F.toints -> SumField(F.toints)))).map( _.documents.headOption flatMap { _.getAs[Int](F.toints) } ).map(~_) def filterByEngine(userIds: List[String]): Fu[List[String]] = - $primitive(Json.obj("_id" -> $in(userIds)) ++ engineSelect(true), F.username)(_.asOpt[String]) + coll.primitive[String]($inIds(userIds) ++ engineSelect(true), F.username) def countEngines(userIds: List[String]): Fu[Int] = - coll.count(BSONDocument( - "_id" -> BSONDocument("$in" -> userIds), - F.engine -> true - ).some) + coll.countSel($inIds(userIds) ++ engineSelect(true)) def mustConfirmEmail(id: String): Fu[Boolean] = - $count.exists($select(id) ++ Json.obj(F.mustConfirmEmail -> $exists(true))) + coll.exists($id(id) ++ $doc(F.mustConfirmEmail $exists true)) - def setEmailConfirmed(id: String): Funit = $update( - $select(id), - BSONDocument("$unset" -> BSONDocument(F.mustConfirmEmail -> true))) + def setEmailConfirmed(id: String): Funit = coll.update($id(id), $unset(F.mustConfirmEmail)).void private def newUser(username: String, password: String, email: Option[String], blind: Boolean, mobileApiVersion: Option[Int]) = { @@ -346,20 +333,20 @@ object UserRepo { implicit def perfsHandler = Perfs.perfsBSONHandler import lila.db.BSON.BSONJodaDateTimeHandler - BSONDocument( + $doc( F.id -> normalize(username), F.username -> username, F.email -> email, F.mustConfirmEmail -> (email.isDefined && mobileApiVersion.isEmpty).option(DateTime.now), "password" -> hash(password, salt), "salt" -> salt, - F.perfs -> Json.obj(), + F.perfs -> $empty, F.count -> Count.default, F.enabled -> true, F.createdAt -> DateTime.now, F.createdWithApiVersion -> mobileApiVersion, F.seenAt -> DateTime.now) ++ { - if (blind) BSONDocument("blind" -> true) else BSONDocument() + if (blind) $doc("blind" -> true) else $empty } } diff --git a/modules/user/src/main/package.scala b/modules/user/src/main/package.scala index a48030ff9b..aead83eec8 100644 --- a/modules/user/src/main/package.scala +++ b/modules/user/src/main/package.scala @@ -2,12 +2,6 @@ package lila package object user extends PackageObject with WithPlay { - object tube { - - // expose user tube - implicit lazy val userTube = User.tube inColl Env.current.userColl - } - private[user] def logger = lila.log("user") type Trophies = List[Trophy] diff --git a/modules/video/src/main/VideoApi.scala b/modules/video/src/main/VideoApi.scala index 4f8374a224..2b3f57a46f 100644 --- a/modules/video/src/main/VideoApi.scala +++ b/modules/video/src/main/VideoApi.scala @@ -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)).uno[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" $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,37 +93,33 @@ 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 => + .gather[List]().map { videos => videos.sortBy { v => -v.similarity(video) } take max } flatMap videoViews(user) @@ -142,25 +138,23 @@ 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] + )).uno[View] def add(a: View) = (viewColl insert a).void recover 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])), diff --git a/modules/wiki/src/main/Api.scala b/modules/wiki/src/main/Api.scala index cb16726a31..09ae21ab8c 100644 --- a/modules/wiki/src/main/Api.scala +++ b/modules/wiki/src/main/Api.scala @@ -1,23 +1,19 @@ package lila.wiki import Page.DefaultLang -import play.api.libs.json._ -import lila.common.PimpedJson._ -import lila.db.api._ -import lila.db.Implicits._ -import tube.pageTube +import lila.db.dsl._ -private[wiki] final class Api { +private[wiki] final class Api(coll: Coll) { + + import Page.PageBSONHandler def show(slug: String, lang: String): Fu[Option[(Page, List[Page])]] = for { - page ← $find.one(Json.obj("slug" -> slug, "lang" -> lang)) zip - $find.one(Json.obj("slug" -> slug, "lang" -> DefaultLang)) map { - case (a, b) => a orElse b - } - pages ← $find($query(Json.obj( - "lang" -> $in(Seq(lang, DefaultLang)) - )).sort($sort asc "number")) + page ← coll.uno[Page]($doc("slug" -> slug, "lang" -> lang)) orElse + coll.uno[Page]($doc("slug" -> slug, "lang" -> DefaultLang)) + pages ← coll.find($doc( + "lang" $in (lang, DefaultLang) + )).sort($sort asc "number").cursor[Page]().gather[List]() } yield page map { _ -> makeMenu(pages) } private def makeMenu(pages: List[Page]): List[Page] = { diff --git a/modules/wiki/src/main/Env.scala b/modules/wiki/src/main/Env.scala index f0fc887eec..dabe44df9d 100644 --- a/modules/wiki/src/main/Env.scala +++ b/modules/wiki/src/main/Env.scala @@ -2,8 +2,7 @@ package lila.wiki import com.typesafe.config.Config -import lila.db.api.$find -import tube.pageTube +import lila.db.dsl._ final class Env(config: Config, db: lila.db.Env) { @@ -11,11 +10,14 @@ final class Env(config: Config, db: lila.db.Env) { private val GitUrl = config getString "git.url" private val MarkdownPath = config getString "markdown_path" - lazy val api = new Api + private lazy val pageColl = db(CollectionPage) - private lazy val fetcher = new Fetch(gitUrl = GitUrl, markdownPath = MarkdownPath)(pageColl) + lazy val api = new Api(pageColl) - private[wiki] lazy val pageColl = db(CollectionPage) + private lazy val fetcher = new Fetch( + coll = pageColl, + gitUrl = GitUrl, + markdownPath = MarkdownPath) def cli = new lila.common.Cli { def process = { diff --git a/modules/wiki/src/main/Fetch.scala b/modules/wiki/src/main/Fetch.scala index 05db6790d2..9199a629ed 100644 --- a/modules/wiki/src/main/Fetch.scala +++ b/modules/wiki/src/main/Fetch.scala @@ -9,11 +9,14 @@ import org.eclipse.jgit.api.Git import org.eclipse.jgit.lib.Repository import Page.DefaultLang -import lila.db.api._ -import lila.db.Types.Coll -import tube._ +import lila.db.dsl._ -private[wiki] final class Fetch(gitUrl: String, markdownPath: String)(implicit coll: Coll) { +private[wiki] final class Fetch( + coll: Coll, + gitUrl: String, + markdownPath: String) { + + import Page.PageBSONHandler def apply: Funit = getFiles flatMap { files => val (defaultPages, langPages) = files.map(filePage).flatten partition (_.isDefaultLang) @@ -22,7 +25,9 @@ private[wiki] final class Fetch(gitUrl: String, markdownPath: String)(implicit c page.copy(slug = default.slug) } }).flatten - $remove($select.all) >> (newLangPages ::: defaultPages).map($insert(_)).sequenceFu.void + coll.remove($empty) >> (newLangPages ::: defaultPages).map { page => + coll.insert[Page](page) + }.sequenceFu.void } private def filePage(file: File): Option[Page] = { diff --git a/modules/wiki/src/main/Page.scala b/modules/wiki/src/main/Page.scala index afd4fac2bb..a4852db959 100644 --- a/modules/wiki/src/main/Page.scala +++ b/modules/wiki/src/main/Page.scala @@ -34,11 +34,6 @@ object Page { case _ => none } - import lila.db.JsTube - import play.api.libs.json._ - - private[wiki] lazy val tube = JsTube(Json.reads[Page], Json.writes[Page]) - // does not lowercase private def slugify(input: String) = { val nowhitespace = input.replace(" ", "_") @@ -48,5 +43,7 @@ object Page { private def dropNumber(input: String) = """^\d+_(.+)$""".r.replaceAllIn(input, m => quoteReplacement(m group 1)) -} + import lila.db.dsl.BSONJodaDateTimeHandler + implicit val PageBSONHandler = reactivemongo.bson.Macros.handler[Page] +} diff --git a/modules/wiki/src/main/package.scala b/modules/wiki/src/main/package.scala index d36bbd6f3a..6f41d410b8 100644 --- a/modules/wiki/src/main/package.scala +++ b/modules/wiki/src/main/package.scala @@ -1,9 +1,3 @@ package lila -package object wiki extends PackageObject with WithPlay { - - object tube { - - private[wiki] implicit lazy val pageTube = Page.tube inColl Env.current.pageColl - } -} +package object wiki extends PackageObject with WithPlay diff --git a/modules/worldMap/src/main/Env.scala b/modules/worldMap/src/main/Env.scala index c278eb5627..8a6cb75026 100644 --- a/modules/worldMap/src/main/Env.scala +++ b/modules/worldMap/src/main/Env.scala @@ -13,19 +13,20 @@ final class Env( private val GeoIPFile = config getString "geoip.file" private val GeoIPCacheTtl = config duration "geoip.cache_ttl" - private val stream = system.actorOf( + val stream = system.actorOf( Props(new Stream( geoIp = MaxMindIpGeo(GeoIPFile, 0), geoIpCacheTtl = GeoIPCacheTtl))) - system.lilaBus.subscribe(stream, 'changeFeaturedGame, 'streams, 'nbMembers, 'nbRounds) - - def getStream = { - import play.api.libs.iteratee._ - import play.api.libs.json._ - import akka.pattern.ask - import makeTimeout.short - stream ? Stream.Get mapTo manifest[Enumerator[JsValue]] - } + + system.lilaBus.subscribe(stream, 'roundDoor) + + import akka.pattern.ask + import akka.stream.scaladsl.Source + import scala.concurrent.duration._ + import play.api.libs.json.JsValue + implicit val timeout = akka.util.Timeout(5 seconds) + def getSource: Fu[Stream.SourceType] = + stream ? Stream.GetSource mapTo manifest[Stream.SourceType] } object Env { diff --git a/modules/worldMap/src/main/Stream.scala b/modules/worldMap/src/main/Stream.scala index fcdb91c520..ff52c15d7d 100644 --- a/modules/worldMap/src/main/Stream.scala +++ b/modules/worldMap/src/main/Stream.scala @@ -1,10 +1,11 @@ package lila.worldMap import akka.actor._ +import akka.stream._ +import akka.stream.scaladsl._ import com.sanoma.cda.geoip.{ MaxMindIpGeo, IpLocation } import java.security.MessageDigest import lila.hub.actorApi.round.SocketEvent -import play.api.libs.iteratee._ import play.api.libs.json._ import scala.concurrent.duration._ @@ -14,49 +15,58 @@ private final class Stream( geoIp: MaxMindIpGeo, geoIpCacheTtl: Duration) extends Actor { - import Stream.game2json + import Stream._ + implicit val mat = context.system.lilaMat - val games = scala.collection.mutable.Map.empty[String, Stream.Game] + val games = scala.collection.mutable.Map.empty[String, Game] - private def makeMd5 = MessageDigest getInstance "MD5" + val (out, publisher) = lila.common.AkkaStream.actorPublisher[Event](100) - private val loadCompleteJson = Json.obj("loadComplete" -> true) + def preload = Source[JsValue](games.values.pp.map(game2json(makeMd5)).toList.pp) + val preloadComplete = Source[JsValue](List(Json.obj("loadComplete" -> true))) + + val transformer = Flow.fromFunction[Event, JsValue] { + case Event.Add(game) => game2json(makeMd5)(game) + case Event.Remove(id) => Json.obj("id" -> id) + } + + def realtime = Source.fromPublisher[Event](publisher) + + def makeSource = + Source.combine(preload, preloadComplete, realtime via transformer)(_ => Concat()) def receive = { + case SocketEvent.OwnerJoin(id, color, ip) => - ipCache get ip foreach { point => + (ipCache get "58.8.28.97").pp foreach { point => val game = games get id match { case Some(game) => game withPoint point - case None => Stream.Game(id, List(point)) + case None => Game(id, List(point)) } games += (id -> game) - channel push Stream.Event.Add(game) + out ! Event.Add(game).pp } + case SocketEvent.Stop(id) => games -= id - channel push Stream.Event.Remove(id) - case Stream.Get => sender ! { - Enumerator.enumerate(games.values.map(game2json(makeMd5))) andThen - Enumerator.enumerate(List(loadCompleteJson)) andThen - producer - } - } + out ! Event.Remove(id) - val (enumerator, channel) = Concurrent.broadcast[Stream.Event] - - val producer = enumerator &> Enumeratee.map[Stream.Event].apply[JsValue] { - case Stream.Event.Add(game) => game2json(makeMd5)(game) - case Stream.Event.Remove(id) => Json.obj("id" -> id) + case GetSource => + println(games) + sender ! makeSource } + def makeMd5 = MessageDigest getInstance "MD5" val ipCache = lila.memo.Builder.cache(geoIpCacheTtl, ipToPoint) - def ipToPoint(ip: String): Option[Stream.Point] = - geoIp getLocation ip flatMap Stream.toPoint + def ipToPoint(ip: String): Option[Point] = + geoIp getLocation ip flatMap toPoint } object Stream { - case object Get + case object GetSource + + type SourceType = Source[JsValue, akka.NotUsed] case class Game(id: String, points: List[Point]) { diff --git a/project/Build.scala b/project/Build.scala index 7bcd658085..c493942993 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1,7 +1,8 @@ import com.typesafe.sbt.packager.Keys.scriptClasspath import com.typesafe.sbt.web.SbtWeb.autoImport._ -import play.Play.autoImport._ +import play.sbt.Play.autoImport._ import play.sbt.PlayImport._ +import play.sbt.routes.RoutesKeys._ import play.twirl.sbt.Import._ import PlayKeys._ import sbt._, Keys._ @@ -21,6 +22,7 @@ object ApplicationBuild extends Build { scalacOptions := compilerOptions, incOptions := incOptions.value.withNameHashing(true), updateOptions := updateOptions.value.withCachedResolution(true), + routesGenerator := StaticRoutesGenerator, sources in doc in Compile := List(), // disable publishing the main API jar publishArtifact in (Compile, packageDoc) := false, @@ -33,9 +35,10 @@ object ApplicationBuild extends Build { // offline := true, libraryDependencies ++= Seq( scalaz, scalalib, hasher, config, apache, - jgit, findbugs, RM, PRM, akka.actor, akka.slf4j, + jgit, findbugs, RM, akka.actor, akka.slf4j, akka.stream, spray.caching, maxmind, prismic, - kamon.core, kamon.statsd, pushy, java8compat, semver), + kamon.core, kamon.statsd, pushy, java8compat, semver, + jzlib), TwirlKeys.templateImports ++= Seq( "lila.game.{ Game, Player, Pov }", "lila.tournament.Tournament", @@ -74,23 +77,23 @@ object ApplicationBuild extends Build { lazy val puzzle = project("puzzle", Seq( common, memo, hub, db, user, rating)).settings( - libraryDependencies ++= provided(play.api, RM, PRM) + libraryDependencies ++= provided(play.api, RM) ) lazy val quote = project("quote", Seq()) lazy val opening = project("opening", Seq( common, memo, hub, db, user)).settings( - libraryDependencies ++= provided(play.api, RM, PRM) + libraryDependencies ++= provided(play.api, RM) ) lazy val video = project("video", Seq( common, memo, hub, db, user)).settings( - libraryDependencies ++= provided(play.api, RM, PRM) + libraryDependencies ++= provided(play.api, RM) ) lazy val coordinate = project("coordinate", Seq(common, db)).settings( - libraryDependencies ++= provided(play.api, RM, PRM) + libraryDependencies ++= provided(play.api, RM) ) lazy val worldMap = project("worldMap", Seq(common, hub, memo, rating)).settings( @@ -98,21 +101,21 @@ object ApplicationBuild extends Build { ) lazy val qa = project("qa", Seq(common, db, memo, user, security)).settings( - libraryDependencies ++= provided(play.api, RM, PRM) + libraryDependencies ++= provided(play.api, RM) ) lazy val blog = project("blog", Seq(common, memo, user, message)).settings( - libraryDependencies ++= provided(play.api, RM, PRM, prismic) + libraryDependencies ++= provided(play.api, RM, prismic) ) lazy val donation = project("donation", Seq( common, db, user)).settings( - libraryDependencies ++= provided(play.api, RM, PRM) + libraryDependencies ++= provided(play.api, RM) ) lazy val evaluation = project("evaluation", Seq( common, hub, db, user, game, analyse)).settings( - libraryDependencies ++= provided(play.api, RM, PRM) + libraryDependencies ++= provided(play.api, RM) ) // lazy val simulation = project("simulation", Seq( @@ -125,19 +128,19 @@ object ApplicationBuild extends Build { ) lazy val rating = project("rating", Seq(common, db, chess)).settings( - libraryDependencies ++= provided(play.api, RM, PRM) + libraryDependencies ++= provided(play.api, RM) ) lazy val perfStat = project("perfStat", Seq(common, db, chess, user, game, rating)).settings( - libraryDependencies ++= provided(play.api, RM, PRM) + libraryDependencies ++= provided(play.api, RM) ) lazy val history = project("history", Seq(common, db, memo, game, user)).settings( - libraryDependencies ++= provided(play.api, RM, PRM) + libraryDependencies ++= provided(play.api, RM) ) lazy val db = project("db", Seq(common)).settings( - libraryDependencies ++= provided(play.test, play.api, RM, PRM) + libraryDependencies ++= provided(play.test, play.api, RM) ) lazy val memo = project("memo", Seq(common, db)).settings( @@ -149,138 +152,138 @@ object ApplicationBuild extends Build { ) lazy val chat = project("chat", Seq(common, db, user, security, i18n)).settings( - libraryDependencies ++= provided(play.api, RM, PRM) + libraryDependencies ++= provided(play.api, RM) ) lazy val timeline = project("timeline", Seq(common, db, game, user, hub, security, relation)).settings( libraryDependencies ++= provided( - play.api, play.test, RM, PRM) + play.api, play.test, RM) ) lazy val mod = project("mod", Seq(common, db, user, hub, security, game, analyse, evaluation, report)).settings( libraryDependencies ++= provided( - play.api, play.test, RM, PRM) + play.api, play.test, RM) ) lazy val user = project("user", Seq(common, memo, db, hub, chess, rating)).settings( libraryDependencies ++= provided( - play.api, play.test, RM, PRM, hasher) + play.api, play.test, RM, hasher) ) lazy val game = project("game", Seq(common, memo, db, hub, user, chess, chat)).settings( libraryDependencies ++= provided( - play.api, RM, PRM) + play.api, RM) ) lazy val gameSearch = project("gameSearch", Seq(common, hub, chess, search, game)).settings( libraryDependencies ++= provided( - play.api, RM, PRM) + play.api, RM) ) lazy val tv = project("tv", Seq(common, db, hub, socket, game, user, chess)).settings( - libraryDependencies ++= provided(play.api, RM, PRM, hasher) + libraryDependencies ++= provided(play.api, RM, hasher) ) lazy val analyse = project("analyse", Seq(common, hub, chess, game, user)).settings( libraryDependencies ++= provided( - play.api, RM, PRM, spray.caching) + play.api, RM, spray.caching) ) lazy val round = project("round", Seq( common, db, memo, hub, socket, chess, game, user, i18n, fishnet, pref, chat, history, playban)).settings( - libraryDependencies ++= provided(play.api, RM, PRM, hasher, kamon.core) + libraryDependencies ++= provided(play.api, RM, hasher, kamon.core) ) lazy val lobby = project("lobby", Seq( common, db, memo, hub, socket, chess, game, user, round, timeline, relation, playban, security)).settings( - libraryDependencies ++= provided(play.api, RM, PRM) + libraryDependencies ++= provided(play.api, RM) ) lazy val setup = project("setup", Seq( common, db, memo, hub, socket, chess, game, user, lobby, pref, relation)).settings( - libraryDependencies ++= provided(play.api, RM, PRM) + libraryDependencies ++= provided(play.api, RM) ) lazy val importer = project("importer", Seq(common, chess, game, round)).settings( - libraryDependencies ++= provided(play.api, RM, PRM) + libraryDependencies ++= provided(play.api, RM) ) lazy val insight = project("insight", Seq(common, chess, game, user, analyse, relation, pref, socket, round, security) ).settings( - libraryDependencies ++= provided(play.api, RM, PRM) + libraryDependencies ++= provided(play.api, RM) ) lazy val tournament = project("tournament", Seq( common, hub, socket, chess, game, round, security, chat, memo, quote)).settings( - libraryDependencies ++= provided(play.api, RM, PRM) + libraryDependencies ++= provided(play.api, RM) ) lazy val simul = project("simul", Seq( common, hub, socket, chess, game, round, chat, memo, quote)).settings( - libraryDependencies ++= provided(play.api, RM, PRM) + libraryDependencies ++= provided(play.api, RM) ) lazy val fishnet = project("fishnet", Seq(common, chess, game, analyse, db)).settings( - libraryDependencies ++= provided(play.api, RM, PRM, semver) + libraryDependencies ++= provided(play.api, RM, semver) ) lazy val security = project("security", Seq(common, hub, db, user)).settings( - libraryDependencies ++= provided(play.api, RM, PRM, maxmind, hasher) + libraryDependencies ++= provided(play.api, RM, maxmind, hasher) ) lazy val shutup = project("shutup", Seq(common, db, hub, game, relation)).settings( - libraryDependencies ++= provided(play.api, RM, PRM) + libraryDependencies ++= provided(play.api, RM) ) lazy val challenge = project("challenge", Seq(common, db, hub, setup, game)).settings( - libraryDependencies ++= provided(play.api, RM, PRM) + libraryDependencies ++= provided(play.api, RM) ) lazy val playban = project("playban", Seq(common, db, game)).settings( - libraryDependencies ++= provided(play.api, RM, PRM) + libraryDependencies ++= provided(play.api, RM) ) lazy val push = project("push", Seq(common, db, user, game, challenge)).settings( - libraryDependencies ++= provided(play.api, RM, PRM, pushy) + libraryDependencies ++= provided(play.api, RM, pushy) ) lazy val slack = project("slack", Seq(common, hub, user)).settings( - libraryDependencies ++= provided(play.api, RM, PRM) + libraryDependencies ++= provided(play.api, RM) ) lazy val relation = project("relation", Seq(common, db, memo, hub, user, game, pref)).settings( - libraryDependencies ++= provided(play.api, RM, PRM) + libraryDependencies ++= provided(play.api, RM) ) lazy val pref = project("pref", Seq(common, db, user)).settings( - libraryDependencies ++= provided(play.api, RM, PRM) + libraryDependencies ++= provided(play.api, RM) ) lazy val message = project("message", Seq(common, db, user, hub, relation, security)).settings( libraryDependencies ++= provided( - play.api, RM, PRM, spray.caching) + play.api, RM, spray.caching) ) lazy val forum = project("forum", Seq(common, db, user, security, hub, mod)).settings( libraryDependencies ++= provided( - play.api, RM, PRM, spray.caching) + play.api, RM, spray.caching) ) lazy val forumSearch = project("forumSearch", Seq(common, hub, forum, search)).settings( libraryDependencies ++= provided( - play.api, RM, PRM) + play.api, RM) ) lazy val team = project("team", Seq(common, memo, db, user, forum, security, hub)).settings( libraryDependencies ++= provided( - play.api, RM, PRM) + play.api, RM) ) lazy val teamSearch = project("teamSearch", Seq(common, hub, team, search)).settings( libraryDependencies ++= provided( - play.api, RM, PRM) + play.api, RM) ) lazy val i18n = project("i18n", Seq(common, db, user, hub)).settings( @@ -290,26 +293,26 @@ object ApplicationBuild extends Build { (sourceManaged in Compile).value / "messages" ) }.taskValue, - libraryDependencies ++= provided(play.api, RM, PRM, jgit) + libraryDependencies ++= provided(play.api, RM, jgit) ) lazy val bookmark = project("bookmark", Seq(common, memo, db, hub, user, game)).settings( libraryDependencies ++= provided( - play.api, play.test, RM, PRM) + play.api, play.test, RM) ) lazy val wiki = project("wiki", Seq(common, db)).settings( libraryDependencies ++= provided( - play.api, RM, PRM, jgit, guava) + play.api, RM, jgit, guava) ) lazy val report = project("report", Seq(common, db, user)).settings( libraryDependencies ++= provided( - play.api, RM, PRM) + play.api, RM) ) lazy val explorer = project("explorer", Seq(common, db, game)).settings( - libraryDependencies ++= provided(play.api, RM, PRM) + libraryDependencies ++= provided(play.api, RM) ) lazy val notification = project("notification", Seq(common, user, hub)).settings( @@ -321,7 +324,7 @@ object ApplicationBuild extends Build { ) lazy val socket = project("socket", Seq(common, hub, memo)).settings( - libraryDependencies ++= provided(play.api) + libraryDependencies ++= provided(play.api, akka.stream) ) lazy val hub = project("hub", Seq(common, chess)).settings( diff --git a/project/BuildSettings.scala b/project/BuildSettings.scala index 3b1851d648..e696d4aa5e 100644 --- a/project/BuildSettings.scala +++ b/project/BuildSettings.scala @@ -1,4 +1,4 @@ -import play.Play.autoImport._ +import play.sbt.Play.autoImport._ import sbt._, Keys._ object BuildSettings { diff --git a/project/Dependencies.scala b/project/Dependencies.scala index f9bb25f3f9..52890c6819 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -35,15 +35,15 @@ object Dependencies { val jgit = "org.eclipse.jgit" % "org.eclipse.jgit" % "3.2.0.201312181205-r" val jodaTime = "joda-time" % "joda-time" % "2.9.2" val RM = "org.reactivemongo" % "reactivemongo_2.11" % "0.11.9.1-LILA" - val PRM = "org.reactivemongo" % "play2-reactivemongo_2.11" % "0.11.9" val maxmind = "com.sanoma.cda" %% "maxmind-geoip2-scala" % "1.2.3-THIB" - val prismic = "io.prismic" %% "scala-kit" % "1.2.11-THIB" + val prismic = "io.prismic" %% "scala-kit" % "1.2.11-THIB2" val pushy = "com.relayrides" % "pushy" % "0.4.3" val java8compat = "org.scala-lang.modules" %% "scala-java8-compat" % "0.7.0" val semver = "com.gilt" %% "gfc-semver" % "0.0.2-9-g11173e1" + val jzlib = "com.jcraft" % "jzlib" % "1.1.3" object play { - val version = "2.4.6" + val version = "2.5.1" val api = "com.typesafe.play" %% "play" % version val test = "com.typesafe.play" %% "play-test" % version } @@ -53,9 +53,10 @@ object Dependencies { val util = "io.spray" %% "spray-util" % version } object akka { - val version = "2.4.2" + val version = "2.4.3" val actor = "com.typesafe.akka" %% "akka-actor" % version val slf4j = "com.typesafe.akka" %% "akka-slf4j" % version + val stream = "com.typesafe.akka" %% "akka-stream" % version } object kamon { val version = "0.5.2" diff --git a/project/MessageCompiler.scala b/project/MessageCompiler.scala index 5ba3f536cf..bb7d29052d 100644 --- a/project/MessageCompiler.scala +++ b/project/MessageCompiler.scala @@ -19,9 +19,11 @@ object MessageCompiler { val (lang, file) = entry val srcFile = src / file val dstFile = dst / s"$lang.scala" - val pairs = readLines(srcFile) map makePair - printToFile(dstFile) { - render(lang, pairs) + if (srcFile.lastModified > dstFile.lastModified) { + val pairs = readLines(srcFile) map makePair + printToFile(dstFile) { + render(lang, pairs) + } } dstFile } diff --git a/project/plugins.sbt b/project/plugins.sbt index 6a8deb5e3a..ec045af777 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,3 +1,3 @@ resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/" -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.4.6") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.5.1") diff --git a/public/worldMap/app.js b/public/worldMap/app.js index e4a23e8d04..cdffbcc453 100644 --- a/public/worldMap/app.js +++ b/public/worldMap/app.js @@ -39,7 +39,7 @@ $(function() { point[1] + Math.random() - 0.5); }; - var source = new EventSource("http://en.lichess.org/network/stream"); + var source = new EventSource("http://" + location.hostname + "/network/stream"); var removeFunctions = {};