diff --git a/app/controllers/Learn.scala b/app/controllers/Learn.scala index 2cc1ef1993..b0b1108efe 100644 --- a/app/controllers/Learn.scala +++ b/app/controllers/Learn.scala @@ -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)) } ) } diff --git a/modules/activity/src/main/Activity.scala b/modules/activity/src/main/Activity.scala index c30a332207..4fead811d0 100644 --- a/modules/activity/src/main/Activity.scala +++ b/modules/activity/src/main/Activity.scala @@ -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 ) } diff --git a/modules/activity/src/main/ActivityAggregation.scala b/modules/activity/src/main/ActivityAggregation.scala index 77eb81b683..db374d88bb 100644 --- a/modules/activity/src/main/ActivityAggregation.scala +++ b/modules/activity/src/main/ActivityAggregation.scala @@ -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 diff --git a/modules/activity/src/main/ActivityApi.scala b/modules/activity/src/main/ActivityApi.scala index 5787c0502f..02d9fb307e 100644 --- a/modules/activity/src/main/ActivityApi.scala +++ b/modules/activity/src/main/ActivityApi.scala @@ -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 = diff --git a/modules/activity/src/main/BSONHandlers.scala b/modules/activity/src/main/BSONHandlers.scala index e4d1e97ff3..633f05e7e8 100644 --- a/modules/activity/src/main/BSONHandlers.scala +++ b/modules/activity/src/main/BSONHandlers.scala @@ -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) ) } } diff --git a/modules/activity/src/main/activities.scala b/modules/activity/src/main/activities.scala new file mode 100644 index 0000000000..a9a9431c99 --- /dev/null +++ b/modules/activity/src/main/activities.scala @@ -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)) +} diff --git a/modules/activity/src/main/model.scala b/modules/activity/src/main/model.scala new file mode 100644 index 0000000000..696582dfff --- /dev/null +++ b/modules/activity/src/main/model.scala @@ -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 +}