diff --git a/app/controllers/User.scala b/app/controllers/User.scala index 878f275036..ac37961a1e 100644 --- a/app/controllers/User.scala +++ b/app/controllers/User.scala @@ -22,7 +22,7 @@ object User extends LilaController { def show(username: String) = showFilter(username, "all", 1) def showFilter(username: String, filterName: String, page: Int) = Open { implicit ctx ⇒ - IOptionIOk(userRepo byUsername username) { u ⇒ + IOptionIOk(userRepo byId username) { u ⇒ u.enabled.fold( env.user.userInfo(u, ctx) map { info ⇒ val filters = user.GameFilterMenu(info, ctx.me, filterName) @@ -79,7 +79,7 @@ object User extends LilaController { _ ⇒ IORedirect { for { - uOption ← userRepo byUsername username + uOption ← userRepo byId username _ ← uOption.filter(_.elo > UserModel.STARTING_ELO).fold( u ⇒ eloUpdater.adjust(u, UserModel.STARTING_ELO) flatMap { _ ⇒ userRepo setEngine u @@ -101,5 +101,5 @@ object User extends LilaController { def export(username: String) = TODO private val onlineUsers: IO[List[UserModel]] = - userRepo byUsernames env.user.usernameMemo.keys + userRepo byIds env.user.usernameMemo.keys } diff --git a/app/core/CoreEnv.scala b/app/core/CoreEnv.scala index 912e509165..2f32a550eb 100644 --- a/app/core/CoreEnv.scala +++ b/app/core/CoreEnv.scala @@ -2,7 +2,7 @@ package lila package core import com.mongodb.casbah.MongoConnection -import com.mongodb.{ DBRef, Mongo, MongoOptions, ServerAddress ⇒ MongoServer } +import com.mongodb.{ Mongo, MongoOptions, ServerAddress ⇒ MongoServer } import akka.actor._ @@ -26,8 +26,7 @@ final class CoreEnv private (application: Application, val settings: Settings) { lazy val user = new lila.user.UserEnv( settings = settings, mongodb = mongodb.apply _, - gameRepo = game.gameRepo, - dbRef = namespace ⇒ id ⇒ mongodb.ref(namespace, id)) + gameRepo = game.gameRepo) lazy val forum = new lila.forum.ForumEnv( settings = settings, @@ -57,14 +56,13 @@ final class CoreEnv private (application: Application, val settings: Settings) { userRepo = user.userRepo, timelinePush = timeline.push.apply, roundMessenger = round.messenger, - ai = ai.ai, - userDbRef = user.userRepo.dbRef) + ai = ai.ai) lazy val timeline = new lila.timeline.TimelineEnv( settings = settings, mongodb = mongodb.apply _, lobbyNotify = lobby.socket.addEntry, - getUsername = user.cached.username) + getUsername = user.cached.usernameOrAnonymous) lazy val ai = new lila.ai.AiEnv( settings = settings) diff --git a/app/forum/CategRepo.scala b/app/forum/CategRepo.scala index 2ce01133e7..e372ca66b8 100644 --- a/app/forum/CategRepo.scala +++ b/app/forum/CategRepo.scala @@ -3,7 +3,6 @@ package forum import com.novus.salat._ import com.novus.salat.dao._ -import com.mongodb.DBRef import com.mongodb.casbah.MongoCollection import com.mongodb.casbah.Imports._ import scalaz.effects._ diff --git a/app/forum/ForumHelper.scala b/app/forum/ForumHelper.scala index 4ece565bb8..dfd3147d76 100644 --- a/app/forum/ForumHelper.scala +++ b/app/forum/ForumHelper.scala @@ -8,10 +8,10 @@ import play.api.templates.Html trait ForumHelper { self: UserHelper ⇒ def authorName(post: Post) = - post.userIdString.fold(userIdToUsername, post.showAuthor) + post.userId.fold(userIdToUsername, post.showAuthor) def authorLink(post: Post, cssClass: Option[String] = None) = - post.userIdString.fold( + post.userId.fold( userId ⇒ userIdLink(userId.some, cssClass), Html("""%s""" .format(cssClass | "", authorName(post))) diff --git a/app/forum/Post.scala b/app/forum/Post.scala index de28e4034f..9fd6cc9f46 100644 --- a/app/forum/Post.scala +++ b/app/forum/Post.scala @@ -3,7 +3,6 @@ package forum import org.joda.time.DateTime import com.novus.salat.annotations.Key -import com.mongodb.casbah.Imports.ObjectId import ornicar.scalalib.OrnicarRandom import user.User @@ -12,14 +11,12 @@ case class Post( @Key("_id") id: String, topicId: String, author: Option[String], - userId: Option[ObjectId], + userId: Option[String], text: String, number: Int, createdAt: DateTime) { def showAuthor = (author map (_.trim) filter ("" !=)) | User.anonymous - - def userIdString: Option[String] = userId map (_.toString) } object Post { @@ -29,7 +26,7 @@ object Post { def apply( topicId: String, author: Option[String], - userId: Option[ObjectId], + userId: Option[String], text: String, number: Int): Post = Post( id = OrnicarRandom nextAsciiString idSize, diff --git a/app/forum/PostRepo.scala b/app/forum/PostRepo.scala index 4ac5c491db..30d9bac421 100644 --- a/app/forum/PostRepo.scala +++ b/app/forum/PostRepo.scala @@ -3,7 +3,6 @@ package forum import com.novus.salat._ import com.novus.salat.dao._ -import com.mongodb.DBRef import com.mongodb.casbah.MongoCollection import com.mongodb.casbah.Imports._ import scalaz.effects._ diff --git a/app/game/DbPlayer.scala b/app/game/DbPlayer.scala index 465c5beb39..c8e10c0ee0 100644 --- a/app/game/DbPlayer.scala +++ b/app/game/DbPlayer.scala @@ -4,8 +4,6 @@ package game import chess._ import user.User -import com.mongodb.DBRef - case class DbPlayer( id: String, color: Color, @@ -16,7 +14,7 @@ case class DbPlayer( isOfferingRematch: Boolean = false, lastDrawOffer: Option[Int] = None, isProposingTakeback: Boolean = false, - user: Option[DBRef] = None, + userId: Option[String] = None, elo: Option[Int] = None, eloDiff: Option[Int] = None, moveTimes: String = "", @@ -34,23 +32,17 @@ case class DbPlayer( ps = encodePieces(allPieces) ) - def withUser(user: User)(dbRef: User ⇒ DBRef): DbPlayer = copy( - user = dbRef(user).some, + def withUser(user: User): DbPlayer = copy( + userId = user.id.some, elo = user.elo.some) def isAi = aiLevel.isDefined def isHuman = !isAi - def userId: Option[String] = user map (_.getId.toString) + def hasUser = userId.isDefined - def hasUser = user.isDefined - - def isUser(u: User) = user.fold(_.getId == u.id, false) - - def withUser(u: User, ref: DBRef) = copy( - elo = u.elo.some, - user = ref.some) + def isUser(u: User) = userId.fold(_ == u.id, false) def wins = isWinner getOrElse false @@ -82,12 +74,12 @@ case class DbPlayer( aiLevel = aiLevel, w = isWinner, elo = elo, - eloDiff = eloDiff, + ed = eloDiff, isOfferingDraw = if (isOfferingDraw) Some(true) else None, isOfferingRematch = if (isOfferingRematch) Some(true) else None, lastDrawOffer = lastDrawOffer, isProposingTakeback = if (isProposingTakeback) Some(true) else None, - user = user, + userId = userId, mts = Some(moveTimes) filter ("" !=), blurs = Some(blurs) filter (0 !=) ) @@ -114,12 +106,12 @@ case class RawDbPlayer( aiLevel: Option[Int], w: Option[Boolean], elo: Option[Int], - eloDiff: Option[Int], + ed: Option[Int], isOfferingDraw: Option[Boolean], isOfferingRematch: Option[Boolean], lastDrawOffer: Option[Int], isProposingTakeback: Option[Boolean], - user: Option[DBRef], + userId: Option[String], mts: Option[String], blurs: Option[Int]) { @@ -132,12 +124,12 @@ case class RawDbPlayer( aiLevel = aiLevel, isWinner = w, elo = elo, - eloDiff = eloDiff, + eloDiff = ed, isOfferingDraw = isOfferingDraw | false, isOfferingRematch = isOfferingRematch | false, lastDrawOffer = lastDrawOffer, isProposingTakeback = isProposingTakeback | false, - user = user, + userId = userId, moveTimes = mts | "", blurs = blurs | 0 ) diff --git a/app/game/GameRepo.scala b/app/game/GameRepo.scala index 30ec56a3ee..a215a35491 100644 --- a/app/game/GameRepo.scala +++ b/app/game/GameRepo.scala @@ -71,16 +71,16 @@ class GameRepo(collection: MongoCollection) update(idSelector(id), $set("players.0.eloDiff" -> white, "players.1.eloDiff" -> black)) } - def setUser(id: String, color: Color, dbRef: DBRef, elo: Int) = io { + def setUser(id: String, color: Color, user: User) = io { val pn = "players.%d".format(color.fold(0, 1)) - update(idSelector(id), $set(pn + ".user" -> dbRef, pn + ".elo" -> elo)) + update(idSelector(id), $set(pn + ".userId" -> user.id, pn + ".elo" -> user.elo)) } def finish(id: String, winnerId: Option[String]) = io { update( idSelector(id), winnerId.fold(userId ⇒ - $set("positionHashes" -> "", "winnerUserId" -> userId), + $set("positionHashes" -> "", "winId" -> userId), $set("positionHashes" -> "")) ++ $unset( "players.0.previousMoveTs", @@ -157,22 +157,6 @@ class GameRepo(collection: MongoCollection) find("_id" $in ids).toList.map(_.decode).flatten sortBy (_.id) } - def ensureIndexes: IO[Unit] = io { - collection.underlying |> { coll ⇒ - coll.ensureIndex(DBObject("status" -> 1)) - coll.ensureIndex(DBObject("userIds" -> 1)) - coll.ensureIndex(DBObject("winnerUserId" -> 1)) - coll.ensureIndex(DBObject("turns" -> 1)) - coll.ensureIndex(DBObject("updatedAt" -> -1)) - coll.ensureIndex(DBObject("createdAt" -> -1)) - coll.ensureIndex(DBObject("createdAt" -> -1, "userIds" -> 1)) - } - } - - def dropIndexes: IO[Unit] = io { - collection.dropIndexes() - } - private def idSelector(game: DbGame): DBObject = idSelector(game.id) private def idSelector(id: String): DBObject = DBObject("_id" -> id) } diff --git a/app/game/Query.scala b/app/game/Query.scala index 755c24b964..154e8bd8fb 100644 --- a/app/game/Query.scala +++ b/app/game/Query.scala @@ -29,19 +29,19 @@ object Query { def clock(c: Boolean): DBObject = "clock.l" $exists c - def user(u: User): DBObject = DBObject("userIds" -> u.idString) + def user(u: User): DBObject = DBObject("userIds" -> u.id) def started(u: User): DBObject = user(u) ++ started def rated(u: User): DBObject = user(u) ++ rated - def win(u: User): DBObject = DBObject("winnerUserId" -> u.idString) + def win(u: User): DBObject = DBObject("winId" -> u.id) def draw(u: User): DBObject = user(u) ++ draw - def loss(u: User): DBObject = user(u) ++ finished ++ ("winnerUserId" $ne u.idString) + def loss(u: User): DBObject = user(u) ++ finished ++ ("winId" $ne u.id) def playing(u: User): DBObject = user(u) ++ playing - def opponents(u1: User, u2: User) = "userIds" $all List(u1.idString, u2.idString) + def opponents(u1: User, u2: User) = "userIds" $all List(u1.id, u2.id) } diff --git a/app/lobby/Hook.scala b/app/lobby/Hook.scala index a725b8985f..5813944cda 100644 --- a/app/lobby/Hook.scala +++ b/app/lobby/Hook.scala @@ -72,7 +72,7 @@ object Hook { increment = clock map (_.increment), mode = mode.id, color = color, - userId = user map (_.idString), + userId = user map (_.id), username = user.fold(_.username, User.anonymous), elo = user map (_.elo), eloRange = eloRange.toString, diff --git a/app/lobby/LobbyEnv.scala b/app/lobby/LobbyEnv.scala index f2432081a6..94c9508153 100644 --- a/app/lobby/LobbyEnv.scala +++ b/app/lobby/LobbyEnv.scala @@ -8,7 +8,6 @@ import play.api.i18n.Lang import play.api.i18n.MessagesPlugin import scalaz.effects._ import com.mongodb.casbah.MongoCollection -import com.mongodb.DBRef import user.{ User, UserRepo } import game.{ GameRepo, DbGame } diff --git a/app/lobby/Messenger.scala b/app/lobby/Messenger.scala index ae4a0c743f..bf30816ce1 100644 --- a/app/lobby/Messenger.scala +++ b/app/lobby/Messenger.scala @@ -13,7 +13,7 @@ final class Messenger( private val urlRegex = """lichess\.org/([\w-]{8})[\w-]{4}""".r def apply(text: String, username: String): IO[Valid[Message]] = for { - userOption ← userRepo byUsername username + userOption ← userRepo byId username message = for { user ← userOption toValid "Unknown user" msg ← createMessage(text, user) @@ -35,7 +35,7 @@ final class Messenger( } def ban(username: String): IO[Unit] = for { - userOption ← userRepo byUsername username + userOption ← userRepo byId username _ ← userOption.fold( user ⇒ for { _ ← userRepo toggleChatBan user diff --git a/app/message/DataForm.scala b/app/message/DataForm.scala index 57d392d209..0432466dcb 100644 --- a/app/message/DataForm.scala +++ b/app/message/DataForm.scala @@ -26,7 +26,7 @@ final class DataForm(userRepo: UserRepo) { )) private def fetchUser(username: String) = - (userRepo byUsername username).unsafePerformIO + (userRepo byId username).unsafePerformIO private def usernameExists(username: String) = fetchUser(username).isDefined diff --git a/app/message/Post.scala b/app/message/Post.scala index ec78f65ede..5e9169baad 100644 --- a/app/message/Post.scala +++ b/app/message/Post.scala @@ -2,7 +2,6 @@ package lila package message import org.joda.time.DateTime -import com.mongodb.casbah.Imports.ObjectId import ornicar.scalalib.OrnicarRandom case class Post( diff --git a/app/message/Thread.scala b/app/message/Thread.scala index f595d8a94f..8508cadef3 100644 --- a/app/message/Thread.scala +++ b/app/message/Thread.scala @@ -5,7 +5,6 @@ import user.User import org.joda.time.DateTime import com.novus.salat.annotations.Key -import com.mongodb.casbah.Imports.ObjectId import ornicar.scalalib.OrnicarRandom case class Thread( @@ -14,9 +13,9 @@ case class Thread( createdAt: DateTime, updatedAt: DateTime, posts: List[Post], - creatorId: ObjectId, - invitedId: ObjectId, - visibleByUserIds: List[ObjectId]) { + creatorId: String, + invitedId: String, + visibleByUserIds: List[String]) { def +(post: Post) = copy( posts = posts :+ post, diff --git a/app/message/ThreadRepo.scala b/app/message/ThreadRepo.scala index 7cca45b9c2..6f402b47b9 100644 --- a/app/message/ThreadRepo.scala +++ b/app/message/ThreadRepo.scala @@ -26,19 +26,19 @@ final class ThreadRepo( def userNbUnread(user: User): IO[Int] = userNbUnread(user.id) - def userNbUnread(userId: ObjectId): IO[Int] = io { + def userNbUnread(userId: String): IO[Int] = io { val result = collection.mapReduce( mapFunction = """function() { var thread = this, nb = 0; thread.posts.forEach(function(p) { if (!p.isRead) { - if (thread.creatorId.equals(ObjectId("%s"))) { + if (thread.creatorId == "%s") { if (!p.isByCreator) nb++; } else if (p.isByCreator) nb++; } }); if (nb > 0) emit("n", nb); -}""" format userId.toString, +}""" format userId, reduceFunction = """function(key, values) { var sum = 0; for(var i in values) { sum += values[i]; } @@ -81,7 +81,7 @@ final class ThreadRepo( def visibleByUserQuery(user: User): DBObject = visibleByUserQuery(user.id) - def visibleByUserQuery(userId: ObjectId): DBObject = + def visibleByUserQuery(userId: String): DBObject = DBObject("visibleByUserIds" -> userId) def selectId(id: String) = DBObject("_id" -> id) diff --git a/app/message/UnreadCache.scala b/app/message/UnreadCache.scala index 6b94b8fb65..7e78d02630 100644 --- a/app/message/UnreadCache.scala +++ b/app/message/UnreadCache.scala @@ -12,12 +12,12 @@ final class UnreadCache(threadRepo: ThreadRepo) { def get(user: User): Int = get(user.id) - def get(userId: ObjectId): Int = + def get(username: String): Int = cache.getOrElseUpdate(username.toLowerCase, { - (threadRepo userNbUnread user).unsafePerformIO + (threadRepo userNbUnread username).unsafePerformIO }) def invalidate(user: User) { - cache -= user.usernameCanonical + cache -= user.id } } diff --git a/app/mongodb/MongoDbEnv.scala b/app/mongodb/MongoDbEnv.scala index c74ff9e296..4c948097d9 100644 --- a/app/mongodb/MongoDbEnv.scala +++ b/app/mongodb/MongoDbEnv.scala @@ -2,8 +2,7 @@ package lila package mongodb import com.mongodb.casbah.MongoConnection -import com.mongodb.casbah.Imports.ObjectId -import com.mongodb.{ DBRef, Mongo, MongoOptions, ServerAddress ⇒ MongoServer } +import com.mongodb.{ Mongo, MongoOptions, ServerAddress ⇒ MongoServer } import core.Settings @@ -14,9 +13,6 @@ final class MongoDbEnv( def apply(coll: String) = connection(coll) - def ref(namespace: String, id: ObjectId): DBRef = - new DBRef(connection.underlying, namespace, id) - lazy val cache = new Cache(connection(MongoCollectionCache)) lazy val connection = MongoConnection(server, options)(MongoDbName) diff --git a/app/security/AuthImpl.scala b/app/security/AuthImpl.scala index 3db7ca6bf6..1f4d08a4e1 100644 --- a/app/security/AuthImpl.scala +++ b/app/security/AuthImpl.scala @@ -85,6 +85,6 @@ trait AuthImpl { def restoreUser(req: RequestHeader): Option[User] = for { sessionId ← req.session.get("sessionId") username ← env.securityStore.getUsername(sessionId) - user ← (env.user.userRepo byUsername username).unsafePerformIO + user ← (env.user.userRepo byId username).unsafePerformIO } yield user } diff --git a/app/setup/FriendJoiner.scala b/app/setup/FriendJoiner.scala index db8e2a3580..83e6a68d00 100644 --- a/app/setup/FriendJoiner.scala +++ b/app/setup/FriendJoiner.scala @@ -7,22 +7,20 @@ import user.User import round.{ Event, Progress, Messenger } import controllers.routes -import com.mongodb.DBRef import scalaz.effects._ final class FriendJoiner( gameRepo: GameRepo, messenger: Messenger, - timelinePush: DbGame ⇒ IO[Unit], - userDbRef: User ⇒ DBRef) { + timelinePush: DbGame ⇒ IO[Unit]) { def apply(game: DbGame, user: Option[User]): Valid[IO[(Pov, List[Event])]] = game.notStarted option { val color = game.invitedColor for { p1 ← user.fold( - u ⇒ gameRepo.setUser(game.id, color, userDbRef(u), u.elo) map { _ ⇒ - Progress(game, game.updatePlayer(color, _.withUser(u, userDbRef(u)))) + u ⇒ gameRepo.setUser(game.id, color, u) map { _ ⇒ + Progress(game, game.updatePlayer(color, _ withUser u)) }, io(Progress(game))) p2 = p1 withGame game.start diff --git a/app/setup/HookJoiner.scala b/app/setup/HookJoiner.scala index db51287b4c..1571a54967 100644 --- a/app/setup/HookJoiner.scala +++ b/app/setup/HookJoiner.scala @@ -8,14 +8,12 @@ import game.{ GameRepo, DbGame, DbPlayer, Pov } import round.{ Messenger, Progress } import scalaz.effects._ -import com.mongodb.DBRef final class HookJoiner( hookRepo: HookRepo, fisherman: Fisherman, gameRepo: GameRepo, userRepo: UserRepo, - userDbRef: User ⇒ DBRef, timelinePush: DbGame ⇒ IO[Unit], messenger: Messenger) { @@ -51,7 +49,7 @@ final class HookJoiner( def blame(color: DbGame ⇒ ChessColor, userOption: Option[User], game: DbGame) = userOption.fold( - user ⇒ game.updatePlayer(color(game), _.withUser(user, userDbRef(user))), + user ⇒ game.updatePlayer(color(game), _ withUser user), game) def makeGame(hook: Hook) = DbGame( diff --git a/app/setup/Processor.scala b/app/setup/Processor.scala index 36703882ce..333f99a286 100644 --- a/app/setup/Processor.scala +++ b/app/setup/Processor.scala @@ -8,7 +8,6 @@ import chess.{ Game, Board } import ai.Ai import lobby.{ Hook, Fisherman } -import com.mongodb.DBRef import scalaz.effects._ final class Processor( @@ -17,8 +16,7 @@ final class Processor( gameRepo: GameRepo, fisherman: Fisherman, timelinePush: DbGame ⇒ IO[Unit], - ai: () ⇒ Ai, - userDbRef: User ⇒ DBRef) { + ai: () ⇒ Ai) { def ai(config: AiConfig)(implicit ctx: Context): IO[Pov] = for { _ ← ctx.me.fold( @@ -27,7 +25,7 @@ final class Processor( ) pov = config.pov game = ctx.me.fold( - user ⇒ pov.game.updatePlayer(pov.color, _.withUser(user, userDbRef(user))), + user ⇒ pov.game.updatePlayer(pov.color, _ withUser user), pov.game) _ ← gameRepo insert game _ ← gameRepo denormalizeStarted game @@ -50,7 +48,7 @@ final class Processor( ) pov = config.pov game = ctx.me.fold( - user ⇒ pov.game.updatePlayer(pov.color, _.withUser(user, userDbRef(user))), + user ⇒ pov.game.updatePlayer(pov.color, _ withUser user), pov.game) _ ← gameRepo insert game _ ← timelinePush(game) diff --git a/app/setup/Rematcher.scala b/app/setup/Rematcher.scala index 6b730ff300..e65b3c506c 100644 --- a/app/setup/Rematcher.scala +++ b/app/setup/Rematcher.scala @@ -89,7 +89,7 @@ final class Rematcher( game.player(color).userId.fold( userId ⇒ userRepo byId userId map { userOption ⇒ userOption.fold( - user ⇒ player.withUser(user)(userRepo.dbRef), + user ⇒ player withUser user, player) }, io(player)) diff --git a/app/setup/SetupEnv.scala b/app/setup/SetupEnv.scala index 7f14428cd4..77e285b7ec 100644 --- a/app/setup/SetupEnv.scala +++ b/app/setup/SetupEnv.scala @@ -10,7 +10,6 @@ import user.{ User, UserRepo } import com.mongodb.casbah.MongoCollection import scalaz.effects._ -import com.mongodb.DBRef final class SetupEnv( settings: Settings, @@ -21,8 +20,7 @@ final class SetupEnv( userRepo: UserRepo, timelinePush: DbGame ⇒ IO[Unit], roundMessenger: Messenger, - ai: () ⇒ Ai, - userDbRef: User ⇒ DBRef) { + ai: () ⇒ Ai) { import settings._ @@ -37,8 +35,7 @@ final class SetupEnv( gameRepo = gameRepo, fisherman = fisherman, timelinePush = timelinePush, - ai = ai, - userDbRef = userDbRef) + ai = ai) lazy val friendConfigMemo = new FriendConfigMemo( ttl = SetupFriendConfigMemoTtl) @@ -52,15 +49,13 @@ final class SetupEnv( lazy val friendJoiner = new FriendJoiner( gameRepo = gameRepo, messenger = roundMessenger, - timelinePush = timelinePush, - userDbRef = userDbRef) + timelinePush = timelinePush) lazy val hookJoiner = new HookJoiner( hookRepo = hookRepo, fisherman = fisherman, gameRepo = gameRepo, userRepo = userRepo, - userDbRef = userDbRef, timelinePush = timelinePush, messenger = roundMessenger) } diff --git a/app/setup/UserConfig.scala b/app/setup/UserConfig.scala index 4ff9b94c9e..cf6a1db9b4 100644 --- a/app/setup/UserConfig.scala +++ b/app/setup/UserConfig.scala @@ -27,7 +27,7 @@ case class UserConfig( object UserConfig { def default(user: User) = UserConfig( - id = user.usernameCanonical, + id = user.id, ai = AiConfig.default, friend = FriendConfig.default, hook = HookConfig.default) diff --git a/app/setup/UserConfigRepo.scala b/app/setup/UserConfigRepo.scala index 9a4bf12127..24e14f97f2 100644 --- a/app/setup/UserConfigRepo.scala +++ b/app/setup/UserConfigRepo.scala @@ -19,7 +19,7 @@ class UserConfigRepo(collection: MongoCollection) } yield () def config(user: User): IO[UserConfig] = io { - findOneByID(user.usernameCanonical) flatMap (_.decode) + findOneByID(user.id) flatMap (_.decode) } except { e ⇒ putStrLn("Can't load config: " + e.getMessage) map (_ ⇒ none[UserConfig]) } map (_ | UserConfig.default(user)) diff --git a/app/socket/HubActor.scala b/app/socket/HubActor.scala index 33e1a84159..f7857ec649 100644 --- a/app/socket/HubActor.scala +++ b/app/socket/HubActor.scala @@ -48,7 +48,7 @@ abstract class HubActor[M <: SocketMember](uidTimeout: Int) extends Actor { setAlive(uid) member(uid) foreach { m ⇒ m.channel push m.username.fold( - u ⇒ pong ++ JsObject(Seq("m" -> env.message.unreadCache.get(u))), + u ⇒ pong ++ JsObject(Seq("m" -> JsNumber(unreadMessages(u)))), pong) } } @@ -83,4 +83,7 @@ abstract class HubActor[M <: SocketMember](uidTimeout: Int) extends Actor { def member(uid: String): Option[M] = members get uid def usernames: Iterable[String] = members.values.map(_.username).flatten + + private def unreadMessages(username: String): Int = + env.message.unreadCache get username } diff --git a/app/user/Cached.scala b/app/user/Cached.scala index d3d4addd6a..1f006b64ea 100644 --- a/app/user/Cached.scala +++ b/app/user/Cached.scala @@ -2,25 +2,17 @@ package lila package user import scala.collection.mutable -import com.mongodb.casbah.Imports.ObjectId final class Cached(userRepo: UserRepo) { - // idString => username|Anonymous - val usernameCache = mutable.Map[String, String]() - - // username => Option[ObjectId] - val idCache = mutable.Map[String, Option[ObjectId]]() + // id => username + val usernameCache = mutable.Map[String, Option[String]]() def username(userId: String) = usernameCache.getOrElseUpdate( - userId, - (userRepo username userId).unsafePerformIO | "Anonymous" + userId.toLowerCase, + (userRepo username userId).unsafePerformIO ) - def id(username: String): Option[ObjectId] = - idCache.getOrElseUpdate( - username.toLowerCase, - (userRepo id username).unsafePerformIO - ) + def usernameOrAnonymous(userId: String) = username(userId) | User.anonymous } diff --git a/app/user/EloUpdater.scala b/app/user/EloUpdater.scala index 0ed70e1870..ff8b0ea398 100644 --- a/app/user/EloUpdater.scala +++ b/app/user/EloUpdater.scala @@ -12,12 +12,12 @@ final class EloUpdater( def game(user: User, elo: Int, gameId: String): IO[Unit] = { val newElo = max(elo, floor) userRepo.setElo(user.id, newElo) flatMap { _ ⇒ - historyRepo.addEntry(user.usernameCanonical, newElo, Some(gameId)) + historyRepo.addEntry(user.id, newElo, Some(gameId)) } } def adjust(user: User, elo: Int): IO[Unit] = userRepo.setElo(user.id, elo) flatMap { _ ⇒ - historyRepo.addEntry(user.usernameCanonical, elo, entryType = HistoryRepo.TYPE_ADJUST) + historyRepo.addEntry(user.id, elo, entryType = HistoryRepo.TYPE_ADJUST) } } diff --git a/app/user/User.scala b/app/user/User.scala index ebab8ccb3c..f89e3b0ea7 100644 --- a/app/user/User.scala +++ b/app/user/User.scala @@ -2,11 +2,10 @@ package lila package user import com.novus.salat.annotations.Key -import com.mongodb.casbah.Imports.ObjectId import org.joda.time.DateTime case class User( - @Key("_id") id: ObjectId, + id: String, username: String, elo: Int, nbGames: Int, @@ -18,8 +17,6 @@ case class User( bio: Option[String] = None, engine: Boolean = false) { - def usernameCanonical = username.toLowerCase - def disabled = !enabled def usernameWithElo = "%s (%d)".format(username, elo) @@ -29,8 +26,6 @@ case class User( def nonEmptyBio = bio filter ("" !=) def hasGames = nbGames > 0 - - def idString = id.toString } object User { @@ -41,7 +36,7 @@ object User { // the password is hashed def apply(username: String): User = User( - id = new ObjectId, + id = username.toLowerCase, username = username, elo = STARTING_ELO, nbGames = 0, diff --git a/app/user/UserEnv.scala b/app/user/UserEnv.scala index f1611f2202..10dc5ad446 100644 --- a/app/user/UserEnv.scala +++ b/app/user/UserEnv.scala @@ -2,8 +2,6 @@ package lila package user import com.mongodb.casbah.MongoCollection -import com.mongodb.casbah.Imports.ObjectId -import com.mongodb.DBRef import chess.EloCalculator import game.GameRepo @@ -12,16 +10,14 @@ import core.Settings final class UserEnv( settings: Settings, mongodb: String ⇒ MongoCollection, - gameRepo: GameRepo, - dbRef: String ⇒ ObjectId ⇒ DBRef) { + gameRepo: GameRepo) { import settings._ lazy val historyRepo = new HistoryRepo(mongodb(MongoCollectionHistory)) lazy val userRepo = new UserRepo( - collection = mongodb(MongoCollectionUser), - dbRef = user ⇒ dbRef(MongoCollectionUser)(user.id)) + collection = mongodb(MongoCollectionUser)) lazy val paginator = new PaginatorBuilder( userRepo = userRepo, diff --git a/app/user/UserHelper.scala b/app/user/UserHelper.scala index 09d62537c0..ffcf945a06 100644 --- a/app/user/UserHelper.scala +++ b/app/user/UserHelper.scala @@ -4,7 +4,6 @@ package user import core.CoreEnv import controllers.routes -import com.mongodb.casbah.Imports.ObjectId import play.api.templates.Html trait UserHelper { @@ -14,20 +13,18 @@ trait UserHelper { private def cached = env.user.cached private def usernameMemo = env.user.usernameMemo - def userIdToUsername(userId: String): String = cached username userId + def userIdToUsername(userId: String): String = + cached usernameOrAnonymous userId def userIdToUsername(userId: Option[String]): String = userId.fold(userIdToUsername, User.anonymous) def isUsernameOnline(username: String) = usernameMemo get username - def isUserIdOnline(userId: String) = - usernameMemo get userIdToUsername(userId) - def userIdLink( userId: Option[String], cssClass: Option[String]): Html = Html { - (userId map userIdToUsername).fold( + (userId flatMap cached.username).fold( username ⇒ """%s""".format( isUsernameOnline(username).fold(" online", ""), cssClass.fold(" " + _, ""), @@ -38,8 +35,8 @@ trait UserHelper { } def userIdLink( - userId: ObjectId, - cssClass: Option[String]): Html = userIdLink(userId.toString.some, cssClass) + userId: String, + cssClass: Option[String]): Html = userIdLink(userId.some, cssClass) def userLink( user: User, diff --git a/app/user/UserRepo.scala b/app/user/UserRepo.scala index 378d68649d..a6e44c6e3d 100644 --- a/app/user/UserRepo.scala +++ b/app/user/UserRepo.scala @@ -3,7 +3,6 @@ package user import com.novus.salat._ import com.novus.salat.dao._ -import com.mongodb.DBRef import com.mongodb.casbah.{ MongoCollection, WriteConcern } import com.mongodb.casbah.Imports._ import scalaz.effects._ @@ -11,51 +10,38 @@ import com.roundeights.hasher.Implicits._ import ornicar.scalalib.OrnicarRandom class UserRepo( - collection: MongoCollection, - val dbRef: User ⇒ DBRef) extends SalatDAO[User, ObjectId](collection) { + collection: MongoCollection + ) extends SalatDAO[User, String](collection) { - private val enabledQuery = DBObject("enabled" -> true) + val enabledQuery = DBObject("enabled" -> true) + def byIdQuery(id: String): DBObject = DBObject("_id" -> normalize(id)) + def byIdQuery(user: User): DBObject = byIdQuery(user.id) - private def byUsernameQuery(username: String) = - DBObject("usernameCanonical" -> username.toLowerCase) + def normalize(id: String) = id.toLowerCase - private def byIdQuery(id: String) = DBObject("_id" -> new ObjectId(id)) + def byId(id: String): IO[Option[User]] = io { + findOneByID(normalize(id)) + } - def byId(userId: String): IO[Option[User]] = byId(new ObjectId(userId)) - - def byId(userId: ObjectId): IO[Option[User]] = io { - findOneByID(userId) + def byIds(ids: Iterable[String]): IO[List[User]] = io { + find("_id" $in ids.map(normalize)).toList } def username(userId: String): IO[Option[String]] = io { primitiveProjection[String](byIdQuery(userId), "username") } - def id(username: String): IO[Option[ObjectId]] = io { - primitiveProjection[ObjectId](byUsernameQuery(username), "_id") - } - - def byUsername(username: String): IO[Option[User]] = io { - findOne(byUsernameQuery(username)) - } - - def byUsernames(usernames: Iterable[String]): IO[List[User]] = io { - find("usernameCanonical" $in usernames).toList - } - def rank(user: User): IO[Int] = io { count("elo" $gt user.elo).toInt + 1 } - def setElo(userId: ObjectId, elo: Int): IO[Unit] = io { - collection.update( - idSelector(userId), - $set("elo" -> elo)) + def setElo(id: String, elo: Int): IO[Unit] = io { + collection.update(byIdQuery(id), $set("elo" -> elo)) } - def incNbGames(userId: String, rated: Boolean): IO[Unit] = io { + def incNbGames(id: String, rated: Boolean): IO[Unit] = io { collection.update( - DBObject("_id" -> new ObjectId(userId)), + byIdQuery(id), if (rated) $inc("nbGames" -> 1, "nbRatedGames" -> 1) else $inc("nbGames" -> 1)) } @@ -66,30 +52,26 @@ class UserRepo( } def toggleChatBan(user: User): IO[Unit] = io { - collection.update( - idSelector(user), - $set("isChatBan" -> !user.isChatBan)) + collection.update(byIdQuery(user), $set("isChatBan" -> !user.isChatBan)) } def saveSetting(user: User, key: String, value: String) = io { - collection.update( - idSelector(user), - $set(("settings." + key) -> value)) + collection.update(byIdQuery(user), $set(("settings." + key) -> value)) } def exists(username: String): IO[Boolean] = io { - count(byUsernameQuery(username)) != 0 + count(byIdQuery(username)) != 0 } def authenticate(username: String, password: String): IO[Option[User]] = for { - userOption ← byUsername(username) + userOption ← byId(username) greenLight ← authenticable(username, password) } yield userOption filter (_ ⇒ greenLight) private def authenticable(username: String, password: String): IO[Boolean] = io { for { data ← collection.findOne( - byUsernameQuery(username), + byIdQuery(username), DBObject("password" -> true, "salt" -> true, "enabled" -> true) ) hashed ← data.getAs[String]("password") @@ -105,8 +87,8 @@ class UserRepo( io { val salt = OrnicarRandom nextAsciiString 32 val obj = DBObject( + "_id" -> normalize(username), "username" -> username, - "usernameCanonical" -> username.toLowerCase, "password" -> hash(password, salt), "salt" -> salt, "elo" -> User.STARTING_ELO, @@ -115,18 +97,18 @@ class UserRepo( "enabled" -> true, "roles" -> Nil) collection.insert(obj, WriteConcern.Safe) - } flatMap { _ ⇒ byUsername(username) } + } flatMap { _ ⇒ byId(username) } ) } yield userOption val countEnabled: IO[Int] = io { count(enabledQuery).toInt } def usernamesLike(username: String): IO[List[String]] = io { - val regex = "^" + username.toLowerCase + ".*$" + val regex = "^" + normalize(username) + ".*$" collection.find( - DBObject("usernameCanonical" -> regex.r), + DBObject("_id" -> regex.r), DBObject("username" -> 1)) - .sort(DBObject("usernameCanonical" -> 1)) + .sort(DBObject("_id" -> 1)) .limit(10) .toList .map(_.expand[String]("username")) @@ -150,18 +132,14 @@ class UserRepo( def disable(user: User) = updateIO(user)($set("enabled" -> false)) def updateIO(username: String)(op: User ⇒ DBObject): IO[Unit] = for { - userOption ← byUsername(username) + userOption ← byId(username) _ ← userOption.fold(user ⇒ updateIO(user)(op(user)), io()) } yield () def updateIO(user: User)(obj: DBObject): IO[Unit] = io { - update(idSelector(user), obj) + update(byIdQuery(user), obj) } - private def idSelector(user: User) = DBObject("_id" -> user.id) - - private def idSelector(id: ObjectId) = DBObject("_id" -> id) - private def hash(pass: String, salt: String): String = "%s{%s}".format(pass, salt).sha1 } diff --git a/bin/migrate b/bin/migrate new file mode 100755 index 0000000000..a4cc6bb5b6 --- /dev/null +++ b/bin/migrate @@ -0,0 +1,7 @@ +#!/bin/sh +mongo lichess mongo_migration_user.js +mongo lichess mongo_migration_forum.js +mongo lichess mongo_migration_message.js +mongo lichess mongo_migration_rest.js +mongo lichess mongo_migration_game.js +bin/cli forum-denormalize diff --git a/cli/src/main/scala/Games.scala b/cli/src/main/scala/Games.scala index f198f2d695..81f8bf8a63 100644 --- a/cli/src/main/scala/Games.scala +++ b/cli/src/main/scala/Games.scala @@ -5,10 +5,4 @@ import scalaz.effects._ case class Games(gameRepo: GameRepo) { - def index: IO[Unit] = for { - _ ← putStrLn("- Drop indexes") - _ ← gameRepo.dropIndexes - _ ← putStrLn("- Ensure indexes") - _ ← gameRepo.ensureIndexes - } yield () } diff --git a/cli/src/main/scala/Main.scala b/cli/src/main/scala/Main.scala index e1131e559e..125e6176d8 100644 --- a/cli/src/main/scala/Main.scala +++ b/cli/src/main/scala/Main.scala @@ -25,7 +25,6 @@ object Main { val op: IO[Unit] = args.toList match { case "average-elo" :: Nil ⇒ infos.averageElo - case "game-index" :: Nil ⇒ games.index case "trans-js-dump" :: Nil ⇒ TransJsDump( path = new File(env.app.path.getCanonicalPath + "/public/trans"), pool = env.i18n.pool, diff --git a/cli/src/main/scala/Users.scala b/cli/src/main/scala/Users.scala index dcc2773aeb..9bdf938d28 100644 --- a/cli/src/main/scala/Users.scala +++ b/cli/src/main/scala/Users.scala @@ -18,7 +18,7 @@ case class Users(userRepo: UserRepo, securityStore: Store) { def perform(username: String, action: String, op: User ⇒ IO[Unit]) = for { _ ← putStrLn(action + " " + username) - userOption ← userRepo byUsername username + userOption ← userRepo byId username _ ← userOption.fold( u ⇒ op(u) flatMap { _ ⇒ putStrLn("Success") }, putStrLn("Not found") diff --git a/conf/application.conf b/conf/application.conf index 937b6a8704..1133c09c8d 100644 --- a/conf/application.conf +++ b/conf/application.conf @@ -3,8 +3,8 @@ mongo { port = 27017 dbName = lichess collection { - game = game - user = user + game = game2 + user = user2 hook = hook entry = lobby_entry message = lobby_room diff --git a/mongo_migration_forum.js b/mongo_migration_forum.js index f241f9c860..a3a9778d65 100644 --- a/mongo_migration_forum.js +++ b/mongo_migration_forum.js @@ -1,8 +1,12 @@ print("Hashing users") -var users = {}; +var userHash = {}; db.user2.find({},{oid:1}).forEach(function(user) { - users[user.oid.toString()] = user._id; + userHash[user.oid.toString()] = user._id; }); +function user(oid) { + if(userHash[oid]) return userHash[oid]; + throw "Missing user " + oid; +} var categSlugs = {}; var topicIds = {}; @@ -73,7 +77,7 @@ var topicIds = {}; number: obj.number }; if (obj.author) { - post.userId = users[obj.author['$id'].toString()]; + post.userId = user(obj.author['$id'].toString()); } coll.insert(post); } diff --git a/mongo_migration_game.js b/mongo_migration_game.js index 4c20a43b72..87f6c75f6c 100644 --- a/mongo_migration_game.js +++ b/mongo_migration_game.js @@ -10,11 +10,12 @@ function user(oid) { print("Games"); var batch = 10000; +var max = 100000; var oGames = db.game; var nGames = db.game2; var it = 0, totalNb = oGames.count(); nGames.drop(); -oGames.find().batchSize(batch).limit(100000).forEach(function(game) { +oGames.find().batchSize(batch).limit(max).forEach(function(game) { delete game["positionHashes"]; delete game["players.0.previousMoveTs"]; delete game["players.1.previousMoveTs"]; @@ -45,5 +46,15 @@ oGames.find().batchSize(batch).limit(100000).forEach(function(game) { } nGames.insert(game); ++it; - if (0 == it % batch) print(it + "/" + totalNb); + if (0 == it % batch) + print(it + "/" + totalNb + " " + Math.round(100*it/totalNb) + "%"); }); + +print("Indexes"); +nGames.ensureIndex({status:1}); +nGames.ensureIndex({userIds:1}); +nGames.ensureIndex({winId:1}); +nGames.ensureIndex({turns:1}); +nGames.ensureIndex({updatedAt:-1}); +nGames.ensureIndex({createdAt:-1}); +nGames.ensureIndex({userIds:1, createdAt:-1}); diff --git a/mongo_migration_message.js b/mongo_migration_message.js index eef7417ecb..db259b1f3e 100644 --- a/mongo_migration_message.js +++ b/mongo_migration_message.js @@ -1,8 +1,12 @@ print("Hashing users") -var users = {}; +var userHash = {}; db.user2.find({},{oid:1}).forEach(function(user) { - users[user.oid.toString()] = user._id; + userHash[user.oid.toString()] = user._id; }); +function user(oid) { + if(userHash[oid]) return userHash[oid]; + throw "Missing user " + oid; +} print("Threads and messages"); var oThreads = db.message_thread; @@ -16,7 +20,7 @@ oThreads.find().forEach(function(oThread) { name: oThread.subject, createdAt: oThread.createdAt, creatorId: creatorId(oThread), - invitedId: users[invOid], + invitedId: user(invOid), visibleByUserIds: visibleByUserIds(oThread) }; var posts = []; @@ -57,7 +61,7 @@ function invitedOid(oThread) { } function userIds(oThread) { - return [creatorId(oThread), users[invitedOid(oThread)]]; + return [creatorId(oThread), user(invitedOid(oThread))]; } function visibleByUserIds(oThread) { @@ -69,8 +73,8 @@ function visibleByUserIds(oThread) { } function username(obj) { - if (typeof obj == "object") return users[objId(obj)]; - return users[obj]; + if (typeof obj == "object") return user(objId(obj)); + return user(obj); } function objId(obj) { diff --git a/mongo_migration_rest.js b/mongo_migration_rest.js new file mode 100644 index 0000000000..16f3976765 --- /dev/null +++ b/mongo_migration_rest.js @@ -0,0 +1,13 @@ +print("Other collections"); + +db.lobby_room.drop(); +db.createCollection("lobby_room", {capped:true, max:1000}) + +db.lobby_entry.drop(); +db.createCollection("lobby_entry", {capped:true, max:50}) + +db.hook.drop(); +db.hook.ensureIndex({ownerId: 1}); +db.hook.ensureIndex({mode: 1}); +db.hook.ensureIndex({createdAt: -1}); +db.hook.ensureIndex({match: 1}); diff --git a/todo b/todo index 4f87dcdb07..ae968d6c87 100644 --- a/todo +++ b/todo @@ -22,12 +22,10 @@ http://codetunes.com/2012/05/09/scala-dsl-tutorial-writing-web-framework-router use POST instead of GET where it makes sense endgame sound http://en.lichess.org/forum/lichess-feedback/checkmate-sound-feature?page=1#1 cached username option app/user/Cached.scala +game.next dbref => string next deploy: -mongo lichess mongo_migration_user.js -mongo lichess mongo_migration_forum.js -mongo lichess mongo_migration_message.js -bin/cli forum-denormalize +bin/migrate new translations: -rematchOfferCanceled=Rematch offer canceled -rematchOfferDeclined=Rematch offer declined