migration WIP

rm0193-mapreduce
Thibault Duplessis 2019-12-05 15:51:18 -06:00
parent b0d798fd37
commit 20b372d5ec
43 changed files with 256 additions and 231 deletions

View File

@ -171,7 +171,7 @@ final class EnvBoot(
lazy val common: lila.common.Env = wire[lila.common.Env]
lazy val baseUrl = common.netConfig.baseUrl
lazy val memo: lila.memo.Env = wire[lila.memo.Env]
lazy val db: lila.db.Env = lila.db.Env.main(config)
lazy val db: lila.db.Env = lila.db.Env.main(config, lifecycle)
lazy val user: lila.user.Env = wire[lila.user.Env]
lazy val security: lila.security.Env = wire[lila.security.Env]
lazy val hub: lila.hub.Env = wire[lila.hub.Env]

View File

@ -12,8 +12,8 @@ import views._
final class Analyse(
env: Env,
gameC: Game,
roundC: Round
gameC: => Game,
roundC: => Round
) extends LilaController(env) {
def requestAnalysis(id: String) = Auth { implicit ctx => me =>

View File

@ -14,9 +14,11 @@ import lila.common.{ HTTPRequest, IpAddress }
final class Api(
env: Env,
gameC: Game
gameC: => Game
) extends LilaController(env) {
import Api._
private val userApi = env.api.userApi
private val gameApi = env.api.gameApi
@ -319,6 +321,39 @@ final class Api(
}
}
def CookieBasedApiRequest(js: Context => Fu[ApiResult]) = Open { ctx =>
js(ctx) map toHttp
}
def ApiRequest(js: RequestHeader => Fu[ApiResult]) = Action.async { req =>
js(req) map toHttp
}
def MobileApiRequest(js: RequestHeader => Fu[ApiResult]) = Action.async { req =>
if (lila.api.Mobile.Api requested req) js(req) map toHttp
else fuccess(NotFound)
}
lazy val tooManyRequests = Results.TooManyRequests(jsonError("Error 429: Too many requests! Try again later."))
def toApiResult(json: Option[JsValue]): ApiResult = json.fold[ApiResult](NoData)(Data.apply)
def toApiResult(json: Seq[JsValue]): ApiResult = Data(JsArray(json))
def toHttp(result: ApiResult): Result = result match {
case Limited => tooManyRequests
case NoData => NotFound
case Custom(result) => result
case Data(json) => Ok(json) as JSON
}
def jsonStream(stream: Source[JsObject, _]): Result = jsonStringStream {
stream.map { o => Json.stringify(o) + "\n" }
}
def jsonOptionStream(stream: Source[Option[JsObject], _]): Result = jsonStringStream {
stream.map { _ ?? Json.stringify + "\n" }
}
def jsonStringStream(stream: Source[String, _]): Result =
Ok.chunked(stream).withHeaders(CONTENT_TYPE -> ndJsonContentType) |> noProxyBuffer
private[controllers] val GlobalLinearLimitPerIP = new lila.memo.LinearLimit[IpAddress](
name = "linear API per IP",
key = "api.ip",
@ -333,43 +368,13 @@ final class Api(
user.fold(f) { u =>
GlobalLinearLimitPerUser(u.id)(f)
}
}
private[controllers] object Api {
sealed trait ApiResult
case class Data(json: JsValue) extends ApiResult
case object NoData extends ApiResult
case object Limited extends ApiResult
case class Custom(result: Result) extends ApiResult
def toApiResult(json: Option[JsValue]): ApiResult = json.fold[ApiResult](NoData)(Data.apply)
def toApiResult(json: Seq[JsValue]): ApiResult = Data(JsArray(json))
def CookieBasedApiRequest(js: Context => Fu[ApiResult]) = Open { ctx =>
js(ctx) map toHttp
}
def ApiRequest(js: RequestHeader => Fu[ApiResult]) = Action.async { req =>
js(req) map toHttp
}
def MobileApiRequest(js: RequestHeader => Fu[ApiResult]) = Action.async { req =>
if (lila.api.Mobile.Api requested req) js(req) map toHttp
else fuccess(NotFound)
}
private[controllers] val tooManyRequests = TooManyRequests(jsonError("Error 429: Too many requests! Try again later."))
private[controllers] def toHttp(result: ApiResult): Result = result match {
case Limited => tooManyRequests
case NoData => NotFound
case Custom(result) => result
case Data(json) => Ok(json) as JSON
}
private[controllers] def jsonStream(stream: Source[JsObject, _]): Result = jsonStringStream {
stream.map { o => Json.stringify(o) + "\n" }
}
private[controllers] def jsonOptionStream(stream: Source[Option[JsObject], _]): Result = jsonStringStream {
stream.map { _ ?? Json.stringify + "\n" }
}
private def jsonStringStream(stream: Source[String, _]): Result =
Ok.chunked(stream).withHeaders(CONTENT_TYPE -> ndJsonContentType) |> noProxyBuffer
}

View File

@ -15,7 +15,7 @@ import views._
final class Auth(
env: Env,
accountC: Account
accountC: => Account
) extends LilaController(env) {
private def api = env.security.api

View File

@ -6,7 +6,7 @@ import lila.app._
final class Bot(
env: Env,
apiC: Api
apiC: => Api
) extends LilaController(env) {
def gameStream(id: String) = Scoped(_.Bot.Play) { req => me =>

View File

@ -12,7 +12,7 @@ import views.html
final class Challenge(
env: Env,
setupC: Setup
setupC: => Setup
) extends LilaController(env) {
def api = env.challenge.api

View File

@ -12,7 +12,7 @@ import lila.game.{ Game => GameModel }
final class Game(
env: Env,
apiC: Api
apiC: => Api
) extends LilaController(env) {
def delete(gameId: String) = Auth { implicit ctx => me =>

View File

@ -17,8 +17,8 @@ import play.api.mvc._
final class Mod(
env: Env,
reportC: Report,
userC: User
reportC: => Report,
userC: => User
) extends LilaController(env) {
private def modApi = env.mod.api

View File

@ -13,7 +13,7 @@ import views._
final class Practice(
env: Env,
userAnalysisC: UserAnalysis
userAnalysisC: => UserAnalysis
) extends LilaController(env) {
val api = env.practice.api

View File

@ -13,7 +13,7 @@ import views._
final class Puzzle(
env: Env,
apiC: Api
apiC: => Api
) extends LilaController(env) {
private def renderJson(

View File

@ -15,7 +15,7 @@ import views._
final class Relation(
env: Env,
apiC: Api
apiC: => Api
) extends LilaController(env) {
val api = env.relation.api
@ -96,7 +96,6 @@ final class Relation(
private def apiRelation(name: String, direction: Direction) = Action.async { req =>
env.user.repo.named(name) flatMap {
_ ?? { user =>
import apiC.limitedDefault
apiC.GlobalLinearLimitPerIP(HTTPRequest lastRemoteAddress req) {
apiC.jsonStream {
env.relation.stream

View File

@ -10,7 +10,7 @@ import views._
final class Relay(
env: Env,
studyC: Study
studyC: => Study
) extends LilaController(env) {
def index(page: Int) = Open { implicit ctx =>

View File

@ -12,8 +12,8 @@ import lila.user.{ User => UserModel }
final class Report(
env: Env,
userC: User,
modC: Mod
userC: => User,
modC: => Mod
) extends LilaController(env) {
private def api = env.report.api

View File

@ -14,10 +14,10 @@ import views._
final class Round(
env: Env,
gameC: Game,
challengeC: Challenge,
analyseC: Analyse,
tournamentC: Tournament
gameC: => Game,
challengeC: => Challenge,
analyseC: => Analyse,
tournamentC: => Tournament
) extends LilaController(env) with TheftPrevention {
private def analyser = env.analyse.analyser

View File

@ -8,7 +8,7 @@ import views._
final class Search(
env: Env,
apiC: Api
apiC: => Api
) extends LilaController(env) {
def searchForm = env.gameSearch.forms.search

View File

@ -17,7 +17,7 @@ import views._
final class Setup(
env: Env,
challengeC: Challenge
challengeC: => Challenge
) extends LilaController(env) with TheftPrevention {
private def forms = env.setup.forms

View File

@ -13,13 +13,12 @@ import views._
final class Simul(
env: Env,
teamC: Team
teamC: => Team,
apiC: => Team
) extends LilaController(env) {
private def forms = lila.simul.SimulForm
import teamC.teamsIBelongTo
private def simulNotFound(implicit ctx: Context) = NotFound(html.simul.bits.notFound())
val home = Open { implicit ctx =>
@ -124,7 +123,7 @@ final class Simul(
def form = Auth { implicit ctx => me =>
NoLameOrBot {
teamsIBelongTo(me) map { teams =>
apiC.teamsIBelongTo(me) map { teams =>
Ok(html.simul.form(forms.create, teams))
}
}
@ -134,7 +133,7 @@ final class Simul(
NoLameOrBot {
implicit val req = ctx.body
forms.create.bindFromRequest.fold(
err => teamsIBelongTo(me) map { teams =>
err => apiC.teamsIBelongTo(me) map { teams =>
BadRequest(html.simul.form(err, teams))
},
setup => env.simul.api.create(setup, me) map { simul =>

View File

@ -9,7 +9,7 @@ import views._
final class Streamer(
env: Env,
apiC: Api
apiC: => Api
) extends LilaController(env) {
private def api = env.streamer.api

View File

@ -18,7 +18,7 @@ import views._
final class Study(
env: Env,
userAnalysisC: UserAnalysis
userAnalysisC: => UserAnalysis
) extends LilaController(env) {
def search(text: String, page: Int) = OpenBody { implicit ctx =>

View File

@ -15,7 +15,7 @@ import views._
final class Team(
env: Env,
apiC: Api
apiC: => Api
) extends LilaController(env) {
private def forms = env.team.forms
@ -49,7 +49,6 @@ final class Team(
} yield html.team.show(team, members, info)
def users(teamId: String) = Action.async { req =>
import apiC.limitedDefault
api.team(teamId) flatMap {
_ ?? { team =>
apiC.GlobalLinearLimitPerIP(HTTPRequest lastRemoteAddress req) {

View File

@ -16,7 +16,7 @@ import views._
final class Tournament(
env: Env,
teamC: Team
teamC: => Team
) extends LilaController(env) {
private def repo = env.tournament.tournamentRepo
@ -24,8 +24,6 @@ final class Tournament(
private def jsonView = env.tournament.jsonView
private def forms = env.tournament.forms
import teamC.teamsIBelongTo
private def tournamentNotFound(implicit ctx: Context) = NotFound(html.tournament.bits.notFound())
private[controllers] val upcomingCache = env.memo.asyncCache.single[(VisibleTournaments, List[Tour])](
@ -214,7 +212,7 @@ final class Tournament(
def form = Auth { implicit ctx => me =>
NoLameOrBot {
teamsIBelongTo(me) map { teams =>
teamC.teamsIBelongTo(me) map { teams =>
Ok(html.tournament.form(forms(me), forms, me, teams))
}
}
@ -250,7 +248,7 @@ final class Tournament(
def create = AuthBody { implicit ctx => me =>
NoLameOrBot {
teamsIBelongTo(me) flatMap { teams =>
teamC.teamsIBelongTo(me) flatMap { teams =>
implicit val req = ctx.body
negotiate(
html = forms(me).bindFromRequest.fold(
@ -284,7 +282,7 @@ final class Tournament(
private def doApiCreate(me: lila.user.User)(implicit req: Request[_]): Fu[Result] =
forms(me).bindFromRequest.fold(
jsonFormErrorDefaultLang,
setup => teamsIBelongTo(me) flatMap { teams =>
setup => teamC.teamsIBelongTo(me) flatMap { teams =>
api.createTournament(setup, me, teams, getUserTeamIds) flatMap { tour =>
jsonView(tour, none, none, getUserTeamIds, env.team.getTeamName, none, none, partial = false, lila.i18n.defaultLang)
}

View File

@ -10,7 +10,7 @@ import views._
final class Tv(
env: Env,
apiC: Api
apiC: => Api
) extends LilaController(env) {
def index = onChannel(lila.tv.Tv.Channel.Best.key)
@ -32,7 +32,7 @@ final class Tv(
implicit val championWrites = Json.writes[lila.tv.Tv.Champion]
env.tv.tv.getChampions map {
_.channels map { case (chan, champ) => chan.name -> champ }
} map { Json.toJson(_) } map apiC.Data.apply
} map { Json.toJson(_) } map Api.Data.apply
}
private def lichessTv(channel: lila.tv.Tv.Channel)(implicit ctx: Context) =

View File

@ -22,8 +22,8 @@ import views._
final class User(
env: Env,
roundC: Round,
modC: Mod
roundC: => Round,
modC: => Mod
) extends LilaController(env) {
private def relationApi = env.relation.api

View File

@ -18,7 +18,7 @@ import views._
final class UserAnalysis(
env: Env,
gameC: Game
gameC: => Game
) extends LilaController(env) with TheftPrevention {
def index = load("", Standard)

View File

@ -169,6 +169,7 @@ relation {
}
bookmark {
collection.bookmark = bookmark
paginator.max_per_page = ${game.paginator.max_per_page}
actor.name = bookmark
}
geoip {

View File

@ -1,37 +1,49 @@
package lila.api
import com.softwaremill.macwire._
import io.methvin.play.autoconfig._
import lila.common.config._
import scala.concurrent.duration._
import lila.common.config._
@Module
final class ApiConfig(
@ConfigName("api.token") val apiToken: Secret,
@ConfigName("api.influx_event.endpoint") val influxEventEndpoint: String,
@ConfigName("api.influx_event.env") val influxEventEnv: String,
@ConfigName("app.stage") val isStage: Boolean,
@ConfigName("prismic.api_url") val prismicApiUrl: String,
@ConfigName("editor.animation.duration") val editorAnimationDuration: FiniteDuration,
@ConfigName("explorer.endpoint") val explorerEndpoint: String,
@ConfigName("explorer.tablebase.endpoint") val tablebaseEndpoint: String,
val apiToken: Secret,
val influxEventEndpoint: String,
val influxEventEnv: String,
val isStage: Boolean,
val prismicApiUrl: String,
val editorAnimationDuration: FiniteDuration,
val explorerEndpoint: String,
val tablebaseEndpoint: String,
val accessibility: ApiConfig.Accessibility
)
object ApiConfig {
final class Accessibility(
@ConfigName("blind.cookie.name") val blindCookieName: String,
@ConfigName("blind.cookie.max_age") val blindCookieMaxAge: FiniteDuration,
@ConfigName("blind.cookie.salt") blindCookieSalt: Secret
val blindCookieName: String,
val blindCookieMaxAge: FiniteDuration,
blindCookieSalt: Secret
) {
def hash(implicit ctx: lila.user.UserContext) = {
import com.roundeights.hasher.Implicits._
(ctx.userId | "anon").salt(blindCookieSalt.value).md5.hex
}
}
private implicit val accessibilityLoader = AutoConfig.loader[Accessibility]
implicit val loader = AutoConfig.loader[ApiConfig]
def loadFrom(c: play.api.Configuration) = new ApiConfig(
c.get[Secret]("api.token"),
c.get[String]("api.influx_event.endpoint"),
c.get[String]("api.influx_event.env"),
c.get[Boolean]("app.stage"),
c.get[String]("prismic.api_url"),
c.get[FiniteDuration]("editor.animation.duration"),
c.get[String]("explorer.endpoint"),
c.get[String]("explorer.tablebase.endpoint"),
new Accessibility(
c.get[String]("accessibility.blind.cookie.name"),
c.get[FiniteDuration]("accessibility.blind.cookie.max_age"),
c.get[Secret]("accessibility.blind.cookie.salt")
)
)
}

View File

@ -46,7 +46,8 @@ final class Env(
val mode: Mode
)(implicit system: ActorSystem) {
val config = appConfig.get[ApiConfig]("")(ApiConfig.loader)
val config = ApiConfig loadFrom appConfig
import config.apiToken
lazy val pgnDump: PgnDump = wire[PgnDump]

View File

@ -6,7 +6,7 @@ import play.api.Configuration
import scala.concurrent.duration.FiniteDuration
@Module
private class PlanConfig(
private class BlogConfig(
@ConfigName("prismic.api_url") val apiUrl: String,
@ConfigName("last_post_cache.ttl") val lastPostTtl: FiniteDuration
)
@ -18,7 +18,7 @@ final class Env(
timelineApi: lila.timeline.EntryApi
)(implicit system: akka.actor.ActorSystem, ws: play.api.libs.ws.WSClient) {
private val config = appConfig.get[PlanConfig]("plan")(AutoConfig.loader)
private val config = appConfig.get[BlogConfig]("blog")(AutoConfig.loader)
lazy val api = new BlogApi(
prismicUrl = config.apiUrl,

View File

@ -22,7 +22,7 @@ final class Env(
gameRepo: lila.game.GameRepo
)(implicit system: ActorSystem) {
private val config = appConfig.get[BookmarkConfig]("game")(AutoConfig.loader)
private val config = appConfig.get[BookmarkConfig]("bookmark")(AutoConfig.loader)
private lazy val bookmarkColl = db(config.bookmarkCollName)

View File

@ -17,7 +17,7 @@ final class Env(
def netDomain = netConfig.domain
private lazy val detectLanguageConfig =
appConfig.get[DetectLanguage.Config]("detect_language.api")
appConfig.get[DetectLanguage.Config]("detectlanguage.api")
lazy val detectLanguage = wire[DetectLanguage]
}

View File

@ -30,5 +30,6 @@ object WMMatching {
}
}
private def lowLevel(nvertex: Int, pairScore: (Int, Int) => Option[Int]): List[(Int, Int)] = ???
// #TODO implement
private def lowLevel(nvertex: Int, pairScore: (Int, Int) => Option[Int]): List[(Int, Int)] = Nil
}

View File

@ -2,27 +2,33 @@ package lila.db
import io.methvin.play.autoconfig._
import play.api.Configuration
import play.api.inject.ApplicationLifecycle
import reactivemongo.api._
import reactivemongo.api.commands.Command
import scala.concurrent.duration._
import scala.concurrent.Await
import scala.concurrent.duration._
import scala.util.{ Failure, Success }
import dsl.Coll
import lila.common.Chronometer
import lila.common.config._
class DbConfig(
final class DbConfig(
val uri: String
)
final class Env(name: String, config: DbConfig) {
final class Env(
name: String,
config: DbConfig,
lifecycle: ApplicationLifecycle
) {
private val driver = MongoDriver()
private val parsedUri = MongoConnection.parseURI(config.uri).get
// private val connection = Future.fromTry(parsedUri.flatMap(driver.connection(_, true)))
private val dbName = parsedUri.db | "lichess"
val conn = driver.connection(parsedUri, name.some, true).get
registerDriverShutdownHook(driver, conn)
private val db = Chronometer.syncEffect(
Await.result(conn database dbName, 3.seconds)
) { lap =>
@ -44,14 +50,32 @@ final class Env(name: String, config: DbConfig) {
import DbImage.DbImageBSONHandler
def fetch(id: String): Fu[Option[DbImage]] = imageColl.byId[DbImage](id)
}
private def registerDriverShutdownHook(
mongoDriver: MongoDriver,
connection: MongoConnection
): Unit = lifecycle.addStopHook { () =>
logger.info("ReactiveMongoApi stopping...")
Await.ready(connection.askClose()(10.seconds).map { _ =>
logger.info("ReactiveMongoApi connections are stopped")
}.andThen {
case Failure(reason) =>
reason.printStackTrace()
mongoDriver.close() // Close anyway
case _ => mongoDriver.close()
}, 12.seconds)
}
}
object Env {
implicit val configLoader = AutoConfig.loader[DbConfig]
def main(appConfig: Configuration) = new Env(
def main(appConfig: Configuration, lifecycle: ApplicationLifecycle) = new Env(
name = "main",
config = appConfig.get[DbConfig]("mongodb")
config = appConfig.get[DbConfig]("mongodb"),
lifecycle = lifecycle
)
}

View File

@ -3,33 +3,27 @@ package lila.forum
import lila.common.paginator._
import lila.db.dsl._
final class CategApi(
postApi: => PostApi,
topicApi: => TopicApi,
categRepo: CategRepo,
topicRepo: TopicRepo,
postRepo: PostRepo
) {
final class CategApi(env: Env) {
import BSONHandlers._
def list(teams: Iterable[String], troll: Boolean): Fu[List[CategView]] = for {
categs <- categRepo withTeams teams
categs <- env.categRepo withTeams teams
views <- (categs map { categ =>
postApi get (categ lastPostId troll) map { topicPost =>
env.postApi get (categ lastPostId troll) map { topicPost =>
CategView(categ, topicPost map {
_ match {
case (topic, post) => (topic, post, postApi lastPageOf topic)
case (topic, post) => (topic, post, env.postApi lastPageOf topic)
}
}, troll)
}
}).sequenceFu
} yield views
def teamNbPosts(slug: String): Fu[Int] = categRepo nbPosts teamSlug(slug)
def teamNbPosts(slug: String): Fu[Int] = env.categRepo nbPosts teamSlug(slug)
def makeTeam(slug: String, name: String): Funit =
categRepo.nextPosition flatMap { position =>
env.categRepo.nextPosition flatMap { position =>
val categ = Categ(
_id = teamSlug(slug),
name = name,
@ -63,25 +57,25 @@ final class CategApi(
categId = categ.id,
modIcon = None
)
categRepo.coll.insert.one(categ).void >>
postRepo.coll.insert.one(post).void >>
topicRepo.coll.insert.one(topic withPost post).void >>
categRepo.coll.update.one($id(categ.id), categ withTopic post).void
env.categRepo.coll.insert.one(categ).void >>
env.postRepo.coll.insert.one(post).void >>
env.topicRepo.coll.insert.one(topic withPost post).void >>
env.categRepo.coll.update.one($id(categ.id), categ withTopic post).void
}
def show(slug: String, page: Int, troll: Boolean): Fu[Option[(Categ, Paginator[TopicView])]] =
optionT(categRepo bySlug slug) flatMap { categ =>
optionT(topicApi.paginator(categ, page, troll) map { (categ, _).some })
optionT(env.categRepo bySlug slug) flatMap { categ =>
optionT(env.topicApi.paginator(categ, page, troll) map { (categ, _).some })
} run
def denormalize(categ: Categ): Funit = for {
nbTopics <- topicRepo countByCateg categ
nbPosts <- postRepo countByCateg categ
lastPost <- postRepo lastByCateg categ
nbTopicsTroll <- topicRepo withTroll true countByCateg categ
nbPostsTroll <- postRepo withTroll true countByCateg categ
lastPostTroll <- postRepo withTroll true lastByCateg categ
_ <- categRepo.coll.update.one($id(categ.id), categ.copy(
nbTopics <- env.topicRepo countByCateg categ
nbPosts <- env.postRepo countByCateg categ
lastPost <- env.postRepo lastByCateg categ
nbTopicsTroll <- env.topicRepo withTroll true countByCateg categ
nbPostsTroll <- env.postRepo withTroll true countByCateg categ
lastPostTroll <- env.postRepo withTroll true lastByCateg categ
_ <- env.categRepo.coll.update.one($id(categ.id), categ.copy(
nbTopics = nbTopics,
nbPosts = nbPosts,
lastPostId = lastPost ?? (_.id),

View File

@ -42,20 +42,22 @@ final class Env(
lazy val topicRepo = new TopicRepo(db(CollName("f_topic")))
lazy val postRepo = new PostRepo(db(CollName("f_post")))
lazy val categApi: CategApi = wire[CategApi]
lazy val mentionNotifier: MentionNotifier = wire[MentionNotifier]
lazy val categApi: CategApi = {
val mk = (env: Env) => wire[CategApi]
mk(this)
}
lazy val topicApi: TopicApi = {
val mk = (max: MaxPerPage) => wire[TopicApi]
mk(config.topicMaxPerPage)
val mk = (max: MaxPerPage, env: Env) => wire[TopicApi]
mk(config.topicMaxPerPage, this)
}
lazy val postApi: PostApi = {
val mk = (max: MaxPerPage) => wire[PostApi]
mk(config.postMaxPerPage)
val mk = (max: MaxPerPage, env: Env) => wire[PostApi]
mk(config.postMaxPerPage, this)
}
lazy val mentionNotifier: MentionNotifier = wire[MentionNotifier]
lazy val forms = wire[DataForm]
lazy val recent = wire[Recent]

View File

@ -12,20 +12,14 @@ import lila.user.{ User, UserContext }
import org.joda.time.DateTime
final class PostApi(
categApi: => CategApi,
topicApi: => TopicApi,
categRepo: CategRepo,
topicRepo: TopicRepo,
postRepo: PostRepo,
recent: Recent,
env: Env,
indexer: lila.hub.actors.ForumSearch,
maxPerPage: lila.common.config.MaxPerPage,
modLog: ModlogApi,
spam: lila.security.Spam,
timeline: lila.hub.actors.Timeline,
shutup: lila.hub.actors.Shutup,
detectLanguage: lila.common.DetectLanguage,
mentionNotifier: MentionNotifier
detectLanguage: lila.common.DetectLanguage
) {
import BSONHandlers._
@ -50,16 +44,16 @@ final class PostApi(
categId = categ.id,
modIcon = (~data.modIcon && ~ctx.me.map(MasterGranter(_.PublicMod))).option(true)
)
postRepo findDuplicate post flatMap {
env.postRepo findDuplicate post flatMap {
case Some(dup) => fuccess(dup)
case _ =>
postRepo.coll.insert.one(post) >>
topicRepo.coll.update.one($id(topic.id), topic withPost post) >> {
shouldHideOnPost(topic) ?? topicRepo.hide(topic.id, true)
env.postRepo.coll.insert.one(post) >>
env.topicRepo.coll.update.one($id(topic.id), topic withPost post) >> {
shouldHideOnPost(topic) ?? env.topicRepo.hide(topic.id, true)
} >>
categRepo.coll.update.one($id(categ.id), categ withTopic post) >>-
env.categRepo.coll.update.one($id(categ.id), categ withTopic post) >>-
(!categ.quiet ?? (indexer ! InsertPost(post))) >>-
(!categ.quiet ?? recent.invalidate) >>-
(!categ.quiet ?? env.recent.invalidate) >>-
ctx.userId.?? { userId =>
shutup ! {
if (post.isTeam) lila.hub.actorApi.shutup.RecordTeamForumMessage(userId, post.text)
@ -72,7 +66,7 @@ final class PostApi(
}
}
lila.mon.forum.post.create()
mentionNotifier.notifyMentionedUsers(post, topic)
env.mentionNotifier.notifyMentionedUsers(post, topic)
Bus.publish(actorApi.CreatePost(post, topic), "forumPost")
} inject post
}
@ -87,7 +81,7 @@ final class PostApi(
fufail("Post can no longer be edited")
case (_, post) =>
val newPost = post.editPost(DateTime.now, spam replace newText)
postRepo.coll.update.one($id(post.id), newPost) inject newPost
env.postRepo.coll.update.one($id(post.id), newPost) inject newPost
}
}
@ -103,7 +97,7 @@ final class PostApi(
def urlData(postId: String, troll: Boolean): Fu[Option[PostUrlData]] = get(postId) flatMap {
case Some((topic, post)) if (!troll && post.troll) => fuccess(none[PostUrlData])
case Some((topic, post)) => postRepo.withTroll(troll).countBeforeNumber(topic.id, post.number) map { nb =>
case Some((topic, post)) => env.postRepo.withTroll(troll).countBeforeNumber(topic.id, post.number) map { nb =>
val page = nb / maxPerPage.value + 1
PostUrlData(topic.categId, topic.slug, page, post.number).some
}
@ -112,14 +106,14 @@ final class PostApi(
def get(postId: String): Fu[Option[(Topic, Post)]] = {
for {
post <- optionT(postRepo.coll.byId[Post](postId))
topic <- optionT(topicRepo.coll.byId[Topic](post.topicId))
post <- optionT(env.postRepo.coll.byId[Post](postId))
topic <- optionT(env.topicRepo.coll.byId[Topic](post.topicId))
} yield topic -> post
} run
def views(posts: List[Post]): Fu[List[PostView]] = for {
topics <- topicRepo.coll.byIds[Topic](posts.map(_.topicId).distinct)
categs <- categRepo.coll.byIds[Categ](topics.map(_.categId).distinct)
topics <- env.topicRepo.coll.byIds[Topic](posts.map(_.topicId).distinct)
categs <- env.categRepo.coll.byIds[Categ](topics.map(_.categId).distinct)
} yield posts map { post =>
for {
topic <- topics find (_.id == post.topicId)
@ -128,27 +122,27 @@ final class PostApi(
} flatten
def viewsFromIds(postIds: Seq[Post.ID]): Fu[List[PostView]] =
postRepo.coll.byOrderedIds[Post, Post.ID](postIds)(_.id) flatMap views
env.postRepo.coll.byOrderedIds[Post, Post.ID](postIds)(_.id) flatMap views
def view(post: Post): Fu[Option[PostView]] =
views(List(post)) map (_.headOption)
def liteViews(posts: Seq[Post]): Fu[Seq[PostLiteView]] =
for {
topics <- topicRepo.coll.byIds[Topic](posts.map(_.topicId).distinct)
topics <- env.topicRepo.coll.byIds[Topic](posts.map(_.topicId).distinct)
} yield posts flatMap { post =>
topics.find(_.id == post.topicId) map { topic =>
PostLiteView(post, topic)
}
}
def liteViewsByIds(postIds: Seq[Post.ID]): Fu[Seq[PostLiteView]] =
postRepo.byIds(postIds) flatMap liteViews
env.postRepo.byIds(postIds) flatMap liteViews
def liteView(post: Post): Fu[Option[PostLiteView]] =
liteViews(List(post)) map (_.headOption)
def miniPosts(posts: List[Post]): Fu[List[MiniForumPost]] = for {
topics <- topicRepo.coll.byIds[Topic](posts.map(_.topicId).distinct)
topics <- env.topicRepo.coll.byIds[Topic](posts.map(_.topicId).distinct)
} yield posts flatMap { post =>
topics find (_.id == post.topicId) map { topic =>
MiniForumPost(
@ -163,45 +157,45 @@ final class PostApi(
}
def lastNumberOf(topic: Topic): Fu[Int] =
postRepo lastByTopic topic map { _ ?? (_.number) }
env.postRepo lastByTopic topic map { _ ?? (_.number) }
def lastPageOf(topic: Topic) =
math.ceil(topic.nbPosts / maxPerPage.value.toFloat).toInt
def paginator(topic: Topic, page: Int, troll: Boolean): Fu[Paginator[Post]] = Paginator(
new Adapter(
collection = postRepo.coll,
selector = postRepo.withTroll(troll) selectTopic topic.id,
collection = env.postRepo.coll,
selector = env.postRepo.withTroll(troll) selectTopic topic.id,
projection = none,
sort = postRepo.sortQuery
sort = env.postRepo.sortQuery
),
currentPage = page,
maxPerPage = maxPerPage
)
def delete(categSlug: String, postId: String, mod: User): Funit = (for {
post <- optionT(postRepo.withTroll(true).byCategAndId(categSlug, postId))
post <- optionT(env.postRepo.withTroll(true).byCategAndId(categSlug, postId))
view <- optionT(view(post))
_ <- optionT(for {
first <- postRepo.isFirstPost(view.topic.id, view.post.id)
_ <- if (first) topicApi.delete(view.categ, view.topic)
else postRepo.coll.delete.one(view.post) >>
(topicApi denormalize view.topic) >>
(categApi denormalize view.categ) >>-
recent.invalidate >>-
first <- env.postRepo.isFirstPost(view.topic.id, view.post.id)
_ <- if (first) env.topicApi.delete(view.categ, view.topic)
else env.postRepo.coll.delete.one(view.post) >>
(env.topicApi denormalize view.topic) >>
(env.categApi denormalize view.categ) >>-
env.recent.invalidate >>-
(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) = postRepo.coll.countSel($doc("userId" -> userId))
def nbByUser(userId: String) = env.postRepo.coll.countSel($doc("userId" -> userId))
def userIds(topic: Topic) = postRepo userIdsByTopicId topic.id
def userIds(topic: Topic) = env.postRepo userIdsByTopicId topic.id
def userIds(topicId: String) = postRepo userIdsByTopicId topicId
def userIds(topicId: String) = env.postRepo userIdsByTopicId topicId
def erase(user: User) = postRepo.coll.update.one(
def erase(user: User) = env.postRepo.coll.update.one(
$doc("userId" -> user.id),
$unset("userId", "editHistory", "lang", "ip") ++
$set("text" -> "", "erasedAt" -> DateTime.now),

View File

@ -11,20 +11,14 @@ import lila.security.{ Granter => MasterGranter }
import lila.user.{ User, UserContext }
private[forum] final class TopicApi(
postApi: => PostApi,
categApi: => CategApi,
categRepo: CategRepo,
topicRepo: TopicRepo,
postRepo: PostRepo,
recent: Recent,
env: Env,
indexer: lila.hub.actors.ForumSearch,
maxPerPage: lila.common.config.MaxPerPage,
modLog: lila.mod.ModlogApi,
spam: lila.security.Spam,
timeline: lila.hub.actors.Timeline,
shutup: lila.hub.actors.Shutup,
detectLanguage: lila.common.DetectLanguage,
mentionNotifier: MentionNotifier
detectLanguage: lila.common.DetectLanguage
) {
import BSONHandlers._
@ -32,14 +26,14 @@ private[forum] final class TopicApi(
def show(categSlug: String, slug: String, page: Int, troll: Boolean): Fu[Option[(Categ, Topic, Paginator[Post])]] =
for {
data <- (for {
categ <- optionT(categRepo bySlug categSlug)
topic <- optionT(topicRepo.withTroll(troll).byTree(categSlug, slug))
categ <- optionT(env.categRepo bySlug categSlug)
topic <- optionT(env.topicRepo.withTroll(troll).byTree(categSlug, slug))
} yield categ -> topic).run
res <- data ?? {
case (categ, topic) =>
lila.mon.forum.topic.view()
topicRepo incViews topic
postApi.paginator(topic, page, troll) map { (categ, topic, _).some }
env.topicRepo incViews topic
env.postApi.paginator(topic, page, troll) map { (categ, topic, _).some }
}
} yield res
@ -47,7 +41,7 @@ private[forum] final class TopicApi(
categ: Categ,
data: DataForm.TopicData
)(implicit ctx: UserContext): Fu[Topic] =
topicRepo.nextSlug(categ, data.name) zip detectLanguage(data.post.text) flatMap {
env.topicRepo.nextSlug(categ, data.name) zip detectLanguage(data.post.text) flatMap {
case (slug, lang) =>
val topic = Topic.make(
categId = categ.slug,
@ -69,11 +63,11 @@ private[forum] final class TopicApi(
categId = categ.id,
modIcon = (~data.post.modIcon && ~ctx.me.map(MasterGranter(_.PublicMod))).option(true)
)
postRepo.coll.insert.one(post) >>
topicRepo.coll.insert.one(topic withPost post) >>
categRepo.coll.update.one($id(categ.id), categ withTopic post) >>-
env.postRepo.coll.insert.one(post) >>
env.topicRepo.coll.insert.one(topic withPost post) >>
env.categRepo.coll.update.one($id(categ.id), categ withTopic post) >>-
(!categ.quiet ?? (indexer ! InsertPost(post))) >>-
(!categ.quiet ?? recent.invalidate) >>-
(!categ.quiet ?? env.recent.invalidate) >>-
ctx.userId.?? { userId =>
val text = s"${topic.name} ${post.text}"
shutup ! {
@ -86,7 +80,7 @@ private[forum] final class TopicApi(
}
lila.mon.forum.post.create()
} >>- {
mentionNotifier.notifyMentionedUsers(post, topic)
env.mentionNotifier.notifyMentionedUsers(post, topic)
Bus.publish(actorApi.CreatePost(post, topic), "forumPost")
} inject topic
}
@ -112,24 +106,24 @@ private[forum] final class TopicApi(
categId = categ.id,
modIcon = true.some
)
postRepo.coll.insert.one(post) >>
topicRepo.coll.insert.one(topic withPost post) >>
categRepo.coll.update.one($id(categ.id), categ withTopic post) >>-
env.postRepo.coll.insert.one(post) >>
env.topicRepo.coll.insert.one(topic withPost post) >>
env.categRepo.coll.update.one($id(categ.id), categ withTopic post) >>-
(indexer ! InsertPost(post)) >>-
recent.invalidate >>-
env.recent.invalidate >>-
Bus.publish(actorApi.CreatePost(post, topic), "forumPost") void
}
def paginator(categ: Categ, page: Int, troll: Boolean): Fu[Paginator[TopicView]] = {
val adapter = new Adapter[Topic](
collection = topicRepo.coll,
selector = topicRepo.withTroll(troll) byCategNotStickyQuery categ,
collection = env.topicRepo.coll,
selector = env.topicRepo.withTroll(troll) byCategNotStickyQuery categ,
projection = none,
sort = $sort.updatedDesc
) mapFutureList { topics =>
postRepo.coll.optionsByOrderedIds[Post, String](topics.map(_ lastPostId troll))(_.id) map { posts =>
env.postRepo.coll.optionsByOrderedIds[Post, String](topics.map(_ lastPostId troll))(_.id) map { posts =>
topics zip posts map {
case topic ~ post => TopicView(categ, topic, post, postApi lastPageOf topic, troll)
case topic ~ post => TopicView(categ, topic, post, env.postApi lastPageOf topic, troll)
}
}
}
@ -144,48 +138,48 @@ private[forum] final class TopicApi(
}
def getSticky(categ: Categ, troll: Boolean): Fu[List[TopicView]] =
topicRepo.stickyByCateg(categ) flatMap { topics =>
env.topicRepo.stickyByCateg(categ) flatMap { topics =>
topics.map { topic =>
postRepo.coll.byId[Post](topic lastPostId troll) map { post =>
TopicView(categ, topic, post, postApi lastPageOf topic, troll)
env.postRepo.coll.byId[Post](topic lastPostId troll) map { post =>
TopicView(categ, topic, post, env.postApi lastPageOf topic, troll)
}
}.sequenceFu
}
def delete(categ: Categ, topic: Topic): Funit =
postRepo.idsByTopicId(topic.id) flatMap { postIds =>
(postRepo removeByTopic topic.id zip topicRepo.coll.delete.one($id(topic.id))) >>
(categApi denormalize categ) >>-
env.postRepo.idsByTopicId(topic.id) flatMap { postIds =>
(env.postRepo removeByTopic topic.id zip env.topicRepo.coll.delete.one($id(topic.id))) >>
(env.categApi denormalize categ) >>-
(indexer ! RemovePosts(postIds)) >>-
recent.invalidate
env.recent.invalidate
}
def toggleClose(categ: Categ, topic: Topic, mod: User): Funit =
topicRepo.close(topic.id, topic.open) >> {
env.topicRepo.close(topic.id, topic.open) >> {
MasterGranter(_.ModerateForum)(mod) ??
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) >> {
env.topicRepo.hide(topic.id, topic.visibleOnHome) >> {
MasterGranter(_.ModerateForum)(mod) ?? {
postRepo.hideByTopic(topic.id, topic.visibleOnHome) >>
env.postRepo.hideByTopic(topic.id, topic.visibleOnHome) >>
modLog.toggleHideTopic(mod.id, categ.name, topic.name, topic.visibleOnHome)
} >>- recent.invalidate
} >>- env.recent.invalidate
}
def toggleSticky(categ: Categ, topic: Topic, mod: User): Funit =
topicRepo.sticky(topic.id, !topic.isSticky) >> {
env.topicRepo.sticky(topic.id, !topic.isSticky) >> {
MasterGranter(_.ModerateForum)(mod) ??
modLog.toggleStickyTopic(mod.id, categ.name, topic.name, topic.isSticky)
}
def denormalize(topic: Topic): Funit = for {
nbPosts <- postRepo countByTopic topic
lastPost <- postRepo lastByTopic topic
nbPostsTroll <- postRepo withTroll true countByTopic topic
lastPostTroll <- postRepo withTroll true lastByTopic topic
_ <- topicRepo.coll.update.one($id(topic.id), topic.copy(
nbPosts <- env.postRepo countByTopic topic
lastPost <- env.postRepo lastByTopic topic
nbPostsTroll <- env.postRepo withTroll true countByTopic topic
lastPostTroll <- env.postRepo withTroll true lastByTopic topic
_ <- env.topicRepo.coll.update.one($id(topic.id), topic.copy(
nbPosts = nbPosts,
lastPostId = lastPost ?? (_.id),
updatedAt = lastPost.fold(topic.updatedAt)(_.createdAt),

View File

@ -11,7 +11,7 @@ import Query.jsonWriter
@Module
private class ForumSearchConfig(
@ConfigName("index.name") val indexName: String,
@ConfigName("index") val indexName: String,
@ConfigName("paginator.max_per_page") val maxPerPage: MaxPerPage,
@ConfigName("actor.name") val actorName: String
)

View File

@ -10,8 +10,7 @@ import scala.concurrent.duration._
import lila.common.config._
@Module
private class GameConfig(
private final class GameConfig(
@ConfigName("collection.game") val gameColl: CollName,
@ConfigName("collection.crosstable") val crosstableColl: CollName,
@ConfigName("collection.matchup") val matchupColl: CollName,
@ -38,6 +37,7 @@ final class Env(
)(implicit system: ActorSystem, scheduler: Scheduler) {
private val config = appConfig.get[GameConfig]("game")(AutoConfig.loader)
import config.paginatorMaxPerPage
lazy val gameRepo = new GameRepo(db(config.gameColl))

View File

@ -15,12 +15,14 @@ final class Env(
gameRepo: lila.game.GameRepo,
analysisRepo: lila.analyse.AnalysisRepo,
prefApi: lila.pref.PrefApi,
relationApi: lila.relation.RelationApi
relationApi: lila.relation.RelationApi,
lifecycle: play.api.inject.ApplicationLifecycle
)(implicit system: akka.actor.ActorSystem) {
private lazy val db = new lila.db.Env(
"insight",
appConfig.get[DbConfig]("insight.mongodb")(AutoConfig.loader)
appConfig.get[DbConfig]("insight.mongodb")(AutoConfig.loader),
lifecycle
)
lazy val share = wire[Share]

View File

@ -22,13 +22,12 @@ final class Env(
appConfig: Configuration,
asyncCache: lila.memo.AsyncCache.Builder,
userRepo: lila.user.UserRepo,
system: ActorSystem,
lifecycle: play.api.inject.ApplicationLifecycle
) {
)(implicit system: ActorSystem) {
private val config = appConfig.get[OauthConfig]("oauth")(AutoConfig.loader)
private lazy val db = new lila.db.Env("oauth", config.mongodb)
private lazy val db = new lila.db.Env("oauth", config.mongodb, lifecycle)
private lazy val tokenColl = db(config.tokenColl)
private lazy val appColl = db(config.appColl)

View File

@ -50,7 +50,7 @@ private object OneSignalPush {
final class Config(
val url: String,
val appId: String,
@ConfigName("app_id") val appId: String,
val key: lila.common.config.Secret
)
implicit val configLoader = AutoConfig.loader[Config]

View File

@ -49,7 +49,7 @@ private object WebPush {
final class Config(
val url: String,
val vapidPublicKey: String
@ConfigName("vapid_public_key") val vapidPublicKey: String
)
implicit val configLoader = AutoConfig.loader[Config]
}

View File

@ -10,7 +10,7 @@ import lila.common.config._
import lila.db.Env.configLoader
@Module
private class CoachConfig(
private class PuzzleConfig(
val mongodb: lila.db.DbConfig,
@ConfigName("collection.puzzle") val puzzleColl: CollName,
@ConfigName("collection.round") val roundColl: CollName,
@ -28,12 +28,13 @@ final class Env(
lightUserApi: lila.user.LightUserApi,
asyncCache: lila.memo.AsyncCache.Builder,
gameRepo: lila.game.GameRepo,
userRepo: lila.user.UserRepo
userRepo: lila.user.UserRepo,
lifecycle: play.api.inject.ApplicationLifecycle
)(implicit system: ActorSystem) {
private val config = appConfig.get[CoachConfig]("coach")(AutoConfig.loader)
private val config = appConfig.get[PuzzleConfig]("puzzle")(AutoConfig.loader)
private lazy val db = new lila.db.Env("puzzle", config.mongodb)
private lazy val db = new lila.db.Env("puzzle", config.mongodb, lifecycle)
private lazy val gameJson = wire[GameJson]