refactor activities and aggregate learn stages

pull/3315/head
Thibault Duplessis 2017-07-18 20:13:50 +02:00
parent 72a582b221
commit 7e14de5f7b
7 changed files with 113 additions and 71 deletions

View File

@ -33,7 +33,8 @@ object Learn extends LilaController {
err => BadRequest.fuccess, {
case (stage, level, s) =>
val score = lila.learn.StageProgress.Score(s)
env.api.setScore(me, stage, level, score) inject Ok(Json.obj("ok" -> true))
env.api.setScore(me, stage, level, score) >>
Env.activity.api.addLearn(me.id, stage) inject Ok(Json.obj("ok" -> true))
}
)
}

View File

@ -1,17 +1,18 @@
package lila.activity
import org.joda.time.{ DateTime, Days }
import ornicar.scalalib.Zero
import lila.rating.PerfType
import lila.user.User
import activities._
case class Activity(
id: Activity.Id,
games: Activity.Games,
comps: Activity.CompAnalysis,
posts: Activity.Posts,
puzzles: Activity.Puzzles
games: Games,
comps: CompAnalysis,
posts: Posts,
puzzles: Puzzles,
learn: Learn
) {
def userId = id.userId
@ -34,71 +35,12 @@ object Activity {
def today = Day(Days.daysBetween(genesis, DateTime.now.withTimeAtStartOfDay).getDays)
}
case class Rating(value: Int) extends AnyVal
case class RatingProg(before: Rating, after: Rating) {
def +(o: RatingProg) = copy(after = o.after)
}
object RatingProg {
def +(rp1O: Option[RatingProg], rp2O: Option[RatingProg]) = (rp1O, rp2O) match {
case (Some(rp1), Some(rp2)) => Some(rp1 + rp2)
case _ => rp2O orElse rp1O
}
}
case class Games(value: Map[PerfType, Score]) extends AnyVal {
def add(pt: PerfType, score: Score) = copy(
value = value + (pt -> value.get(pt).fold(score)(_ + score))
)
}
implicit val GamesZero = Zero.instance(Games(Map.empty))
case class Score(win: Int, loss: Int, draw: Int, rp: Option[RatingProg]) {
def +(s: Score) = copy(
win = win + s.win,
loss = loss + s.loss,
draw = draw + s.draw,
rp = RatingProg.+(rp, s.rp)
)
def size = win + loss + draw
}
object Score {
def make(res: Option[Boolean], rp: Option[RatingProg]) = Score(
win = res.has(true) ?? 1,
loss = res.has(false) ?? 1,
draw = res.isEmpty ?? 1,
rp = rp
)
}
implicit val ScoreZero = Zero.instance(Score(0, 0, 0, none))
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 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 {
def +(gameId: GameId) = CompAnalysis(gameId :: gameIds)
}
case class GameId(value: String) extends AnyVal
implicit val CompsZero = Zero.instance(CompAnalysis(Nil))
case class Puzzles(score: Score) extends AnyVal {
def +(s: Score) = Puzzles(score + s)
}
implicit val PuzzlesZero = Zero.instance(Puzzles(ScoreZero.zero))
def make(userId: User.ID) = Activity(
id = Id today userId,
games = GamesZero.zero,
posts = PostsZero.zero,
comps = CompsZero.zero,
puzzles = PuzzlesZero.zero
puzzles = PuzzlesZero.zero,
learn = LearnZero.zero
)
}

View File

@ -5,7 +5,8 @@ import lila.game.Game
private object ActivityAggregation {
import Activity._
import activities._
import model._
def addGame(game: Game, userId: String)(a: Activity): Option[Activity] = for {
pt <- game.perfType

View File

@ -10,6 +10,7 @@ final class ActivityApi(coll: Coll) {
import Activity._
import BSONHandlers._
import activities._
def get(userId: User.ID) = coll.byId[Activity, Id](Id today userId)
@ -28,6 +29,9 @@ final class ActivityApi(coll: Coll) {
def addPuzzle(res: lila.puzzle.Puzzle.UserResult): Funit =
update(res.userId) { ActivityAggregation.addPuzzle(res) _ }
def addLearn(userId: User.ID, stage: String) =
update(userId) { a => a.copy(learn = a.learn + Learn.Stage(stage)).some }
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 =

View File

@ -11,6 +11,8 @@ import lila.rating.PerfType
private object BSONHandlers {
import Activity._
import activities._
import model._
implicit val activityIdHandler: BSONHandler[BSONString, Id] = new BSONHandler[BSONString, Id] {
private val sep = ':'
@ -66,6 +68,10 @@ private object BSONHandlers {
private implicit val puzzlesHandler = Macros.handler[Puzzles]
private implicit val learnStageIso = Iso.string[Learn.Stage](Learn.Stage.apply, _.value)
private implicit val learnMapHandler = MapValue.MapHandler[Learn.Stage, Int]
private implicit val learnHandler = isoHandler[Learn, Map[Learn.Stage, Int], Bdoc]((l: Learn) => l.value, Learn.apply _)
implicit val activityHandler = new lila.db.BSON[Activity] {
private val id = "_id"
@ -73,13 +79,15 @@ private object BSONHandlers {
private val comps = "c"
private val posts = "p"
private val puzzles = "z"
private val learn = "l"
def reads(r: lila.db.BSON.Reader) = Activity(
id = r.get[Id](id),
games = r.getD[Games](games),
comps = r.getD[CompAnalysis](comps),
posts = r.getD[Posts](posts),
puzzles = r.getD[Puzzles](puzzles)
puzzles = r.getD[Puzzles](puzzles),
learn = r.getD[Learn](learn)
)
def writes(w: lila.db.BSON.Writer, o: Activity) = BSONDocument(
@ -87,7 +95,8 @@ private object BSONHandlers {
games -> w.zero(o.games),
comps -> w.zero(o.comps),
posts -> w.zero(o.posts),
puzzles -> w.zero(o.puzzles)
puzzles -> w.zero(o.puzzles),
learn -> w.zero(o.learn)
)
}
}

View File

@ -0,0 +1,47 @@
package lila.activity
import ornicar.scalalib.Zero
import lila.rating.PerfType
import model._
object activities {
case class Games(value: Map[PerfType, Score]) extends AnyVal {
def add(pt: PerfType, score: Score) = copy(
value = value + (pt -> value.get(pt).fold(score)(_ + score))
)
}
implicit val GamesZero = Zero.instance(Games(Map.empty))
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 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 {
def +(gameId: GameId) = CompAnalysis(gameId :: gameIds)
}
implicit val CompsZero = Zero.instance(CompAnalysis(Nil))
case class Puzzles(score: Score) extends AnyVal {
def +(s: Score) = Puzzles(score + s)
}
implicit val PuzzlesZero = Zero.instance(Puzzles(ScoreZero.zero))
case class Learn(value: Map[Learn.Stage, Int]) {
def +(stage: Learn.Stage) = copy(
value = value + (stage -> value.get(stage).fold(1)(1 +))
)
}
object Learn {
case class Stage(value: String) extends AnyVal
}
implicit val LearnZero = Zero.instance(Learn(Map.empty))
}

View File

@ -0,0 +1,38 @@
package lila.activity
import ornicar.scalalib.Zero
object model {
case class Rating(value: Int) extends AnyVal
case class RatingProg(before: Rating, after: Rating) {
def +(o: RatingProg) = copy(after = o.after)
}
object RatingProg {
def +(rp1O: Option[RatingProg], rp2O: Option[RatingProg]) = (rp1O, rp2O) match {
case (Some(rp1), Some(rp2)) => Some(rp1 + rp2)
case _ => rp2O orElse rp1O
}
}
case class Score(win: Int, loss: Int, draw: Int, rp: Option[RatingProg]) {
def +(s: Score) = copy(
win = win + s.win,
loss = loss + s.loss,
draw = draw + s.draw,
rp = RatingProg.+(rp, s.rp)
)
def size = win + loss + draw
}
object Score {
def make(res: Option[Boolean], rp: Option[RatingProg]) = Score(
win = res.has(true) ?? 1,
loss = res.has(false) ?? 1,
draw = res.isEmpty ?? 1,
rp = rp
)
}
implicit val ScoreZero = Zero.instance(Score(0, 0, 0, none))
case class GameId(value: String) extends AnyVal
}