denormalize bookmark count in game doc and show popular games

pull/1/merge
Thibault Duplessis 2012-06-08 03:26:35 +02:00
parent 3f85628258
commit 97699a81a5
23 changed files with 134 additions and 60 deletions

View File

@ -46,10 +46,18 @@ object Game extends LilaController {
}
def star(page: Int) = Auth { implicit ctx
me =>
me
reasonable(page) {
IOk(makeListMenu map { menu
html.game.star(starApi.gamePaginatorByUser(me, page), menu)
})
}
}
def popular(page: Int) = Open { implicit ctx
reasonable(page) {
IOk(makeListMenu map { menu
html.game.star(starApi.gamePaginatorByUser(me, page), menu)
html.game.popular(paginator popular page, menu)
})
}
}

View File

@ -15,13 +15,13 @@ import scalaz.effects._
object Round extends LilaController {
private val gameRepo = env.game.gameRepo
private val socket = env.round.socket
private val hand = env.round.hand
private val messenger = env.round.messenger
private val rematcher = env.setup.rematcher
private val joiner = env.setup.friendJoiner
private val starApi = env.star.api
def gameRepo = env.game.gameRepo
def socket = env.round.socket
def hand = env.round.hand
def messenger = env.round.messenger
def rematcher = env.setup.rematcher
def joiner = env.setup.friendJoiner
def starApi = env.star.api
def websocketWatcher(gameId: String, color: String) = WebSocket.async[JsValue] { req
implicit val ctx = reqToCtx(req)
@ -53,21 +53,21 @@ object Round extends LilaController {
def watcher(gameId: String, color: String) = Open { implicit ctx
IOptionIOResult(gameRepo.pov(gameId, color)) { pov
pov.game.started.fold(
starApi usersByGame pov.game map { bookmarkers
Ok(html.round.watcher(pov, version(pov.gameId), bookmarkers))
},
join(pov))
pov.game.started.fold(watch(pov), join(pov))
}
}
private def watch(pov: Pov)(implicit ctx: Context): IO[Result] =
pov.game.hasBookmarks.fold(
starApi usersByGame pov.game,
io(Nil)
) map { bookmarkers
Ok(html.round.watcher(pov, version(pov.gameId), bookmarkers))
}
private def join(pov: Pov)(implicit ctx: Context): IO[Result] =
joiner(pov.game, ctx.me).fold(
err putFailures(err) flatMap { _
starApi usersByGame pov.game map { bookmarkers
Ok(html.round.watcher(pov, version(pov.gameId), bookmarkers))
}
},
err putFailures(err) flatMap (_ watch(pov)),
_ flatMap {
case (p, events) performEvents(p.gameId)(events) map { _
Redirect(routes.Round.player(p.fullId))

View File

@ -8,16 +8,11 @@ import scalaz.effects._
object Star extends LilaController {
val starRepo = env.star.starRepo
val api = env.star.api
val gameRepo = env.game.gameRepo
def toggle(gameId: String) = Auth { implicit ctx
me
IOk {
for {
exists gameRepo exists gameId
_ exists.fold(starRepo.toggle(gameId, me.id), io())
} yield ()
}
IOk(api.toggle(gameId, me))
}
}

View File

@ -19,17 +19,19 @@ object User extends LilaController {
def forms = user.DataForm
def eloUpdater = env.user.eloUpdater
def lobbyMessenger = env.lobby.messenger
def starApi = env.star.api
def show(username: String) = showFilter(username, "all", 1)
def showFilter(username: String, filterName: String, page: Int) = Open { implicit ctx
IOptionIOk(userRepo byId username) { u
u.enabled.fold(
env.user.userInfo(u, ctx) map { info
env.user.userInfo(u, starApi, ctx) map { info
val filters = user.GameFilterMenu(info, ctx.me, filterName)
html.user.show(u, info,
games = gamePaginator.recentlyCreated(filters.query)(page),
filters = filters)
val paginator = filters.query.fold(
query gamePaginator.recentlyCreated(query)(page),
starApi.gamePaginatorByUser(u, page))
html.user.show(u, info, paginator, filters)
},
io(html.user.disabled(u)))
}

View File

@ -23,12 +23,14 @@ final class Cached(
def nbGames: Int = memo(NbGames)
def nbMates: Int = memo(NbMates)
def nbPopular: Int = memo(NbPopular)
private val memo = ActorMemo(loadFromDb, nbTtl, 5.seconds)
private def loadFromDb(key: Key) = key match {
case NbGames gameRepo.count(_.all).unsafePerformIO
case NbMates gameRepo.count(_.mate).unsafePerformIO
case NbPopular gameRepo.count(_.popular).unsafePerformIO
}
}
@ -38,4 +40,5 @@ object Cached {
case object NbGames extends Key
case object NbMates extends Key
case object NbPopular extends Key
}

View File

@ -31,6 +31,7 @@ case class DbGame(
variant: Variant = Variant.default,
next: Option[String] = None,
lastMoveTime: Option[Int] = None,
bookmarks: Int = 0,
createdAt: Option[DateTime] = None,
updatedAt: Option[DateTime] = None) {
@ -325,6 +326,8 @@ case class DbGame(
!finishedOrAborted && updatedAt.fold(
_ > DateTime.now - 20.seconds, false)
def hasBookmarks = bookmarks > 0
def encode = RawDbGame(
id = id,
players = players map (_.encode),
@ -341,6 +344,7 @@ case class DbGame(
v = variant.id,
next = next,
lmt = lastMoveTime,
bm = bookmarks.some filter (0 <),
createdAt = createdAt,
updatedAt = updatedAt
)
@ -397,6 +401,7 @@ case class RawDbGame(
v: Int = 1,
next: Option[String] = None,
lmt: Option[Int] = None,
bm: Option[Int] = None,
createdAt: Option[DateTime],
updatedAt: Option[DateTime]) {
@ -425,6 +430,7 @@ case class RawDbGame(
variant = trueVariant,
next = next,
lastMoveTime = lmt,
bookmarks = bm | 0,
createdAt = createdAt,
updatedAt = updatedAt
)

View File

@ -76,6 +76,10 @@ class GameRepo(collection: MongoCollection)
update(idSelector(id), $set(pn + ".userId" -> user.id, pn + ".elo" -> user.elo))
}
def incBookmarks(id: String, value: Int) = io {
update(idSelector(id), $inc("bm" -> value))
}
def finish(id: String, winnerId: Option[String]) = io {
update(
idSelector(id),

View File

@ -8,6 +8,7 @@ import scalaz.effects._
case class ListMenu(
nbGames: Int,
nbMates: Int,
nbPopular: Int,
nbStars: Option[Int])
object ListMenu {
@ -22,6 +23,7 @@ object ListMenu {
new ListMenu(
nbGames = cached.nbGames,
nbMates = cached.nbMates,
nbPopular = cached.nbPopular,
nbStars = nbStars)
}
}

View File

@ -17,6 +17,9 @@ final class PaginatorBuilder(
def checkmate(page: Int): Paginator[DbGame] =
paginator(checkmateAdapter, page)
def popular(page: Int): Paginator[DbGame] =
paginator(popularAdapter, page)
def recentlyUpdated(query: DBObject) = apply(query, Query.sortUpdated) _
def recentlyCreated(query: DBObject) = apply(query, Query.sortCreated) _
@ -31,7 +34,10 @@ final class PaginatorBuilder(
adapter(DBObject(), Query.sortUpdated)
private val checkmateAdapter =
adapter(DBObject("status" -> Status.Mate.id), Query.sortUpdated)
adapter(Query.mate, Query.sortUpdated)
private val popularAdapter =
adapter(Query.popular, Query.sortPopular)
private def adapter(query: DBObject, sort: DBObject) = SalatAdapter(
dao = gameRepo,

View File

@ -31,6 +31,8 @@ object Query {
val notFinished: DBObject = DBObject("status" -> Status.Started.id)
val popular: DBObject = "bm" $gt 0
def clock(c: Boolean): DBObject = "clock.l" $exists c
def user(u: User): DBObject = DBObject("userIds" -> u.id)
@ -52,4 +54,6 @@ object Query {
val sortCreated = DBObject("createdAt" -> -1)
val sortUpdated = DBObject("updatedAt" -> -1)
val sortPopular = DBObject("bm" -> -1)
}

View File

@ -47,7 +47,6 @@ final class I18nKeys(translator: Translator) {
val noGameAvailableRightNowCreateOne = new Key("noGameAvailableRightNowCreateOne")
val whiteIsVictorious = new Key("whiteIsVictorious")
val blackIsVictorious = new Key("blackIsVictorious")
val playANewGame = new Key("playANewGame")
val rematch = new Key("rematch")
val playWithTheSameOpponentAgain = new Key("playWithTheSameOpponentAgain")
val newOpponent = new Key("newOpponent")
@ -87,6 +86,7 @@ final class I18nKeys(translator: Translator) {
val viewAllNbGames = new Key("viewAllNbGames")
val viewNbCheckmates = new Key("viewNbCheckmates")
val nbBookmarks = new Key("nbBookmarks")
val nbPopularGames = new Key("nbPopularGames")
val bookmarkedByNbPlayers = new Key("bookmarkedByNbPlayers")
val viewInFullSize = new Key("viewInFullSize")
val logOut = new Key("logOut")

View File

@ -1,16 +1,27 @@
package lila
package star
import game.DbGame
import game.{ DbGame, GameRepo }
import user.{ User, UserRepo }
import scalaz.effects._
final class StarApi(
starRepo: StarRepo,
gameRepo: GameRepo,
userRepo: UserRepo,
paginator: PaginatorBuilder) {
def toggle(gameId: String, user: User): IO[Unit] = for {
gameOption gameRepo game gameId
_ gameOption.fold(
game for {
bookmarked starRepo.toggle(game.id, user.id)
_ gameRepo.incBookmarks(game.id, bookmarked.fold(1, -1))
} yield (),
io())
} yield ()
def starred(game: DbGame, user: User): IO[Boolean] =
starRepo.exists(game.id, user.id)

View File

@ -25,6 +25,7 @@ final class StarEnv(
lazy val api = new StarApi(
starRepo = starRepo,
gameRepo = gameRepo,
userRepo = userRepo,
paginator = paginator)
}

View File

@ -13,12 +13,16 @@ import org.joda.time.DateTime
// db.star.ensureIndex({d: -1})
final class StarRepo(val collection: MongoCollection) {
def toggle(gameId: String, userId: String): IO[Unit] = io {
private[star] def toggle(gameId: String, userId: String): IO[Boolean] = io {
try {
add(gameId, userId, DateTime.now)
true
}
catch {
case e: DuplicateKey remove(gameId, userId)
case e: DuplicateKey {
remove(gameId, userId)
false
}
}
}
@ -31,7 +35,7 @@ final class StarRepo(val collection: MongoCollection) {
}
def userIdsByGameId(gameId: String): IO[List[String]] = io {
(collection find gameIdQuery(gameId) sort sortQuery() map { obj
(collection find gameIdQuery(gameId) sort sortQuery(1) map { obj
obj.getAs[String]("u")
}).toList.flatten
}

View File

@ -18,12 +18,13 @@ object GameFilter {
case object Loss extends GameFilter("loss")
case object Draw extends GameFilter("draw")
case object Playing extends GameFilter("playing")
case object Bookmark extends GameFilter("bookmark")
}
case class GameFilterMenu(
all: NonEmptyList[GameFilter],
current: GameFilter,
query: DBObject) {
query: Option[DBObject]) {
def list = all.list
}
@ -45,19 +46,21 @@ object GameFilterMenu extends NonEmptyLists {
(info.nbWin > 0) option Win,
(info.nbLoss > 0) option Loss,
(info.nbDraw > 0) option Draw,
(info.nbPlaying > 0) option Playing
(info.nbPlaying > 0) option Playing,
(info.nbBookmark > 0) option Bookmark
).flatten)
val current = (all.list find (_.name == currentName)) | all.head
val query = current match {
case All Query user user
case Me Query.opponents(user, me | user)
case Rated Query rated user
case Win Query win user
case Loss Query loss user
case Draw Query draw user
case Playing Query notFinished user
val query: Option[DBObject] = current match {
case All Some(Query user user)
case Me Some(Query.opponents(user, me | user))
case Rated Some(Query rated user)
case Win Some(Query win user)
case Loss Some(Query loss user)
case Draw Some(Query draw user)
case Playing Some(Query notFinished user)
case Bookmark None
}
new GameFilterMenu(all, current, query)

View File

@ -4,25 +4,27 @@ package user
import chess.{ EloCalculator, Color }
import game.{ GameRepo, DbGame }
import http.Context
import star.StarApi
import scalaz.effects._
case class UserInfo(
user: User,
rank: Option[(Int, Int)],
nbWin: Int,
nbDraw: Int,
nbLoss: Int,
nbPlaying: Int,
nbWithMe: Option[Int],
eloWithMe: Option[List[(String, Int)]],
eloChart: Option[EloChart],
winChart: Option[WinChart]) {
user: User,
rank: Option[(Int, Int)],
nbWin: Int,
nbDraw: Int,
nbLoss: Int,
nbPlaying: Int,
nbWithMe: Option[Int],
nbBookmark: Int,
eloWithMe: Option[List[(String, Int)]],
eloChart: Option[EloChart],
winChart: Option[WinChart]) {
def nbRated = user.nbRatedGames
def nbRated = user.nbRatedGames
def percentRated: Int = math.round(nbRated / user.nbGames.toFloat * 100)
}
def percentRated: Int = math.round(nbRated / user.nbGames.toFloat * 100)
}
object UserInfo {
@ -32,6 +34,7 @@ object UserInfo {
eloCalculator: EloCalculator,
eloChartBuilder: User IO[Option[EloChart]])(
user: User,
starApi: StarApi,
ctx: Context): IO[UserInfo] = for {
rank (user.elo >= 1500).fold(
userRepo rank user flatMap { rank
@ -51,6 +54,7 @@ object UserInfo {
me gameRepo count (_.opponents(user, me)) map (_.some),
io(none)
)
nbBookmark starApi countByUser user
eloChart eloChartBuilder(user)
winChart = (user.nbRatedGames > 0) option {
new WinChart(nbWin, nbDraw, nbLoss)
@ -69,6 +73,7 @@ object UserInfo {
nbLoss = nbLoss,
nbPlaying = nbPlaying | 0,
nbWithMe = nbWithMe,
nbBookmark = nbBookmark,
eloWithMe = eloWithMe,
eloChart = eloChart,
winChart = winChart)

View File

@ -0,0 +1,7 @@
@(paginator: Paginator[DbGame], listMenu: lila.game.ListMenu)(implicit ctx: Context)
@game.list(
name = trans.nbPopularGames.str(listMenu.nbPopular.localize),
paginator = paginator,
next = paginator.nextPage map { n => routes.Game.popular(n) },
menu = sideMenu(listMenu, "popular"))

View File

@ -13,4 +13,7 @@
<a class="@active.active("star")" href="@routes.Game.star()">
@trans.nbBookmarks(nb.localize)
</a>
<a class="@active.active("popular")" href="@routes.Game.popular()">
@trans.nbPopularGames(listMenu.nbPopular.localize)
</a>
}

View File

@ -22,5 +22,8 @@ case lila.user.GameFilter.Draw => {
case lila.user.GameFilter.Playing => {
@info.nbPlaying playing
}
case lila.user.GameFilter.Bookmark => {
@info.nbBookmark bookmarks
}
}

View File

@ -62,6 +62,7 @@ gamesBeingPlayedRightNow=Current Games
viewAllNbGames=%s Games
viewNbCheckmates=%s Checkmates
nbBookmarks=%s Bookmarks
nbPopularGames=%s Popular Games
bookmarkedByNbPlayers=Bookmarked by %s players
viewInFullSize=View in full size
logOut=Log out

View File

@ -7,6 +7,7 @@ GET /games/refresh/:ids controllers.Game.realtimeInner(ids: String)
GET /games/all controllers.Game.all(page: Int ?= 1)
GET /games/checkmate controllers.Game.checkmate(page: Int ?= 1)
GET /games/bookmark controllers.Game.star(page: Int ?= 1)
GET /games/popular controllers.Game.popular(page: Int ?= 1)
# Round
GET /$gameId<[\w\-]{8}> controllers.Round.watcher(gameId: String, color: String = "white")

View File

@ -212,7 +212,7 @@ div.lichess_goodies div.box .player {
}
div.bookmarkers {
margin-top: 1em;
margin-top: 2em;
}
div.bookmarkers li {
margin-top: 5px;

5
todo
View File

@ -24,6 +24,11 @@ star people and games (and forum threads?)
autoclose top menus
tournaments http://www.chess.com/tournaments/help.html
long name display issue http://en.lichess.org/forum/lichess-feedback/long-names-alter-layout
remove bookmark when removing game
fix game list translations
next deploy:
db.game2.ensureIndex({"bm": -1}, {sparse: true})
new translations:
-rematchOfferCanceled=Rematch offer canceled