use async team caches whenever possible
parent
68d4fe196c
commit
5b9a3a840a
|
@ -40,6 +40,7 @@ final class Env(
|
|||
getRanks = Env.user.cached.ranking.getAll,
|
||||
isHostingSimul = Env.simul.isHosting,
|
||||
fetchIsStreamer = Env.tv.isStreamer.apply,
|
||||
fetchTeamIds = Env.team.api.teamIds,
|
||||
fetchIsCoach = Env.coach.api.isListedCoach,
|
||||
insightShare = Env.insight.share,
|
||||
getPlayTime = Env.game.playTime.apply,
|
||||
|
@ -62,7 +63,8 @@ final class Env(
|
|||
system.actorOf(Props(new actor.Renderer), name = RendererName)
|
||||
|
||||
lila.log.boot.info("Preloading modules")
|
||||
lila.common.Chronometer.syncEffect(List(Env.socket,
|
||||
lila.common.Chronometer.syncEffect(List(
|
||||
Env.socket,
|
||||
Env.site,
|
||||
Env.tournament,
|
||||
Env.lobby,
|
||||
|
|
|
@ -7,7 +7,10 @@ object ForumCateg extends LilaController with ForumController {
|
|||
|
||||
def index = Open { implicit ctx =>
|
||||
NotForKids {
|
||||
categApi.list(ctx.userId ?? teamCache.teamIds, ctx.troll) map { html.forum.categ.index(_) }
|
||||
for {
|
||||
teamIds <- ctx.userId ?? teamCache.teamIds
|
||||
categs <- categApi.list(teamIds, ctx.troll)
|
||||
} yield html.forum.categ.index(categs)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,8 +18,11 @@ object ForumCateg extends LilaController with ForumController {
|
|||
NotForKids {
|
||||
Reasonable(page, 50, errorPage = notFound) {
|
||||
CategGrantRead(slug) {
|
||||
OptionOk(categApi.show(slug, page, ctx.troll)) {
|
||||
case (categ, topics) => html.forum.categ.show(categ, topics)
|
||||
OptionFuOk(categApi.show(slug, page, ctx.troll)) {
|
||||
case (categ, topics) =>
|
||||
isGrantedWrite(categ.slug) map { canWrite =>
|
||||
html.forum.categ.show(categ, topics, canWrite)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,20 +15,23 @@ private[controllers] trait ForumController extends forum.Granter { self: LilaCon
|
|||
|
||||
protected def teamCache = Env.team.cached
|
||||
|
||||
protected def userBelongsToTeam(teamId: String, userId: String): Boolean =
|
||||
protected def userBelongsToTeam(teamId: String, userId: String): Fu[Boolean] =
|
||||
Env.team.api.belongsTo(teamId, userId)
|
||||
|
||||
protected def userOwnsTeam(teamId: String, userId: String): Fu[Boolean] =
|
||||
Env.team.api.owns(teamId, userId)
|
||||
|
||||
protected def CategGrantRead[A <: Result](categSlug: String)(a: => Fu[A])(implicit ctx: Context): Fu[Result] =
|
||||
isGrantedRead(categSlug).fold(a,
|
||||
isGrantedRead(categSlug).fold(
|
||||
a,
|
||||
fuccess(Forbidden("You cannot access to this category"))
|
||||
)
|
||||
|
||||
protected def CategGrantWrite[A <: Result](categSlug: String)(a: => Fu[A])(implicit ctx: Context): Fu[Result] =
|
||||
if (isGrantedWrite(categSlug)) a
|
||||
else fuccess(Forbidden("You cannot post to this category"))
|
||||
isGrantedWrite(categSlug) flatMap { granted =>
|
||||
if (granted) a
|
||||
else fuccess(Forbidden("You cannot post to this category"))
|
||||
}
|
||||
|
||||
protected def CategGrantMod[A <: Result](categSlug: String)(a: => Fu[A])(implicit ctx: Context): Fu[Result] =
|
||||
isGrantedMod(categSlug) flatMap { granted =>
|
||||
|
|
|
@ -48,7 +48,8 @@ object ForumTopic extends LilaController with ForumController {
|
|||
OptionFuOk(topicApi.show(categSlug, slug, page, ctx.troll)) {
|
||||
case (categ, topic, posts) => for {
|
||||
unsub <- ctx.userId ?? Env.timeline.status(s"forum:${topic.id}")
|
||||
form <- (!posts.hasNextPage && isGrantedWrite(categSlug) && topic.open) ?? forms.postWithCaptcha.map(_.some)
|
||||
canWrite <- isGrantedWrite(categSlug)
|
||||
form <- (!posts.hasNextPage && canWrite && topic.open) ?? forms.postWithCaptcha.map(_.some)
|
||||
canModCateg <- isGrantedMod(categ.slug)
|
||||
} yield html.forum.topic.show(categ, topic, posts, form, unsub, canModCateg = canModCateg)
|
||||
}
|
||||
|
|
|
@ -24,8 +24,10 @@ object Team extends LilaController {
|
|||
|
||||
def home(page: Int) = Open { implicit ctx =>
|
||||
NotForKids {
|
||||
if (ctx.me.??(api.hasTeams)) Redirect(routes.Team.mine).fuccess
|
||||
else Redirect(routes.Team.all(page)).fuccess
|
||||
ctx.me.??(api.hasTeams) map {
|
||||
case true => Redirect(routes.Team.mine)
|
||||
case false => Redirect(routes.Team.all(page))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ final class TeamInfoApi(
|
|||
|
||||
def apply(team: Team, me: Option[User]): Fu[TeamInfo] = for {
|
||||
requests ← (team.enabled && me.??(m => team.isCreator(m.id))) ?? api.requestsWithUsers(team)
|
||||
mine = me.??(m => api.belongsTo(team.id, m.id))
|
||||
mine <- me.??(m => api.belongsTo(team.id, m.id))
|
||||
requestedByMe ← !mine ?? me.??(m => RequestRepo.exists(team.id, m.id))
|
||||
cachable <- cache(team.id)
|
||||
forumNbPosts ← getForumNbPosts(team.id)
|
||||
|
|
|
@ -24,6 +24,7 @@ case class UserInfo(
|
|||
nbStudies: Int,
|
||||
playTime: User.PlayTime,
|
||||
trophies: Trophies,
|
||||
teamIds: Set[String],
|
||||
isStreamer: Boolean,
|
||||
isCoach: Boolean,
|
||||
insightVisible: Boolean,
|
||||
|
@ -73,6 +74,7 @@ object UserInfo {
|
|||
getRanks: String => Fu[Map[String, Int]],
|
||||
isHostingSimul: String => Fu[Boolean],
|
||||
fetchIsStreamer: String => Fu[Boolean],
|
||||
fetchTeamIds: User.ID => Fu[Set[String]],
|
||||
fetchIsCoach: User => Fu[Boolean],
|
||||
insightShare: lila.insight.Share,
|
||||
getPlayTime: User => Fu[User.PlayTime],
|
||||
|
@ -87,13 +89,14 @@ object UserInfo {
|
|||
postApi.nbByUser(user.id) zip
|
||||
studyRepo.countByOwner(user.id) zip
|
||||
trophyApi.findByUser(user) zip
|
||||
fetchTeamIds(user.id) zip
|
||||
fetchIsCoach(user) zip
|
||||
fetchIsStreamer(user.id) zip
|
||||
(user.count.rated >= 10).??(insightShare.grant(user, ctx.me)) zip
|
||||
getPlayTime(user) zip
|
||||
completionRate(user.id) zip
|
||||
bookmarkApi.countByUser(user) flatMap {
|
||||
case (((((((((((((((ranks, nbPlaying), nbImported), crosstable), ratingChart), nbFollowers), nbBlockers), nbPosts), nbStudies), trophies), isCoach), isStreamer), insightVisible), playTime), completionRate), nbBookmarks) =>
|
||||
case ((((((((((((((((ranks, nbPlaying), nbImported), crosstable), ratingChart), nbFollowers), nbBlockers), nbPosts), nbStudies), trophies), teamIds), isCoach), isStreamer), insightVisible), playTime), completionRate), nbBookmarks) =>
|
||||
(nbPlaying > 0) ?? isHostingSimul(user.id) map { hasSimul =>
|
||||
new UserInfo(
|
||||
user = user,
|
||||
|
@ -110,6 +113,7 @@ object UserInfo {
|
|||
nbStudies = nbStudies,
|
||||
playTime = playTime,
|
||||
trophies = trophies,
|
||||
teamIds = teamIds,
|
||||
isStreamer = isStreamer,
|
||||
isCoach = isCoach,
|
||||
insightVisible = insightVisible,
|
||||
|
|
|
@ -10,7 +10,7 @@ trait ForumHelper { self: UserHelper with StringHelper =>
|
|||
|
||||
private object Granter extends lila.forum.Granter {
|
||||
|
||||
protected def userBelongsToTeam(teamId: String, userId: String): Boolean =
|
||||
protected def userBelongsToTeam(teamId: String, userId: String): Fu[Boolean] =
|
||||
Env.team.api.belongsTo(teamId, userId)
|
||||
|
||||
protected def userOwnsTeam(teamId: String, userId: String): Fu[Boolean] =
|
||||
|
|
|
@ -12,9 +12,7 @@ trait TeamHelper {
|
|||
private def api = teamEnv.api
|
||||
|
||||
def myTeam(teamId: String)(implicit ctx: Context): Boolean =
|
||||
ctx.me.??(me => api.belongsTo(teamId, me.id))
|
||||
|
||||
def teamIds(userId: String): Set[String] = api teamIds userId
|
||||
ctx.me.??(me => api.syncBelongsTo(teamId, me.id))
|
||||
|
||||
def teamIdToName(id: String): String = api teamName id getOrElse id
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
@(categ: lila.forum.Categ, topics: Paginator[lila.forum.TopicView])(implicit ctx: Context)
|
||||
@(categ: lila.forum.Categ, topics: Paginator[lila.forum.TopicView], canWrite: Boolean)(implicit ctx: Context)
|
||||
|
||||
@newTopicButton = {
|
||||
@if(isGrantedWrite(categ.slug)) {
|
||||
@if(canWrite) {
|
||||
<a href="@routes.ForumTopic.form(categ.slug)" class="action button" data-icon="m"> @trans.createANewTopic()</a>
|
||||
}
|
||||
}
|
||||
|
|
|
@ -209,8 +209,8 @@ description = describeUser(u)).some) {
|
|||
<p>@trans.tpTimeSpentOnTV(showPeriod(tvPeriod))</p>
|
||||
}
|
||||
<div class="teams">
|
||||
@teamIds(u.id).toList.sortBy(t => !myTeam(t)).map { teamId =>
|
||||
@teamLink(teamId, myTeam(teamId).option("mine"))
|
||||
@info.teamIds.toList.map(t => t -> myTeam(t)).sortBy(t => (!t._2, t._1)).map { t =>
|
||||
@teamLink(t._1, t._2.option("mine"))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package lila.forum
|
||||
|
||||
import lila.security.{ Permission, Granter => Master }
|
||||
import lila.user.UserContext
|
||||
import lila.user.{ User, UserContext }
|
||||
import org.joda.time.DateTime
|
||||
|
||||
trait Granter {
|
||||
|
@ -9,7 +9,7 @@ trait Granter {
|
|||
private val TeamSlugPattern = """^team-([\w-]+)$""".r
|
||||
private val StaffSlug = "staff"
|
||||
|
||||
protected def userBelongsToTeam(teamId: String, userId: String): Boolean
|
||||
protected def userBelongsToTeam(teamId: String, userId: String): Fu[Boolean]
|
||||
protected def userOwnsTeam(teamId: String, userId: String): Fu[Boolean]
|
||||
|
||||
def isGrantedRead(categSlug: String)(implicit ctx: UserContext): Boolean =
|
||||
|
@ -17,21 +17,18 @@ trait Granter {
|
|||
ctx.me exists Master(Permission.StaffForum),
|
||||
true)
|
||||
|
||||
def isGrantedWrite(categSlug: String)(implicit ctx: UserContext): Boolean =
|
||||
isOldEnoughToForum && {
|
||||
ctx.me ?? { me =>
|
||||
Master(Permission.StaffForum)(me) || {
|
||||
categSlug match {
|
||||
case StaffSlug => false
|
||||
case TeamSlugPattern(teamId) => userBelongsToTeam(teamId, me.id)
|
||||
case _ => true
|
||||
}
|
||||
}
|
||||
def isGrantedWrite(categSlug: String)(implicit ctx: UserContext): Fu[Boolean] =
|
||||
ctx.me.filter(isOldEnoughToForum) ?? { me =>
|
||||
if (Master(Permission.StaffForum)(me)) fuccess(true)
|
||||
else categSlug match {
|
||||
case StaffSlug => fuccess(false)
|
||||
case TeamSlugPattern(teamId) => userBelongsToTeam(teamId, me.id)
|
||||
case _ => fuccess(true)
|
||||
}
|
||||
}
|
||||
|
||||
def isOldEnoughToForum(implicit ctx: UserContext) =
|
||||
ctx.me ?? { u => u.count.game > 0 && (u.createdAt isBefore DateTime.now.minusDays(2)) }
|
||||
private def isOldEnoughToForum(u: User) =
|
||||
u.count.game > 0 && (u.createdAt isBefore DateTime.now.minusDays(2))
|
||||
|
||||
def isGrantedMod(categSlug: String)(implicit ctx: UserContext): Fu[Boolean] =
|
||||
categSlug match {
|
||||
|
|
|
@ -13,10 +13,10 @@ private[forum] final class Recent(
|
|||
nb: Int,
|
||||
publicCategIds: List[String]) {
|
||||
|
||||
private type GetTeams = String => Set[String]
|
||||
private type GetTeamIds = String => Fu[Set[String]]
|
||||
|
||||
def apply(user: Option[User], getTeams: GetTeams): Fu[List[MiniForumPost]] =
|
||||
userCacheKey(user, getTeams) |> { key => cache(key)(fetch(key)) }
|
||||
def apply(user: Option[User], getTeams: GetTeamIds): Fu[List[MiniForumPost]] =
|
||||
userCacheKey(user, getTeams) flatMap { key => cache(key)(fetch(key)) }
|
||||
|
||||
def team(teamId: String): Fu[List[MiniForumPost]] = {
|
||||
// prepend empty language list
|
||||
|
@ -26,12 +26,14 @@ private[forum] final class Recent(
|
|||
|
||||
def invalidate: Funit = fuccess(cache.clear)
|
||||
|
||||
private def userCacheKey(user: Option[User], getTeams: GetTeams): String =
|
||||
user.fold("en")(_.langs.mkString(",")) :: {
|
||||
(user.??(_.troll) ?? List("[troll]")) :::
|
||||
(user ?? MasterGranter(Permission.StaffForum)).fold(staffCategIds, publicCategIds) :::
|
||||
((user.map(_.id) ?? getTeams) map teamSlug).toList
|
||||
} mkString ";"
|
||||
private def userCacheKey(user: Option[User], getTeams: GetTeamIds): Fu[String] =
|
||||
(user.map(_.id) ?? getTeams).map { teamIds =>
|
||||
user.fold("en")(_.langs.mkString(",")) :: {
|
||||
(user.??(_.troll) ?? List("[troll]")) :::
|
||||
(user ?? MasterGranter(Permission.StaffForum)).fold(staffCategIds, publicCategIds) :::
|
||||
(teamIds map teamSlug).toList
|
||||
} mkString ";"
|
||||
}
|
||||
|
||||
private lazy val staffCategIds = "staff" :: publicCategIds
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ package lila
|
|||
|
||||
package object forum extends PackageObject with WithPlay {
|
||||
|
||||
private[forum] def teamSlug(id: String) = "team-" + id
|
||||
private[forum] def teamSlug(id: String) = s"team-$id"
|
||||
|
||||
private[forum] val logger = lila.log("forum")
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ private[team] final class Cached(implicit system: akka.actor.ActorSystem) {
|
|||
|
||||
def name(id: String) = nameCache sync id
|
||||
|
||||
private[team] val teamIdsCache = new Syncache[String, Set[String]](
|
||||
private val teamIdsCache = new Syncache[String, Set[String]](
|
||||
name = "team.ids",
|
||||
compute = MemberRepo.teamIdsByUser,
|
||||
default = _ => Set.empty,
|
||||
|
@ -23,7 +23,10 @@ private[team] final class Cached(implicit system: akka.actor.ActorSystem) {
|
|||
timeToLive = 2 hours,
|
||||
logger = logger)
|
||||
|
||||
def teamIds(userId: String) = teamIdsCache sync userId
|
||||
def syncTeamIds = teamIdsCache sync _
|
||||
def teamIds = teamIdsCache async _
|
||||
|
||||
def invalidateTeamIds = teamIdsCache invalidate _
|
||||
|
||||
val nbRequests = AsyncCache[String, Int](
|
||||
name = "team.nbRequests",
|
||||
|
|
|
@ -35,7 +35,7 @@ final class TeamApi(
|
|||
createdBy = me)
|
||||
coll.team.insert(team) >>
|
||||
MemberRepo.add(team.id, me.id) >>- {
|
||||
(cached.teamIdsCache invalidate me.id)
|
||||
(cached invalidateTeamIds me.id)
|
||||
(forum ! MakeTeam(team.id, team.name))
|
||||
(indexer ! InsertTeam(team))
|
||||
(timeline ! Propagate(
|
||||
|
@ -49,13 +49,14 @@ final class TeamApi(
|
|||
location = e.location,
|
||||
description = e.description,
|
||||
open = e.isOpen) |> { team =>
|
||||
coll.team.update($id(team.id), team).void >>- (indexer ! InsertTeam(team))
|
||||
}
|
||||
coll.team.update($id(team.id), team).void >>- (indexer ! InsertTeam(team))
|
||||
}
|
||||
}
|
||||
|
||||
def mine(me: User): Fu[List[Team]] = coll.team.byIds[Team](cached teamIds me.id)
|
||||
def mine(me: User): Fu[List[Team]] =
|
||||
cached teamIds me.id flatMap coll.team.byIds[Team]
|
||||
|
||||
def hasTeams(me: User): Boolean = cached.teamIds(me.id).nonEmpty
|
||||
def hasTeams(me: User): Fu[Boolean] = cached.teamIds(me.id).map(_.nonEmpty)
|
||||
|
||||
def hasCreatedRecently(me: User): Fu[Boolean] =
|
||||
TeamRepo.userHasCreatedSince(me.id, creationPeriod)
|
||||
|
@ -90,11 +91,10 @@ final class TeamApi(
|
|||
able ← teamOption.??(requestable(_, user))
|
||||
} yield teamOption filter (_ => able)
|
||||
|
||||
def requestable(team: Team, user: User): Fu[Boolean] =
|
||||
RequestRepo.exists(team.id, user.id).map { _ -> belongsTo(team.id, user.id) } map {
|
||||
case (false, false) => true
|
||||
case _ => false
|
||||
}
|
||||
def requestable(team: Team, user: User): Fu[Boolean] = for {
|
||||
belongs <- belongsTo(team.id, user.id)
|
||||
requested <- RequestRepo.exists(team.id, user.id)
|
||||
} yield !belongs && !requested
|
||||
|
||||
def createRequest(team: Team, setup: RequestSetup, user: User): Funit =
|
||||
requestable(team, user) flatMap {
|
||||
|
@ -113,13 +113,15 @@ final class TeamApi(
|
|||
)
|
||||
} yield ()
|
||||
|
||||
def doJoin(team: Team, userId: String): Funit = (!belongsTo(team.id, userId)) ?? {
|
||||
MemberRepo.add(team.id, userId) >>
|
||||
TeamRepo.incMembers(team.id, +1) >>- {
|
||||
cached.teamIdsCache invalidate userId
|
||||
timeline ! Propagate(TeamJoin(userId, team.id)).toFollowersOf(userId)
|
||||
}
|
||||
} recover lila.db.recoverDuplicateKey(e => ())
|
||||
def doJoin(team: Team, userId: String): Funit = !belongsTo(team.id, userId) flatMap {
|
||||
_ ?? {
|
||||
MemberRepo.add(team.id, userId) >>
|
||||
TeamRepo.incMembers(team.id, +1) >>- {
|
||||
cached invalidateTeamIds userId
|
||||
timeline ! Propagate(TeamJoin(userId, team.id)).toFollowersOf(userId)
|
||||
}
|
||||
} recover lila.db.recoverDuplicateKey(e => ())
|
||||
}
|
||||
|
||||
def quit(teamId: String)(implicit ctx: UserContext): Fu[Option[Team]] = for {
|
||||
teamOption ← coll.team.byId[Team](teamId)
|
||||
|
@ -128,10 +130,12 @@ final class TeamApi(
|
|||
})
|
||||
} yield result
|
||||
|
||||
def doQuit(team: Team, userId: String): Funit = belongsTo(team.id, userId) ?? {
|
||||
MemberRepo.remove(team.id, userId) >>
|
||||
TeamRepo.incMembers(team.id, -1) >>-
|
||||
(cached.teamIdsCache invalidate userId)
|
||||
def doQuit(team: Team, userId: String): Funit = belongsTo(team.id, userId) flatMap {
|
||||
_ ?? {
|
||||
MemberRepo.remove(team.id, userId) >>
|
||||
TeamRepo.incMembers(team.id, -1) >>-
|
||||
(cached invalidateTeamIds userId)
|
||||
}
|
||||
}
|
||||
|
||||
def quitAll(userId: String): Funit = MemberRepo.removeByUser(userId)
|
||||
|
@ -150,8 +154,11 @@ final class TeamApi(
|
|||
MemberRepo.removeByteam(team.id) >>-
|
||||
(indexer ! RemoveTeam(team.id))
|
||||
|
||||
def belongsTo(teamId: String, userId: String): Boolean =
|
||||
cached.teamIds(userId) contains teamId
|
||||
def syncBelongsTo(teamId: String, userId: String): Boolean =
|
||||
cached.syncTeamIds(userId) contains teamId
|
||||
|
||||
def belongsTo(teamId: String, userId: String): Fu[Boolean] =
|
||||
cached.teamIds(userId) map (_ contains teamId)
|
||||
|
||||
def owns(teamId: String, userId: String): Fu[Boolean] =
|
||||
TeamRepo ownerOf teamId map (Some(userId) ==)
|
||||
|
|
|
@ -293,6 +293,9 @@ div.user_show .teams a {
|
|||
display: block;
|
||||
margin: 5px 0;
|
||||
}
|
||||
div.user_show .teams a.mine {
|
||||
color: #d59120;
|
||||
}
|
||||
div.user_show div.games {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue