migration WIP
parent
e0819404db
commit
209d3fed95
|
@ -24,7 +24,7 @@ PlayKeys.externalizeResources := false
|
|||
scriptClasspath := Seq("*")
|
||||
// offline := true
|
||||
libraryDependencies ++= Seq(
|
||||
macwire, play.json, jodaForms, ws,
|
||||
macwire.macros, macwire.util, play.json, jodaForms, ws,
|
||||
scalaz, chess, compression, scalalib, hasher,
|
||||
reactivemongo.driver, reactivemongo.bson, reactivemongo.native,
|
||||
maxmind, prismic, markdown, scalatags,
|
||||
|
|
|
@ -518,7 +518,6 @@ studySearch {
|
|||
paginator.max_per_page = ${study.paginator.max_per_page}
|
||||
}
|
||||
user {
|
||||
paginator.max_per_page = 40
|
||||
cached.nb.ttl = 10 minutes
|
||||
online.ttl = 7 seconds
|
||||
collection {
|
||||
|
|
|
@ -11,10 +11,11 @@ case class Bookmark(game: lila.game.Game, user: lila.user.User)
|
|||
|
||||
final class BookmarkApi(
|
||||
coll: Coll,
|
||||
gameRepo: GameRepo,
|
||||
paginator: PaginatorBuilder
|
||||
) {
|
||||
|
||||
private def exists(gameId: String, userId: User.ID): Fu[Boolean] =
|
||||
private def exists(gameId: Game.ID, userId: User.ID): Fu[Boolean] =
|
||||
coll exists selectId(gameId, userId)
|
||||
|
||||
def exists(game: Game, user: User): Fu[Boolean] =
|
||||
|
@ -24,29 +25,27 @@ final class BookmarkApi(
|
|||
def exists(game: Game, user: Option[User]): Fu[Boolean] =
|
||||
user.?? { exists(game, _) }
|
||||
|
||||
def filterGameIdsBookmarkedBy(games: Seq[Game], user: Option[User]): Fu[Set[String]] =
|
||||
def filterGameIdsBookmarkedBy(games: Seq[Game], user: Option[User]): Fu[Set[Game.ID]] =
|
||||
user ?? { u =>
|
||||
val candidateIds = games.filter(_.bookmarks > 0).map(_.id)
|
||||
if (candidateIds.isEmpty) fuccess(Set.empty)
|
||||
else coll.distinct[String, Set]("g", Some(
|
||||
userIdQuery(u.id) ++ $doc("g" $in candidateIds)
|
||||
))
|
||||
val candidateIds = games collect { case g if g.bookmarks > 0 => g.id }
|
||||
candidateIds.nonEmpty ??
|
||||
coll.distinctEasy[Game.ID, Set]("g", userIdQuery(u.id) ++ $doc("g" $in candidateIds))
|
||||
}
|
||||
|
||||
def removeByGameId(gameId: String): Funit =
|
||||
def removeByGameId(gameId: Game.ID): Funit =
|
||||
coll.remove($doc("g" -> gameId)).void
|
||||
|
||||
def removeByGameIds(gameIds: List[String]): Funit =
|
||||
def removeByGameIds(gameIds: List[Game.ID]): Funit =
|
||||
coll.remove($doc("g" $in gameIds)).void
|
||||
|
||||
def remove(gameId: String, userId: User.ID): Funit = coll.remove(selectId(gameId, userId)).void
|
||||
def remove(gameId: Game.ID, userId: User.ID): Funit = coll.remove(selectId(gameId, userId)).void
|
||||
// def remove(selector: Bdoc): Funit = coll.remove(selector).void
|
||||
|
||||
def toggle(gameId: String, userId: User.ID): Funit =
|
||||
def toggle(gameId: Game.ID, userId: User.ID): Funit =
|
||||
exists(gameId, userId) flatMap { e =>
|
||||
(if (e) remove(gameId, userId) else add(gameId, userId, DateTime.now)) inject !e
|
||||
} flatMap { bookmarked =>
|
||||
GameRepo.incBookmarks(gameId, if (bookmarked) 1 else -1)
|
||||
gameRepo.incBookmarks(gameId, if (bookmarked) 1 else -1)
|
||||
}
|
||||
|
||||
def countByUser(user: User): Fu[Int] = coll.countSel(userIdQuery(user.id))
|
||||
|
@ -54,7 +53,7 @@ final class BookmarkApi(
|
|||
def gamePaginatorByUser(user: User, page: Int) =
|
||||
paginator.byUser(user, page) map2 { (b: Bookmark) => b.game }
|
||||
|
||||
private def add(gameId: String, userId: User.ID, date: DateTime): Funit =
|
||||
private def add(gameId: Game.ID, userId: User.ID, date: DateTime): Funit =
|
||||
coll.insert($doc(
|
||||
"_id" -> makeId(gameId, userId),
|
||||
"g" -> gameId,
|
||||
|
@ -63,6 +62,6 @@ final class BookmarkApi(
|
|||
)).void
|
||||
|
||||
private def userIdQuery(userId: User.ID) = $doc("u" -> userId)
|
||||
private def makeId(gameId: String, userId: User.ID) = s"$gameId$userId"
|
||||
private def selectId(gameId: String, userId: User.ID) = $id(makeId(gameId, userId))
|
||||
private def makeId(gameId: Game.ID, userId: User.ID) = s"$gameId$userId"
|
||||
private def selectId(gameId: Game.ID, userId: User.ID) = $id(makeId(gameId, userId))
|
||||
}
|
||||
|
|
|
@ -1,45 +1,41 @@
|
|||
package lila.bookmark
|
||||
|
||||
import akka.actor._
|
||||
import com.typesafe.config.Config
|
||||
import com.softwaremill.macwire._
|
||||
import io.methvin.play.autoconfig._
|
||||
import play.api.Configuration
|
||||
|
||||
import lila.common.config._
|
||||
import lila.common.tagging._
|
||||
import lila.hub.actorApi.bookmark._
|
||||
|
||||
@Module
|
||||
class BookmarkConfig(
|
||||
@ConfigName("collection.bookmark") val bookmarkCollName: CollName,
|
||||
@ConfigName("paginator.maxPerPage") val paginatorMaxPerPage: MaxPerPage,
|
||||
@ConfigName("actor.name") val actorName: String
|
||||
)
|
||||
|
||||
trait PagMax
|
||||
|
||||
final class Env(
|
||||
config: Config,
|
||||
system: ActorSystem,
|
||||
db: lila.db.Env
|
||||
) {
|
||||
appConfig: Configuration,
|
||||
db: lila.db.Env,
|
||||
gameRepo: lila.game.GameRepo
|
||||
)(implicit system: ActorSystem) {
|
||||
|
||||
private val CollectionBookmark = config getString "collection.bookmark"
|
||||
private val PaginatorMaxPerPage = config getInt "paginator.max_per_page"
|
||||
private val ActorName = config getString "actor.name"
|
||||
private val config = appConfig.get[BookmarkConfig]("game")(AutoConfig.loader)
|
||||
|
||||
private[bookmark] lazy val bookmarkColl = db(CollectionBookmark)
|
||||
private lazy val bookmarkColl = db(config.bookmarkCollName)
|
||||
|
||||
lazy val paginator = new PaginatorBuilder(
|
||||
coll = bookmarkColl,
|
||||
maxPerPage = lila.common.MaxPerPage(PaginatorMaxPerPage)
|
||||
)
|
||||
lazy val paginator = wire[PaginatorBuilder]
|
||||
|
||||
lazy val api = new BookmarkApi(
|
||||
coll = bookmarkColl,
|
||||
paginator = paginator
|
||||
)
|
||||
lazy val api = wire[BookmarkApi]
|
||||
|
||||
system.actorOf(Props(new Actor {
|
||||
def receive = {
|
||||
case Toggle(gameId, userId) => api.toggle(gameId, userId)
|
||||
case Remove(gameId) => api removeByGameId gameId
|
||||
}
|
||||
}), name = ActorName)
|
||||
}
|
||||
|
||||
object Env {
|
||||
|
||||
lazy val current = "bookmark" boot new Env(
|
||||
config = lila.common.PlayApp loadConfig "bookmark",
|
||||
system = lila.common.PlayApp.system,
|
||||
db = lila.db.Env.current
|
||||
)
|
||||
}), name = config.actorName)
|
||||
}
|
||||
|
|
|
@ -7,7 +7,8 @@ import lila.user.User
|
|||
|
||||
private[bookmark] final class PaginatorBuilder(
|
||||
coll: Coll,
|
||||
maxPerPage: lila.common.MaxPerPage
|
||||
gameRepo: GameRepo,
|
||||
maxPerPage: lila.common.config.MaxPerPage
|
||||
) {
|
||||
|
||||
def byUser(user: User, page: Int): Fu[Paginator[Bookmark]] =
|
||||
|
@ -25,12 +26,12 @@ private[bookmark] final class PaginatorBuilder(
|
|||
def nbResults: Fu[Int] = coll countSel selector
|
||||
|
||||
def slice(offset: Int, length: Int): Fu[Seq[Bookmark]] = for {
|
||||
gameIds <- coll.find(selector, $doc("g" -> true))
|
||||
gameIds <- coll.find(selector, $doc("g" -> true).some)
|
||||
.sort(sorting)
|
||||
.skip(offset)
|
||||
.cursor[Bdoc]()
|
||||
.gather[List](length) map { _ flatMap { _.getAs[String]("g") } }
|
||||
games <- GameRepo gamesFromSecondary gameIds
|
||||
.gather[List](length) map { _ flatMap { _.string("g") } }
|
||||
games <- gameRepo gamesFromSecondary gameIds
|
||||
} yield games map { g => Bookmark(g, user) }
|
||||
|
||||
private def selector = $doc("u" -> user.id)
|
||||
|
|
|
@ -15,6 +15,9 @@ object config {
|
|||
|
||||
case class AppPath(value: String) extends AnyVal with StringValue
|
||||
|
||||
case class Max(value: Int) extends AnyVal with IntValue with Ordered[Int] {
|
||||
def compare(other: Int) = Integer.compare(value, other)
|
||||
}
|
||||
case class MaxPerPage(value: Int) extends AnyVal with IntValue
|
||||
|
||||
case class MaxPerSecond(value: Int) extends AnyVal with IntValue
|
||||
|
@ -26,6 +29,7 @@ object config {
|
|||
email: EmailAddress
|
||||
)
|
||||
|
||||
implicit val maxLoader = intLoader(Max.apply)
|
||||
implicit val maxPerPageLoader = intLoader(MaxPerPage.apply)
|
||||
implicit val maxPerSecondLoader = intLoader(MaxPerSecond.apply)
|
||||
implicit val collNameLoader = strLoader(CollName.apply)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package lila
|
||||
package lila.common
|
||||
|
||||
/**
|
||||
* Tag instances with arbitrary types. The tags are usually empty `trait`s. Tags have no runtime overhead and are only
|
||||
|
|
|
@ -177,14 +177,6 @@ trait CollExt { self: dsl with QueryBuilderExt =>
|
|||
readPreference = readPreference
|
||||
)(f).collect[List](maxDocs = maxDocs, Cursor.FailOnError[List[Bdoc]]())
|
||||
|
||||
def aggregateOne(
|
||||
firstOperator: coll.PipelineOperator,
|
||||
otherOperators: List[coll.PipelineOperator] = Nil,
|
||||
readPreference: ReadPreference = ReadPreference.primary
|
||||
): Fu[Option[Bdoc]] =
|
||||
coll.aggregatorContext[Bdoc](firstOperator, otherOperators, readPreference = readPreference)
|
||||
.prepared(CursorProducer.defaultCursorProducer[Bdoc]).cursor.headOption
|
||||
|
||||
def distinctEasy[T, M[_] <: Iterable[_]](
|
||||
key: String,
|
||||
selector: coll.pack.Document
|
||||
|
|
|
@ -10,16 +10,17 @@ import scala.concurrent.duration._
|
|||
|
||||
import lila.common.config._
|
||||
|
||||
private case class GameConfig(
|
||||
@ConfigName("collection.game") gameColl: CollName,
|
||||
@ConfigName("collection.crosstable") crosstableColl: CollName,
|
||||
@ConfigName("collection.matchup") matchupColl: CollName,
|
||||
@ConfigName("paginator.maxPerPage") paginatorMaxPerPage: MaxPerPage,
|
||||
@ConfigName("captcher.name") captcherName: String,
|
||||
@ConfigName("captcher.duration") captcherDuration: FiniteDuration,
|
||||
uciMemoTtl: FiniteDuration,
|
||||
pngUrl: String,
|
||||
pngSize: Int
|
||||
@Module
|
||||
private class GameConfig(
|
||||
@ConfigName("collection.game") val gameColl: CollName,
|
||||
@ConfigName("collection.crosstable") val crosstableColl: CollName,
|
||||
@ConfigName("collection.matchup") val matchupColl: CollName,
|
||||
@ConfigName("paginator.maxPerPage") val paginatorMaxPerPage: MaxPerPage,
|
||||
@ConfigName("captcher.name") val captcherName: String,
|
||||
@ConfigName("captcher.duration") val captcherDuration: FiniteDuration,
|
||||
val uciMemoTtl: FiniteDuration,
|
||||
val pngUrl: String,
|
||||
val pngSize: Int
|
||||
)
|
||||
|
||||
final class Env(
|
||||
|
@ -36,28 +37,26 @@ final class Env(
|
|||
)(implicit system: ActorSystem, scheduler: Scheduler) {
|
||||
|
||||
private val config = appConfig.get[GameConfig]("game")(AutoConfig.loader)
|
||||
import config._
|
||||
|
||||
lazy val gameRepo = new GameRepo(db(gameColl))
|
||||
lazy val gameRepo = new GameRepo(db(config.gameColl))
|
||||
|
||||
lazy val pngExport = new PngExport(ws, pngUrl, pngSize)
|
||||
lazy val pngExport = new PngExport(ws, config.pngUrl, config.pngSize)
|
||||
|
||||
lazy val divider = wire[Divider]
|
||||
|
||||
lazy val cached: Cached = wire[Cached]
|
||||
|
||||
lazy val paginator = new PaginatorBuilder(gameRepo, cached, paginatorMaxPerPage)
|
||||
// lazy val paginator = wire[PaginatorBuilder]
|
||||
lazy val paginator = wire[PaginatorBuilder]
|
||||
|
||||
lazy val rewind = wire[Rewind]
|
||||
|
||||
lazy val uciMemo = new UciMemo(gameRepo, uciMemoTtl)
|
||||
lazy val uciMemo = new UciMemo(gameRepo, config.uciMemoTtl)
|
||||
|
||||
lazy val pgnDump = wire[PgnDump]
|
||||
|
||||
lazy val crosstableApi = new CrosstableApi(
|
||||
coll = db(crosstableColl),
|
||||
matchupColl = db(matchupColl),
|
||||
coll = db(config.crosstableColl),
|
||||
matchupColl = db(config.matchupColl),
|
||||
userRepo = userRepo,
|
||||
gameRepo = gameRepo,
|
||||
asyncCache = asyncCache
|
||||
|
@ -76,8 +75,8 @@ final class Env(
|
|||
lazy val jsonView = new JsonView(rematchOf = rematches.getIfPresent)
|
||||
|
||||
// eargerly load captcher actor
|
||||
private val captcher = system.actorOf(Props(new Captcher(gameRepo)), name = captcherName)
|
||||
scheduler.scheduleWithFixedDelay(captcherDuration, captcherDuration) {
|
||||
private val captcher = system.actorOf(Props(new Captcher(gameRepo)), name = config.captcherName)
|
||||
scheduler.scheduleWithFixedDelay(config.captcherDuration, config.captcherDuration) {
|
||||
() => captcher ! actorApi.NewCaptcha
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import scala.concurrent.duration._
|
|||
|
||||
import lila.db.BSON.BSONJodaDateTimeHandler
|
||||
import lila.db.dsl._
|
||||
import lila.tagging._
|
||||
|
||||
final class MongoCache[K, V: BSONHandler] private (
|
||||
prefix: String,
|
||||
|
|
|
@ -14,15 +14,15 @@ final class PersonalTokenApi(
|
|||
import AccessToken.{ BSONFields => F, _ }
|
||||
|
||||
def list(u: User): Fu[List[AccessToken]] =
|
||||
tokenColl.find($doc(
|
||||
tokenColl.ext.find($doc(
|
||||
F.userId -> u.id,
|
||||
F.clientId -> clientId
|
||||
)).sort($sort desc F.createdAt).list[AccessToken](100)
|
||||
|
||||
def create(token: AccessToken) = tokenColl insert token void
|
||||
def create(token: AccessToken) = tokenColl.insert.one(token).void
|
||||
|
||||
def deleteBy(tokenId: AccessToken.Id, user: User) =
|
||||
tokenColl.remove($doc(
|
||||
tokenColl.delete.one($doc(
|
||||
F.id -> tokenId,
|
||||
F.clientId -> clientId,
|
||||
F.userId -> user.id
|
||||
|
|
|
@ -4,7 +4,6 @@ import io.methvin.play.autoconfig._
|
|||
import play.api.Configuration
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import lila.common.CollName
|
||||
import lila.common.config._
|
||||
|
||||
case class PrefConfig(
|
||||
|
|
|
@ -1,79 +1,63 @@
|
|||
package lila.relation
|
||||
|
||||
import akka.actor._
|
||||
import com.typesafe.config.Config
|
||||
import com.softwaremill.macwire._
|
||||
import io.methvin.play.autoconfig._
|
||||
import play.api.Configuration
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import lila.common.config._
|
||||
|
||||
@Module
|
||||
private class RelationConfig(
|
||||
@ConfigName("collection.relation") val collection: CollName,
|
||||
@ConfigName("actor.notify_freq") val actorNotifyFreq: FiniteDuration,
|
||||
@ConfigName("actor.name") val actorName: String,
|
||||
@ConfigName("limit.follow") val maxFollow: Max,
|
||||
@ConfigName("limit.block") val maxBlock: Max
|
||||
)
|
||||
|
||||
final class Env(
|
||||
config: Config,
|
||||
appConfig: Configuration,
|
||||
db: lila.db.Env,
|
||||
hub: lila.hub.Env,
|
||||
onlineUserIds: () => Set[lila.user.User.ID],
|
||||
lightUserApi: lila.user.LightUserApi,
|
||||
followable: String => Fu[Boolean],
|
||||
system: ActorSystem,
|
||||
asyncCache: lila.memo.AsyncCache.Builder,
|
||||
scheduler: lila.common.Scheduler
|
||||
) {
|
||||
asyncCache: lila.memo.AsyncCache.Builder
|
||||
)(implicit system: ActorSystem) {
|
||||
|
||||
private val settings = new {
|
||||
val CollectionRelation = config getString "collection.relation"
|
||||
val ActorNotifyFreq = config duration "actor.notify_freq"
|
||||
val ActorName = config getString "actor.name"
|
||||
val MaxBlock = config getInt "limit.block"
|
||||
}
|
||||
import settings._
|
||||
private val config = appConfig.get[RelationConfig]("relation")(AutoConfig.loader)
|
||||
|
||||
val MaxFollow = config getInt "limit.follow"
|
||||
def maxFollow = config.maxFollow
|
||||
|
||||
private[relation] val coll = db(CollectionRelation)
|
||||
private lazy val coll = db(config.collection)
|
||||
|
||||
private lazy val repo: RelationRepo = wire[RelationRepo]
|
||||
|
||||
lazy val api = new RelationApi(
|
||||
coll = coll,
|
||||
repo = repo,
|
||||
actor = hub.relation,
|
||||
timeline = hub.timeline,
|
||||
reporter = hub.report,
|
||||
followable = followable,
|
||||
asyncCache = asyncCache,
|
||||
maxFollow = MaxFollow,
|
||||
maxBlock = MaxBlock
|
||||
maxFollow = config.maxFollow,
|
||||
maxBlock = config.maxBlock
|
||||
)
|
||||
|
||||
lazy val stream = new RelationStream(coll = coll)(system)
|
||||
lazy val stream = wire[RelationStream]
|
||||
|
||||
val online = new OnlineDoing(
|
||||
api,
|
||||
lightUser = lightUserApi.sync,
|
||||
onlineUserIds
|
||||
)
|
||||
lazy val online: OnlineDoing = wire[OnlineDoing]
|
||||
|
||||
def isPlaying(userId: lila.user.User.ID): Boolean =
|
||||
online.playing.get(userId)
|
||||
def isPlaying(userId: lila.user.User.ID): Boolean = online.playing.get(userId)
|
||||
|
||||
private[relation] val actor = system.actorOf(Props(new RelationActor(
|
||||
lightUser = lightUserApi.sync,
|
||||
api = api,
|
||||
online = online
|
||||
)), name = ActorName)
|
||||
private val lightSync = lightUserApi.sync _
|
||||
|
||||
scheduler.once(15 seconds) {
|
||||
scheduler.message(ActorNotifyFreq) {
|
||||
actor -> actorApi.ComputeMovement
|
||||
}
|
||||
private[relation] val actor = system.actorOf(Props(wire[RelationActor]), name = config.actorName)
|
||||
|
||||
system.scheduler.scheduleWithFixedDelay(15 seconds, config.actorNotifyFreq) {
|
||||
() => actor ! actorApi.ComputeMovement
|
||||
}
|
||||
}
|
||||
|
||||
object Env {
|
||||
|
||||
lazy val current = "relation" boot new Env(
|
||||
config = lila.common.PlayApp loadConfig "relation",
|
||||
db = lila.db.Env.current,
|
||||
hub = lila.hub.Env.current,
|
||||
onlineUserIds = lila.socket.Env.current.onlineUserIds,
|
||||
lightUserApi = lila.user.Env.current.lightUserApi,
|
||||
followable = lila.pref.Env.current.api.followable _,
|
||||
system = lila.common.PlayApp.system,
|
||||
asyncCache = lila.memo.Env.current.asyncCache,
|
||||
scheduler = lila.common.PlayApp.scheduler
|
||||
)
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ final class OnlineDoing(
|
|||
api fetchFollowing userId map userIds().intersect map { friends =>
|
||||
if (friends.isEmpty) OnlineFriends.empty
|
||||
else OnlineFriends(
|
||||
users = friends.flatMap { lightUser(_) }(scala.collection.breakOut),
|
||||
users = friends.view.flatMap { lightUser(_) }.to(List),
|
||||
playing = playing intersect friends,
|
||||
studying = friends filter studying.getAllPresent(friends).contains
|
||||
)
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package lila.relation
|
||||
|
||||
import akka.actor.Actor
|
||||
import scala.collection.breakOut
|
||||
|
||||
import actorApi._
|
||||
import lila.common.{ Bus, LightUser }
|
||||
|
@ -32,8 +31,8 @@ private[relation] final class RelationActor(
|
|||
|
||||
case ComputeMovement =>
|
||||
val curIds = online.userIds()
|
||||
val leaveUsers: List[LightUser] = (previousOnlineIds diff curIds).flatMap { lightUser(_) }(breakOut)
|
||||
val enterUsers: List[LightUser] = (curIds diff previousOnlineIds).flatMap { lightUser(_) }(breakOut)
|
||||
val leaveUsers: List[LightUser] = (previousOnlineIds diff curIds).view.flatMap { lightUser(_) }.to(List)
|
||||
val enterUsers: List[LightUser] = (curIds diff previousOnlineIds).view.flatMap { lightUser(_) }.to(List)
|
||||
|
||||
val friendsEntering = enterUsers map { u =>
|
||||
FriendEntering(u, online.playing get u.id, online isStudying u.id)
|
||||
|
@ -132,7 +131,7 @@ private[relation] final class RelationActor(
|
|||
}
|
||||
}
|
||||
|
||||
private def notifyFollowersGameStateChanged(userIds: Traversable[ID], message: String) =
|
||||
private def notifyFollowersGameStateChanged(userIds: Iterable[ID], message: String) =
|
||||
userIds foreach { userId =>
|
||||
api.fetchFollowersFromSecondary(userId) map online.userIds().intersect foreach { ids =>
|
||||
if (ids.nonEmpty) Bus.publish(SendTos(ids.toSet, message, userId), "socketUsers")
|
||||
|
|
|
@ -1,28 +1,29 @@
|
|||
package lila.relation
|
||||
|
||||
import akka.actor.ActorSelection
|
||||
import reactivemongo.api._
|
||||
import reactivemongo.api.bson._
|
||||
import reactivemongo.api.collections.bson.BSONBatchCommands.AggregationFramework._
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import BSONHandlers._
|
||||
import lila.common.Bus
|
||||
import lila.common.config.Max
|
||||
import lila.db.dsl._
|
||||
import lila.db.paginator._
|
||||
import lila.hub.actorApi.timeline.{ Propagate, Follow => FollowUser }
|
||||
import lila.user.User
|
||||
|
||||
import BSONHandlers._
|
||||
import reactivemongo.api._
|
||||
import reactivemongo.api.collections.bson.BSONBatchCommands.AggregationFramework._
|
||||
import reactivemongo.api.bson._
|
||||
|
||||
final class RelationApi(
|
||||
coll: Coll,
|
||||
repo: RelationRepo,
|
||||
actor: ActorSelection,
|
||||
timeline: ActorSelection,
|
||||
reporter: ActorSelection,
|
||||
followable: ID => Fu[Boolean],
|
||||
asyncCache: lila.memo.AsyncCache.Builder,
|
||||
maxFollow: Int,
|
||||
maxBlock: Int
|
||||
maxFollow: Max,
|
||||
maxBlock: Max
|
||||
) {
|
||||
|
||||
import RelationRepo.makeId
|
||||
|
@ -32,25 +33,29 @@ final class RelationApi(
|
|||
}
|
||||
def fetchRelation(u1: User, u2: User): Fu[Option[Relation]] = fetchRelation(u1.id, u2.id)
|
||||
|
||||
def fetchFollowing = RelationRepo following _
|
||||
def fetchFollowing = repo following _
|
||||
|
||||
def fetchFollowersFromSecondary = RelationRepo.followersFromSecondary _
|
||||
def fetchFollowersFromSecondary = repo.followersFromSecondary _
|
||||
|
||||
def fetchBlocking = RelationRepo blocking _
|
||||
def fetchBlocking = repo blocking _
|
||||
|
||||
def fetchFriends(userId: ID) = coll.aggregateOne(Match($doc(
|
||||
"$or" -> $arr($doc("u1" -> userId), $doc("u2" -> userId)),
|
||||
"r" -> Follow
|
||||
)), List(
|
||||
Group(BSONNull)(
|
||||
"u1" -> AddFieldToSet("u1"),
|
||||
"u2" -> AddFieldToSet("u2")
|
||||
),
|
||||
Project($id($doc("$setIntersection" -> $arr("$u1", "$u2"))))
|
||||
),
|
||||
ReadPreference.secondaryPreferred).map {
|
||||
~_.flatMap(_.getAs[Set[String]]("_id")) - userId
|
||||
}
|
||||
def fetchFriends(userId: ID) = coll.aggregateWith[Bdoc](
|
||||
readPreference = ReadPreference.secondaryPreferred
|
||||
) { framework =>
|
||||
import framework._
|
||||
Match($doc(
|
||||
"$or" -> $arr($doc("u1" -> userId), $doc("u2" -> userId)),
|
||||
"r" -> Follow
|
||||
)) -> List(
|
||||
Group(BSONNull)(
|
||||
"u1" -> AddFieldToSet("u1"),
|
||||
"u2" -> AddFieldToSet("u2")
|
||||
),
|
||||
Project($id($doc("$setIntersection" -> $arr("$u1", "$u2"))))
|
||||
)
|
||||
}.headOption.map {
|
||||
~_.flatMap(_.getAsOpt[Set[String]]("_id")) - userId
|
||||
}
|
||||
|
||||
def fetchFollows(u1: ID, u2: ID): Fu[Boolean] = (u1 != u2) ?? {
|
||||
coll.exists($doc("_id" -> makeId(u1, u2), "r" -> Follow))
|
||||
|
@ -82,16 +87,16 @@ final class RelationApi(
|
|||
def countFollowers(userId: ID) = countFollowersCache get userId
|
||||
|
||||
def countBlocking(userId: ID) =
|
||||
coll.count($doc("u1" -> userId, "r" -> Block).some)
|
||||
coll.countSel($doc("u1" -> userId, "r" -> Block))
|
||||
|
||||
def countBlockers(userId: ID) =
|
||||
coll.count($doc("u2" -> userId, "r" -> Block).some)
|
||||
coll.countSel($doc("u2" -> userId, "r" -> Block))
|
||||
|
||||
def followingPaginatorAdapter(userId: ID) = new CachedAdapter[Followed](
|
||||
adapter = new Adapter[Followed](
|
||||
collection = coll,
|
||||
selector = $doc("u1" -> userId, "r" -> Follow),
|
||||
projection = $doc("u2" -> true, "_id" -> false),
|
||||
projection = $doc("u2" -> true, "_id" -> false).some,
|
||||
sort = $empty
|
||||
),
|
||||
nbResults = countFollowing(userId)
|
||||
|
@ -101,7 +106,7 @@ final class RelationApi(
|
|||
adapter = new Adapter[Follower](
|
||||
collection = coll,
|
||||
selector = $doc("u2" -> userId, "r" -> Follow),
|
||||
projection = $doc("u1" -> true, "_id" -> false),
|
||||
projection = $doc("u1" -> true, "_id" -> false).some,
|
||||
sort = $empty
|
||||
),
|
||||
nbResults = countFollowers(userId)
|
||||
|
@ -110,7 +115,7 @@ final class RelationApi(
|
|||
def blockingPaginatorAdapter(userId: ID) = new Adapter[Blocked](
|
||||
collection = coll,
|
||||
selector = $doc("u1" -> userId, "r" -> Block),
|
||||
projection = $doc("u2" -> true, "_id" -> false),
|
||||
projection = $doc("u2" -> true, "_id" -> false).some,
|
||||
sort = $empty
|
||||
).map(_.userId)
|
||||
|
||||
|
@ -119,9 +124,9 @@ final class RelationApi(
|
|||
case true => fetchRelation(u1, u2) zip fetchRelation(u2, u1) flatMap {
|
||||
case (Some(Follow), _) => funit
|
||||
case (_, Some(Block)) => funit
|
||||
case _ => RelationRepo.follow(u1, u2) >> limitFollow(u1) >>- {
|
||||
case _ => repo.follow(u1, u2) >> limitFollow(u1) >>- {
|
||||
countFollowersCache.update(u2, 1+)
|
||||
countFollowingCache.update(u1, prev => (prev + 1) atMost maxFollow)
|
||||
countFollowingCache.update(u1, prev => (prev + 1) atMost maxFollow.value)
|
||||
reloadOnlineFriends(u1, u2)
|
||||
timeline ! Propagate(FollowUser(u1, u2)).toFriendsOf(u1).toUsers(List(u2))
|
||||
Bus.publish(lila.hub.actorApi.relation.Follow(u1, u2), "relation")
|
||||
|
@ -131,18 +136,18 @@ final class RelationApi(
|
|||
}
|
||||
|
||||
private def limitFollow(u: ID) = countFollowing(u) flatMap { nb =>
|
||||
(nb > maxFollow) ?? RelationRepo.drop(u, true, nb - maxFollow)
|
||||
(maxFollow < nb) ?? repo.drop(u, true, nb - maxFollow.value)
|
||||
}
|
||||
|
||||
private def limitBlock(u: ID) = countBlocking(u) flatMap { nb =>
|
||||
(nb > maxBlock) ?? RelationRepo.drop(u, false, nb - maxBlock)
|
||||
(maxBlock < nb) ?? repo.drop(u, false, nb - maxBlock.value)
|
||||
}
|
||||
|
||||
def block(u1: ID, u2: ID): Funit = (u1 != u2) ?? {
|
||||
fetchBlocks(u1, u2) flatMap {
|
||||
case true => funit
|
||||
case _ =>
|
||||
RelationRepo.block(u1, u2) >> limitBlock(u1) >> unfollow(u2, u1) >>- {
|
||||
repo.block(u1, u2) >> limitBlock(u1) >> unfollow(u2, u1) >>- {
|
||||
reloadOnlineFriends(u1, u2)
|
||||
Bus.publish(lila.hub.actorApi.relation.Block(u1, u2), "relation")
|
||||
lila.mon.relation.block()
|
||||
|
@ -152,7 +157,7 @@ final class RelationApi(
|
|||
|
||||
def unfollow(u1: ID, u2: ID): Funit = (u1 != u2) ?? {
|
||||
fetchFollows(u1, u2) flatMap {
|
||||
case true => RelationRepo.unfollow(u1, u2) >>- {
|
||||
case true => repo.unfollow(u1, u2) >>- {
|
||||
countFollowersCache.update(u2, _ - 1)
|
||||
countFollowingCache.update(u1, _ - 1)
|
||||
reloadOnlineFriends(u1, u2)
|
||||
|
@ -162,11 +167,11 @@ final class RelationApi(
|
|||
}
|
||||
}
|
||||
|
||||
def unfollowAll(u1: ID): Funit = RelationRepo.unfollowAll(u1)
|
||||
def unfollowAll(u1: ID): Funit = repo.unfollowAll(u1)
|
||||
|
||||
def unblock(u1: ID, u2: ID): Funit = (u1 != u2) ?? {
|
||||
fetchBlocks(u1, u2) flatMap {
|
||||
case true => RelationRepo.unblock(u1, u2) >>- {
|
||||
case true => repo.unblock(u1, u2) >>- {
|
||||
reloadOnlineFriends(u1, u2)
|
||||
Bus.publish(lila.hub.actorApi.relation.UnBlock(u1, u2), "relation")
|
||||
lila.mon.relation.unblock()
|
||||
|
@ -176,7 +181,7 @@ final class RelationApi(
|
|||
}
|
||||
|
||||
def searchFollowedBy(u: User, term: String, max: Int): Fu[List[User.ID]] =
|
||||
RelationRepo.followingLike(u.id, term) map { _.sorted take max }
|
||||
repo.followingLike(u.id, term) map { _.sorted take max }
|
||||
|
||||
private def reloadOnlineFriends(u1: ID, u2: ID): Unit = {
|
||||
import lila.hub.actorApi.relation.ReloadOnlineFriends
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
package lila.relation
|
||||
|
||||
import reactivemongo.api.ReadPreference
|
||||
import reactivemongo.api.bson._
|
||||
import reactivemongo.api.ReadPreference
|
||||
|
||||
import lila.db.dsl._
|
||||
|
||||
private[relation] object RelationRepo {
|
||||
private final class RelationRepo(coll: Coll) {
|
||||
|
||||
// dirty
|
||||
private val coll = Env.current.coll
|
||||
import RelationRepo._
|
||||
|
||||
def following(userId: ID) = relating(userId, Follow)
|
||||
|
||||
|
@ -19,51 +18,53 @@ private[relation] object RelationRepo {
|
|||
|
||||
def followingLike(userId: ID, term: String): Fu[List[ID]] =
|
||||
lila.user.User.couldBeUsername(term) ?? {
|
||||
coll.distinctWithReadPreference[ID, List]("u2", $doc(
|
||||
coll.secondaryPreferred.distinctEasy[ID, List]("u2", $doc(
|
||||
"u1" -> userId,
|
||||
"u2" $startsWith term.toLowerCase,
|
||||
"r" -> Follow
|
||||
).some,
|
||||
ReadPreference.secondaryPreferred)
|
||||
))
|
||||
}
|
||||
|
||||
private def relaters(userId: ID, relation: Relation, rp: ReadPreference = ReadPreference.primary): Fu[Set[ID]] =
|
||||
coll.distinctWithReadPreference[String, Set]("u1", $doc(
|
||||
coll.withReadPreference(rp).distinctEasy[String, Set]("u1", $doc(
|
||||
"u2" -> userId,
|
||||
"r" -> relation
|
||||
).some, rp)
|
||||
))
|
||||
|
||||
private def relating(userId: ID, relation: Relation): Fu[Set[ID]] =
|
||||
coll.distinct[String, Set]("u2", $doc(
|
||||
coll.distinctEasy[String, Set]("u2", $doc(
|
||||
"u1" -> userId,
|
||||
"r" -> relation
|
||||
).some)
|
||||
))
|
||||
|
||||
def follow(u1: ID, u2: ID): Funit = save(u1, u2, Follow)
|
||||
def unfollow(u1: ID, u2: ID): Funit = remove(u1, u2)
|
||||
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 = coll.remove($doc("u1" -> u1)).void
|
||||
def unfollowAll(u1: ID): Funit = coll.delete.one($doc("u1" -> u1)).void
|
||||
|
||||
private def save(u1: ID, u2: ID, relation: Relation): Funit = coll.update(
|
||||
private def save(u1: ID, u2: ID, relation: Relation): Funit = coll.update.one(
|
||||
$id(makeId(u1, u2)),
|
||||
$doc("u1" -> u1, "u2" -> u2, "r" -> relation),
|
||||
upsert = true
|
||||
).void
|
||||
|
||||
def remove(u1: ID, u2: ID): Funit = coll.remove($id(makeId(u1, u2))).void
|
||||
def remove(u1: ID, u2: ID): Funit = coll.delete.one($id(makeId(u1, u2))).void
|
||||
|
||||
def drop(userId: ID, relation: Relation, nb: Int) =
|
||||
coll.find(
|
||||
$doc("u1" -> userId, "r" -> relation),
|
||||
$doc("_id" -> true)
|
||||
$doc("_id" -> true).some
|
||||
)
|
||||
.list[Bdoc](nb).map {
|
||||
_.flatMap { _.getAs[String]("_id") }
|
||||
_.flatMap { _.string("_id") }
|
||||
} flatMap { ids =>
|
||||
coll.remove($inIds(ids)).void
|
||||
coll.delete.one($inIds(ids)).void
|
||||
}
|
||||
|
||||
def makeId(u1: String, u2: String) = s"$u1/$u2"
|
||||
}
|
||||
|
||||
object RelationRepo {
|
||||
|
||||
def makeId(u1: ID, u2: ID) = s"$u1/$u2"
|
||||
}
|
||||
|
|
|
@ -1,37 +1,35 @@
|
|||
package lila.relation
|
||||
|
||||
import play.api.libs.iteratee._
|
||||
import reactivemongo.api.ReadPreference
|
||||
import reactivemongo.play.iteratees.cursorProducer
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import lila.common.MaxPerSecond
|
||||
import lila.common.config.MaxPerSecond
|
||||
import lila.db.dsl._
|
||||
import lila.user.{ User, UserRepo }
|
||||
|
||||
final class RelationStream(coll: Coll)(implicit system: akka.actor.ActorSystem) {
|
||||
|
||||
import RelationStream._
|
||||
// import RelationStream._
|
||||
|
||||
def follow(user: User, direction: Direction, perSecond: MaxPerSecond): Enumerator[User] = {
|
||||
val field = direction match {
|
||||
case Direction.Following => "u2"
|
||||
case Direction.Followers => "u1"
|
||||
}
|
||||
val projection = $doc(field -> true, "_id" -> false)
|
||||
val query = direction match {
|
||||
case Direction.Following => coll.find($doc("u1" -> user.id, "r" -> Follow), projection)
|
||||
case Direction.Followers => coll.find($doc("u2" -> user.id, "r" -> Follow), projection)
|
||||
}
|
||||
query.copy(options = query.options.batchSize(perSecond.value))
|
||||
.cursor[Bdoc](readPreference = ReadPreference.secondaryPreferred)
|
||||
.bulkEnumerator() &>
|
||||
lila.common.Iteratee.delay(1 second) &>
|
||||
Enumeratee.mapM { docs =>
|
||||
UserRepo usersFromSecondary docs.toSeq.flatMap(_.getAs[User.ID](field))
|
||||
} &>
|
||||
Enumeratee.mapConcat(_.toSeq)
|
||||
}
|
||||
// def follow(user: User, direction: Direction, perSecond: MaxPerSecond): Enumerator[User] = {
|
||||
// val field = direction match {
|
||||
// case Direction.Following => "u2"
|
||||
// case Direction.Followers => "u1"
|
||||
// }
|
||||
// val projection = $doc(field -> true, "_id" -> false)
|
||||
// val query = direction match {
|
||||
// case Direction.Following => coll.find($doc("u1" -> user.id, "r" -> Follow), projection)
|
||||
// case Direction.Followers => coll.find($doc("u2" -> user.id, "r" -> Follow), projection)
|
||||
// }
|
||||
// query.copy(options = query.options.batchSize(perSecond.value))
|
||||
// .cursor[Bdoc](readPreference = ReadPreference.secondaryPreferred)
|
||||
// .bulkEnumerator() &>
|
||||
// lila.common.Iteratee.delay(1 second) &>
|
||||
// Enumeratee.mapM { docs =>
|
||||
// UserRepo usersFromSecondary docs.toSeq.flatMap(_.getAs[User.ID](field))
|
||||
// } &>
|
||||
// Enumeratee.mapConcat(_.toSeq)
|
||||
// }
|
||||
}
|
||||
|
||||
object RelationStream {
|
||||
|
|
|
@ -41,7 +41,7 @@ object RoomSocket {
|
|||
case chatApi.OnReinstate(userId) =>
|
||||
this ! NotifyVersion("chat_reinstate", userId, false)
|
||||
}
|
||||
override def stop() {
|
||||
override def stop() = {
|
||||
super.stop()
|
||||
send(Protocol.Out.stop(roomId))
|
||||
chat foreach { c =>
|
||||
|
@ -55,7 +55,7 @@ object RoomSocket {
|
|||
mkTrouper = roomId => new RoomState(
|
||||
RoomId(roomId),
|
||||
send,
|
||||
chatBus option RoomChat(Chat classify Chat.Id(roomId))
|
||||
chatBus option RoomChat(Chat chanOf Chat.Id(roomId))
|
||||
),
|
||||
accessTimeout = 5 minutes
|
||||
)
|
||||
|
|
|
@ -6,21 +6,21 @@ import io.methvin.play.autoconfig._
|
|||
import play.api.Configuration
|
||||
import play.api.libs.ws._
|
||||
|
||||
case class SearchConfig(
|
||||
enabled: Boolean,
|
||||
writeable: Boolean,
|
||||
endpoint: String
|
||||
@Module
|
||||
private class SearchConfig(
|
||||
val enabled: Boolean,
|
||||
val writeable: Boolean,
|
||||
val endpoint: String
|
||||
)
|
||||
|
||||
final class Env(
|
||||
appConfig: Configuration,
|
||||
system: ActorSystem,
|
||||
ws: WSClient
|
||||
) {
|
||||
)(implicit system: ActorSystem) {
|
||||
|
||||
private val config = appConfig.get[SearchConfig]("search")(AutoConfig.loader)
|
||||
|
||||
def makeHttp(index: Index): ESClientHttp = wire[ESClientHttp]
|
||||
private def makeHttp(index: Index): ESClientHttp = wire[ESClientHttp]
|
||||
|
||||
val makeClient = (index: Index) =>
|
||||
if (config.enabled) makeHttp(index)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package lila.search
|
||||
|
||||
import lila.common.MaxPerPage
|
||||
import lila.common.config.MaxPerPage
|
||||
import lila.common.paginator._
|
||||
|
||||
import play.api.libs.json.Writes
|
||||
|
|
|
@ -11,16 +11,15 @@ import lila.common.config._
|
|||
import lila.common.LightUser
|
||||
import lila.db.dsl.Coll
|
||||
|
||||
case class UserConfig(
|
||||
@ConfigName("paginator.max_per_page") paginatorMaxPerPage: MaxPerPage,
|
||||
@ConfigName("cached.nb.ttl") cachedNbTtl: FiniteDuration,
|
||||
@ConfigName("online.ttl") onlineTtl: FiniteDuration,
|
||||
@ConfigName("collection.user") collectionUser: CollName,
|
||||
@ConfigName("collection.note") collectionNote: CollName,
|
||||
@ConfigName("collection.trophy") collectionTrophy: CollName,
|
||||
@ConfigName("collection.trophyKind") collectionTrophyKind: CollName,
|
||||
@ConfigName("collection.ranking") collectionRanking: CollName,
|
||||
@ConfigName("password.bpass.secret") passwordBPassSecret: Secret
|
||||
private class UserConfig(
|
||||
@ConfigName("cached.nb.ttl") val cachedNbTtl: FiniteDuration,
|
||||
@ConfigName("online.ttl") val onlineTtl: FiniteDuration,
|
||||
@ConfigName("collection.user") val collectionUser: CollName,
|
||||
@ConfigName("collection.note") val collectionNote: CollName,
|
||||
@ConfigName("collection.trophy") val collectionTrophy: CollName,
|
||||
@ConfigName("collection.trophyKind") val collectionTrophyKind: CollName,
|
||||
@ConfigName("collection.ranking") val collectionRanking: CollName,
|
||||
@ConfigName("password.bpass.secret") val passwordBPassSecret: Secret
|
||||
)
|
||||
|
||||
final class Env(
|
||||
|
|
|
@ -27,7 +27,10 @@ object BuildSettings {
|
|||
.setPreference(DanglingCloseParenthesis, Force)
|
||||
.setPreference(DoubleIndentConstructorArguments, true)
|
||||
|
||||
def defaultDeps = Seq(scalaz, chess, scalalib, jodaTime, ws, macwire, autoconfig) // , specs2, specs2Scalaz)
|
||||
def defaultDeps = Seq(
|
||||
scalaz, chess, scalalib, jodaTime, ws,
|
||||
macwire.macros, macwire.util, autoconfig
|
||||
) // , specs2, specs2Scalaz)
|
||||
|
||||
def compile(deps: ModuleID*): Seq[ModuleID] = deps map (_ % "compile")
|
||||
def provided(deps: ModuleID*): Seq[ModuleID] = deps map (_ % "provided")
|
||||
|
|
|
@ -40,11 +40,16 @@ object Dependencies {
|
|||
val lettuce = "io.lettuce" % "lettuce-core" % "5.2.1.RELEASE"
|
||||
val epoll = "io.netty" % "netty-transport-native-epoll" % "4.1.43.Final" classifier "linux-x86_64"
|
||||
val markdown = "com.vladsch.flexmark" % "flexmark-all" % "0.50.44"
|
||||
val macwire = "com.softwaremill.macwire" %% "macros" % "2.3.3" % "provided"
|
||||
val autoconfig = "io.methvin.play" %% "autoconfig-macros" % "0.3.0"
|
||||
|
||||
object macwire {
|
||||
val version = "2.3.3"
|
||||
val macros = "com.softwaremill.macwire" %% "macros" % version
|
||||
val util = "com.softwaremill.macwire" %% "util" % version
|
||||
}
|
||||
|
||||
object reactivemongo {
|
||||
val version = "0.19.1"
|
||||
val version = "0.19.2"
|
||||
val driver = "org.reactivemongo" %% "reactivemongo" % version
|
||||
val bson = "org.reactivemongo" %% "reactivemongo-bson-api" % version
|
||||
val native = "org.reactivemongo" % "reactivemongo-shaded-native" % s"$version-linux-x86-64" % "runtime" classifier "linux-x86_64"
|
||||
|
|
Loading…
Reference in New Issue