activity: aggregate games, computer analysis, and forum posts

This commit is contained in:
Thibault Duplessis 2017-07-18 14:32:11 +02:00
parent df5396d2f8
commit f2402e4693
9 changed files with 87 additions and 35 deletions

View file

@ -7,14 +7,13 @@ import lila.rating.PerfType
import lila.user.User
case class Activity(
_id: Activity.Id,
id: Activity.Id,
games: Activity.Games,
tours: Activity.Tours,
comps: Activity.CompAnalysis,
posts: Activity.Posts
) {
def id = _id
def userId = id.userId
def day = id.day
}
@ -45,11 +44,11 @@ object Activity {
case class Score(win: Int, loss: Int, draw: Int, rd: RatingDiff) {
def add(s: Score) = copy(win = win + s.win, loss = loss + s.loss, draw = draw + s.draw, rd = rd + s.rd)
}
case class RatingDiff(by100: Int) extends AnyVal {
def real = by100 / 100d
def +(r: RatingDiff) = RatingDiff(by100 + r.by100)
case class RatingDiff(value: Int) extends AnyVal {
def +(r: RatingDiff) = RatingDiff(value + r.value)
}
implicit val ScoreZero = Zero.instance(Score(0, 0, 0, RatingDiff(0)))
implicit val RatingDiffZero = Zero.instance(RatingDiff(0))
implicit val ScoreZero = Zero.instance(Score(0, 0, 0, RatingDiffZero.zero))
case class Tours(value: Map[Tours.TourId, Tours.Result]) extends AnyVal
object Tours {
@ -58,21 +57,26 @@ object Activity {
}
implicit val ToursZero = Zero.instance(Tours(Map.empty))
case class Posts(posts: Map[Posts.ThreadId, List[Posts.PostId]]) {
case class Posts(posts: Map[Posts.TopicId, List[Posts.PostId]]) {
def total = posts.foldLeft(0)(_ + _._2.size)
def +(postId: Posts.PostId, topicId: Posts.TopicId) = Posts {
posts + (topicId -> (postId :: ~posts.get(topicId)))
}
}
object Posts {
case class ThreadId(value: String) extends AnyVal
case class TopicId(value: String) extends AnyVal
case class PostId(value: String) extends AnyVal
}
implicit val PostsZero = Zero.instance(Posts(Map.empty))
case class CompAnalysis(gameIds: List[GameId]) extends AnyVal
case class CompAnalysis(gameIds: List[GameId]) extends AnyVal {
def +(gameId: GameId) = CompAnalysis(gameId :: gameIds)
}
case class GameId(value: String) extends AnyVal
implicit val CompsZero = Zero.instance(CompAnalysis(Nil))
def make(userId: User.ID) = Activity(
_id = Id today userId,
id = Id today userId,
games = GamesZero.zero,
tours = ToursZero.zero,
posts = PostsZero.zero,

View file

@ -1,22 +1,29 @@
package lila.activity
import lila.analyse.Analysis
import lila.game.Game
private object ActivityAggregation {
import Activity._
def addGame(ua: Activity.WithUserId, game: Game): Option[Activity] = for {
def addGame(game: Game, userId: String)(a: Activity): Option[Activity] = for {
pt <- game.perfType
player <- game playerByUserId ua.userId
won = game.winnerUserId map (ua.userId ==)
player <- game playerByUserId userId
won = game.winnerColor map (player.color ==)
score = Score(
win = won.has(true) ?? 1,
loss = won.has(false) ?? 1,
draw = won.isEmpty ?? 1,
rd = RatingDiff(~player.ratingDiff * 100)
rd = RatingDiff(~player.ratingDiff)
)
} yield ua.activity.copy(
games = ua.activity.games.add(pt, score)
)
} yield a.copy(games = a.games.add(pt, score))
def addAnalysis(analysis: Analysis)(a: Activity): Option[Activity] =
a.copy(comps = a.comps + GameId(analysis.id)).some
def addForumPost(post: lila.forum.Post, topic: lila.forum.Topic)(a: Activity) =
post.userId map { userId =>
a.copy(posts = a.posts.+(Posts.PostId(post.id), Posts.TopicId(topic.id)))
}
}

View file

@ -1,21 +1,34 @@
package lila.activity
import lila.analyse.Analysis
import lila.db.dsl._
import lila.game.Game
import lila.user.User
import lila.user.UserRepo.lichessId
final class ActivityApi(coll: Coll) {
import Activity._
import BSONHandlers._
def get(userId: User.ID) = coll.byId[Activity, Id](Id today userId)
def addGame(game: Game): Funit = game.userIds.map { userId =>
getOrCreate(userId) flatMap { old =>
ActivityAggregation.addGame(Activity.WithUserId(old, userId), game) ?? save
}
update(userId) { ActivityAggregation.addGame(game, userId) _ }
}.sequenceFu.void
def get(userId: User.ID) = coll.byId[Activity, Id](Id today userId)
def getOrCreate(userId: User.ID) = get(userId) map { _ | Activity.make(userId) }
def save(activity: Activity) = coll.update($id(activity.id), activity, upsert = true).void
def addAnalysis(analysis: Analysis): Funit = analysis.uid.filter(lichessId !=) ?? { userId =>
update(userId) { ActivityAggregation.addAnalysis(analysis) _ }
}
def addForumPost(post: lila.forum.Post, topic: lila.forum.Topic): Funit = post.userId.filter(lichessId !=) ?? { userId =>
update(userId) { ActivityAggregation.addForumPost(post, topic) _ }
}
private def getOrCreate(userId: User.ID) = get(userId) map { _ | Activity.make(userId) }
private def save(activity: Activity) = coll.update($id(activity.id), activity, upsert = true).void
private def update(userId: User.ID)(f: Activity => Option[Activity]): Funit =
getOrCreate(userId) flatMap { old =>
f(old) ?? save
}
}

View file

@ -23,8 +23,28 @@ private object BSONHandlers {
def write(id: Id) = BSONString(s"${id.userId}$sep${id.day.value}")
}
private implicit val ratingDiffHandler = intAnyValHandler[RatingDiff](_.by100, RatingDiff.apply)
private implicit val scoreHandler = Macros.handler[Score]
private implicit val ratingDiffHandler = intAnyValHandler[RatingDiff](_.value, RatingDiff.apply)
implicit val scoreHandler = new lila.db.BSON[Score] {
private val win = "w"
private val loss = "l"
private val draw = "d"
private val rd = "r"
def reads(r: lila.db.BSON.Reader) = Score(
win = r.intD(win),
loss = r.intD(loss),
draw = r.intD(draw),
rd = r.getD[RatingDiff](rd)
)
def writes(w: lila.db.BSON.Writer, o: Score) = BSONDocument(
win -> w.intO(o.win),
loss -> w.intO(o.loss),
draw -> w.intO(o.draw),
rd -> w.zero(o.rd)
)
}
private implicit val gamesMapHandler = MapDocument.MapHandler[PerfType, Score]
private implicit val gamesHandler = isoHandler[Games, Map[PerfType, Score], Bdoc]((g: Games) => g.value, Games.apply _)
@ -41,11 +61,11 @@ private object BSONHandlers {
private implicit val gameIdsHandler = bsonArrayToListHandler[GameId]
private implicit val compAnalysisHandler = isoHandler[CompAnalysis, List[GameId], Barr]((c: CompAnalysis) => c.gameIds, CompAnalysis.apply _)
private implicit val threadIdIso = Iso.string[Posts.ThreadId](Posts.ThreadId.apply, _.value)
private implicit val topicIdIso = Iso.string[Posts.TopicId](Posts.TopicId.apply, _.value)
private implicit val postIdHandler = stringAnyValHandler[Posts.PostId](_.value, Posts.PostId.apply)
private implicit val postIdsHandler = bsonArrayToListHandler[Posts.PostId]
private implicit val postsMapHandler = MapValue.MapHandler[Posts.ThreadId, List[Posts.PostId]]
private implicit val postsHandler = isoHandler[Posts, Map[Posts.ThreadId, List[Posts.PostId]], Bdoc]((p: Posts) => p.posts, Posts.apply _)
private implicit val postsMapHandler = MapValue.MapHandler[Posts.TopicId, List[Posts.PostId]]
private implicit val postsHandler = isoHandler[Posts, Map[Posts.TopicId, List[Posts.PostId]], Bdoc]((p: Posts) => p.posts, Posts.apply _)
implicit val activityHandler = new lila.db.BSON[Activity] {
@ -56,7 +76,7 @@ private object BSONHandlers {
private val posts = "p"
def reads(r: lila.db.BSON.Reader) = Activity(
_id = r.get[Id](id),
id = r.get[Id](id),
games = r.getD[Games](games),
tours = r.getD[Tours](tours),
comps = r.getD[CompAnalysis](comps),

View file

@ -19,8 +19,10 @@ final class Env(
system.lilaBus.subscribe(system.actorOf(Props(new Actor {
def receive = {
case lila.game.actorApi.FinishGame(game, _, _) if !game.aborted => api addGame game
case lila.analyse.actorApi.AnalysisReady(_, analysis) => api addAnalysis analysis
case lila.forum.actorApi.CreatePost(post, topic) => api.addForumPost(post, topic)
}
})), 'finishGame)
})), 'finishGame, 'analysisReady, 'forumPost)
}
object Env {

View file

@ -60,7 +60,8 @@ final class Env(
shutup = shutup,
timeline = hub.actor.timeline,
detectLanguage = detectLanguage,
mentionNotifier = mentionNotifier
mentionNotifier = mentionNotifier,
bus = system.lilaBus
)
lazy val forms = new DataForm(hub.actor.captcher)

View file

@ -2,7 +2,6 @@ package lila.forum
import actorApi._
import akka.actor.ActorSelection
import org.joda.time.DateTime
import lila.common.paginator._
import lila.db.dsl._
import lila.db.paginator._
@ -10,6 +9,7 @@ import lila.hub.actorApi.timeline.{ ForumPost, Propagate }
import lila.mod.ModlogApi
import lila.security.{ Granter => MasterGranter }
import lila.user.{ User, UserContext }
import org.joda.time.DateTime
final class PostApi(
env: Env,
@ -19,7 +19,8 @@ final class PostApi(
shutup: ActorSelection,
timeline: ActorSelection,
detectLanguage: lila.common.DetectLanguage,
mentionNotifier: MentionNotifier
mentionNotifier: MentionNotifier,
bus: lila.common.Bus
) {
import BSONHandlers._
@ -68,7 +69,9 @@ final class PostApi(
))
}
lila.mon.forum.post.create()
} >>- mentionNotifier.notifyMentionedUsers(post, topic) inject post
mentionNotifier.notifyMentionedUsers(post, topic)
bus.publish(actorApi.CreatePost(post, topic), 'forumPost)
} inject post
}
}

View file

@ -4,3 +4,5 @@ package actorApi
case class InsertPost(post: Post)
case class RemovePost(id: String)
case class RemovePosts(ids: List[String])
case class CreatePost(post: Post, topic: Topic)

View file

@ -193,7 +193,7 @@ object ApplicationBuild extends Build {
libraryDependencies ++= provided(play.api, reactivemongo.driver)
)
lazy val activity = project("activity", Seq(common, game, user)).settings(
lazy val activity = project("activity", Seq(common, game, analyse, user, forum, study, pool, puzzle)).settings(
libraryDependencies ++= provided(play.api, reactivemongo.driver)
)