many post-deploy fixes

pull/83/head
Thibault Duplessis 2013-12-22 14:15:02 +01:00
parent 85eafa181a
commit 87846bac10
25 changed files with 541 additions and 36 deletions

View File

@ -1,13 +1,13 @@
package controllers
import play.api.data._, Forms._
import play.api.mvc._, Results._
import lila.app._
import lila.common.LilaCookie
import lila.user.{ UserRepo, HistoryRepo }
import views._
import play.api.mvc._, Results._
import play.api.data._, Forms._
object Auth extends LilaController {
private def api = Env.security.api
@ -70,6 +70,19 @@ object Auth extends LilaController {
)
}
def newPassword = AuthBody { implicit ctx
me
implicit val req = ctx.body
forms.newPassword.bindFromRequest.fold(
err fuccess {
BadRequest(html.auth.artificialPassword(me, err))
},
pass UserRepo.artificialSetPassword(me.id, pass) map { _
Redirect(routes.Lobby.home)
}
)
}
private def gotoLogoutSucceeded(implicit req: RequestHeader) = {
req.session get "sessionId" foreach lila.security.Store.delete
logoutSucceeded(req) withCookies LilaCookie.newSession

View File

@ -23,7 +23,7 @@ object Cli extends LilaController {
})
}
private def CliAuth(password: String)(op: Fu[SimpleResult]): Fu[SimpleResult] =
private def CliAuth(password: String)(op: => Fu[SimpleResult]): Fu[SimpleResult] =
lila.user.UserRepo.checkPassword(Env.api.CliUsername, password) flatMap {
_.fold(op, fuccess(Unauthorized))
}

View File

@ -13,9 +13,14 @@ import views._
object Lobby extends LilaController {
def home = Open { implicit ctx
renderHome(Results.Ok).map(_.withHeaders(
CACHE_CONTROL -> "no-cache", PRAGMA -> "no-cache"
))
ctx.me match {
case Some(u) if u.artificial fuccess {
views.html.auth.artificialPassword(u, Env.security.forms.newPassword)
}
case _ renderHome(Results.Ok).map(_.withHeaders(
CACHE_CONTROL -> "no-cache", PRAGMA -> "no-cache"
))
}
}
def handleStatus(req: RequestHeader, status: Results.Status): Fu[SimpleResult] =

View File

@ -39,7 +39,9 @@ object User extends LilaController {
OptionFuResult(UserRepo named username) { u
(u.enabled || isGranted(_.UserSpy)).fold({
userShow(u, filterName, page) map { Ok(_) }
}, fuccess(NotFound(html.user.disabled(u))))
}, UserRepo isArtificial u.id map { artificial
NotFound(html.user.disabled(u, artificial))
})
}
}

View File

@ -0,0 +1,32 @@
@(user: User, form: Form[_])(implicit ctx: Context)
@auth.layout("Please enter your password") {
<div class="content_box" style="width: 400px;">
<h1 class="lichess_title">Please enter your password</h1>
<p>
Due to a system failure that happened the 22 Dec. 2013,<br />
You have to re-enter your password to continue using lichess.<br />
For details, see <a href="http://en.lichess.org/forum/lichess-feedback/server-failure--data-has-been-lost">The announcement</a>.
</p>
<form action="@routes.Auth.newPassword" method="POST">
@form.globalError.map { error =>
<p class="error">@error.message</p>
}
<ul>
<li class="password">
<label for="@form("password").id">@trans.password()</label>
<input
type="password"
required="required"
name="@form("password").name"
id="@form("password").id" />
@errMsg(form("password"))
</li>
<li>
<input type="submit" value="@trans.apply()" class="submit button">
</li>
</ul>
</form>
</div>
}

View File

@ -1,4 +1,4 @@
@(u: User)(implicit ctx: Context)
@(u: User, artificial: Boolean)(implicit ctx: Context)
@user.layout(
title = u.username,
@ -9,6 +9,11 @@ robots = false) {
</div>
<div class="content_box_content clearfix">
This account is closed.
@if(artificial) {
This account infos have been lost during a server failure.<br />
All new accounts between 12 Dec 2013 and 22 Dec 2013 are affected.<br />
We are very sorry for the inconvenience.
}
</div>
</div>
}

View File

@ -7,10 +7,20 @@
</form>
<div class="user_lists">
@user.top(online, "Online players") { u =>
<td>@u.rating</td>
<td>@showProgress(u.progress)</td>
}
<div class="user_top">
<h2>Online players</h2>
<table>
<tbody>
@online.map { u =>
<tr>
<td>@userLink(u, withOnline = false, withRating = false, cssClass="revert-underline".some)</td>
<td>@u.rating</td>
<td>@showProgress(u.progress)</td>
</tr>
}
</tbody>
</table>
</div>
<a class="more" href="@routes.User.online" title="@trans.more()">@trans.more() »</a>
</div>
</div>

View File

@ -2,10 +2,10 @@
. bin/lilarc
mkdir -p public/compiled
mkdir -p public/javascripts/compiled
for file in big.js chart2.js user.js boardEditor.js pgn4hacks.js; do
orig=public/javascripts/$file
comp=public/compiled/$file
comp=public/javascripts/compiled/$file
if [[ ! -f $comp || $orig -nt $comp ]]; then
lilalog "Compiling lila javascript - $file"
closure --js $orig --js_output_file $comp

View File

@ -0,0 +1,9 @@
db.tournament.find({}, {players:1}).forEach(function(tour) {
for (var i in tour.players) {
var p = tour.players[i];
p.rating = p.elo;
delete p.elo;
tour.players[i] = p;
}
db.tournament.update({"_id": tour._id}, {"$set":{players: tour.players}});
});

View File

@ -1,5 +1,5 @@
var games = db.game5;
var users = db.user3;
var users = db.user4;
var batchSize = 1000;
var i, t, timeStrings, times, it=0;

View File

@ -140,6 +140,7 @@ POST /login controllers.Auth.authenticate
GET /logout controllers.Auth.logout
GET /signup controllers.Auth.signup
POST /signup controllers.Auth.signupPost
POST /new-password controllers.Auth.newPassword
# Mod
POST /mod/:username/engine controllers.Mod.engine(username: String)

View File

@ -18,7 +18,7 @@ final class Env(
val IsServer = config getBoolean "server"
val IsClient = config getBoolean "client"
val StockfishRemotes = config getStringList "stockfish.remotes" toList
val StockfishLocal = config getString "stockfish.local"
val StockfishLocal = config getString "stockfish.local"
val StockfishPlayRoute = config getString "stockfish.play.route"
val StockfishAnalyseRoute = config getString "stockfish.analyse.route"
val StockfishLoadRoute = config getString "stockfish.load.route"
@ -69,7 +69,7 @@ final class Env(
playRoute = StockfishPlayRoute,
analyseRoute = StockfishAnalyseRoute,
loadRoute = StockfishLoadRoute) _
)
)
), name = "stockfish-dispatcher"),
fallback = stockfishServer,
config = stockfishConfig,

View File

@ -22,6 +22,14 @@ private[api] final class Cli(bus: lila.common.Bus, renderer: ActorSelection) ext
lila.db.Env.current,
lila.game.Env.current,
lila.user.Env.current)
case "glicko" :: "migration" :: "end" :: Nil => GlickoMigrationEnd(
lila.db.Env.current,
lila.game.Env.current,
lila.user.Env.current)
case "glicko" :: "migration" :: "fix" :: Nil => GlickoMigrationFix(
lila.db.Env.current,
lila.game.Env.current,
lila.user.Env.current)
}
private def remindDeploy(event: RemindDeploy): Fu[String] = {

View File

@ -0,0 +1,172 @@
package lila.api
import scala.concurrent.duration._
import scala.concurrent.Future
import scala.util.{ Try, Success, Failure }
import org.goochjs.glicko2._
import org.joda.time.DateTime
import play.api.libs.iteratee._
import play.api.libs.json.Json
import reactivemongo.bson._
import lila.db.api._
import lila.db.Implicits._
import lila.game.Game
import lila.game.Game.{ BSONFields G }
import lila.round.PerfsUpdater.{ Ratings, resultOf, updateRatings, mkPerf, system, makeProgress }
import lila.user.{ User, UserRepo, HistoryRepo, Glicko, GlickoEngine, Perfs, Perf, HistoryEntry }
object GlickoMigrationEnd {
def apply(
db: lila.db.Env,
gameEnv: lila.game.Env,
userEnv: lila.user.Env) = {
val oldUserColl = db("user3")
val oldUserRepo = new UserRepo {
def userTube = lila.user.tube.userTube inColl oldUserColl
}
val gameColl = lila.game.tube.gameTube.coll
val limit = Int.MaxValue
// val limit = 100000
// val limit = 1000
var nb = 0
import scala.collection.mutable
val ratings = mutable.Map.empty[String, Ratings]
val histories = mutable.Map.empty[String, mutable.ListBuffer[HistoryEntry]]
val enumerator: Enumerator[Option[Game]] = lila.game.tube.gameTube |> { implicit gameTube
import Game.gameBSONHandler
$query(lila.game.Query.rated)
// .batch(1000)
.sort($sort asc G.createdAt)
.cursor[Option[Game]].enumerate(limit, false)
}
def iteratee(isEngine: Set[String]): Iteratee[Option[Game], Unit] = {
Iteratee.foreach[Option[Game]] {
_ foreach { game
nb = nb + 1
if (nb % 1000 == 0) println(nb)
game.userIds match {
// case _ if game.source.exists(lila.game.Source.Position ==) unrate(game)
// case List(uidW, uidB) if (uidW == uidB) unrate(game)
// case List(uidW, uidB) if isEngine(uidW) || isEngine(uidB) unrate(game)
case List(uidW, uidB) {
val ratingsW = ratings.getOrElseUpdate(uidW, mkRatings)
val ratingsB = ratings.getOrElseUpdate(uidB, mkRatings)
val prevRatingW = ratingsW.global.getRating.toInt
val prevRatingB = ratingsB.global.getRating.toInt
val result = resultOf(game)
updateRatings(ratingsW.global, ratingsB.global, result, system)
updateRatings(ratingsW.white, ratingsB.black, result, system)
game.variant match {
case chess.Variant.Standard
updateRatings(ratingsW.standard, ratingsB.standard, result, system)
case chess.Variant.Chess960
updateRatings(ratingsW.chess960, ratingsB.chess960, result, system)
case _
}
chess.Speed(game.clock) match {
case chess.Speed.Bullet
updateRatings(ratingsW.bullet, ratingsB.bullet, result, system)
case chess.Speed.Blitz
updateRatings(ratingsW.blitz, ratingsB.blitz, result, system)
case chess.Speed.Slow | chess.Speed.Unlimited
updateRatings(ratingsW.slow, ratingsB.slow, result, system)
}
// histories.getOrElseUpdate(uidW, mkHistory) +=
// HistoryEntry(game.createdAt, ratingsW.global.getRating.toInt, ratingsW.global.getRatingDeviation.toInt, prevRatingB)
// histories.getOrElseUpdate(uidB, mkHistory) +=
// HistoryEntry(game.createdAt, ratingsB.global.getRating.toInt, ratingsB.global.getRatingDeviation.toInt, prevRatingW)
// gameColl.uncheckedUpdate(
// BSONDocument("_id" -> game.id),
// BSONDocument("$set" -> BSONDocument(
// "p0.e" -> prevRatingW,
// "p0.d" -> (ratingsW.global.getRating.toInt - prevRatingW),
// "p1.e" -> prevRatingB,
// "p1.d" -> (ratingsB.global.getRating.toInt - prevRatingB)
// )))
}
case _
}
}
}
}
// def unrate(game: Game) {
// gameColl.uncheckedUpdate(
// BSONDocument("_id" -> game.id),
// BSONDocument("$unset" -> BSONDocument(
// "ra" -> true,
// "p0.d" -> true,
// "p1.d" -> true
// )))
// }
// def mkHistory = mutable.ListBuffer(
// HistoryEntry(DateTime.now, Glicko.default.intRating, Glicko.default.intDeviation, Glicko.default.intRating)
// )
def mkRatings = {
def r = new Rating(system.getDefaultRating, system.getDefaultRatingDeviation, system.getDefaultVolatility, 0)
new Ratings(r, r, r, r, r, r, r, r)
}
def updateUsers(userPerfs: Map[String, Perfs]): Future[Unit] = lila.user.tube.userTube |> { implicit userTube
userTube.coll.drop() flatMap { _
oldUserColl.genericQueryBuilder.cursor[BSONDocument].enumerate() |>>> Iteratee.foreach[BSONDocument] { user
user.getAs[String]("_id") foreach { id
val perfs = userPerfs get id getOrElse Perfs.default
makeProgress(id) foreach { progress
userTube.coll insert {
writeDoc(user, Set("elo", "variantElos", "speedElos")) ++ BSONDocument(
User.BSONFields.perfs -> lila.user.Perfs.tube.handler.write(perfs),
User.BSONFields.rating -> perfs.global.glicko.intRating,
User.BSONFields.progress -> progress
)
}
}
}
}
}
}
// def updateHistories(histories: Iterable[(String, Iterable[HistoryEntry])]): Funit = {
// userEnv.historyColl.drop() recover {
// case e: Exception fuccess()
// } flatMap { _
// Future.traverse(histories) {
// case (id, history) HistoryRepo.set(id, history)
// }
// }
// }.void
def mkPerfs(ratings: Ratings): Perfs = Perfs(
global = mkPerf(ratings.global, None),
standard = mkPerf(ratings.standard, None),
chess960 = mkPerf(ratings.chess960, None),
bullet = mkPerf(ratings.bullet, None),
blitz = mkPerf(ratings.blitz, None),
slow = mkPerf(ratings.slow, None),
white = mkPerf(ratings.white, None),
black = mkPerf(ratings.black, None))
oldUserRepo.engineIds flatMap { engineIds
(enumerator |>>> iteratee(engineIds)) flatMap { _
val perfs = (ratings mapValues mkPerfs).toMap
updateUsers(perfs) map { _
println("Done!")
"done"
}
}
}
}
private def writeDoc(doc: BSONDocument, drops: Set[String]) = BSONDocument(doc.elements collect {
case (k, v) if !drops(k) k -> v
})
}

View File

@ -0,0 +1,205 @@
package lila.api
import scala.concurrent.duration._
import scala.concurrent.Future
import scala.util.{ Try, Success, Failure }
import org.goochjs.glicko2._
import org.joda.time.DateTime
import play.api.libs.iteratee._
import play.api.libs.json.Json
import reactivemongo.bson._
import lila.db.api._
import lila.db.Implicits._
import lila.game.Game
import lila.game.Game.{ BSONFields G }
import lila.round.PerfsUpdater.{ Ratings, resultOf, updateRatings, mkPerf, system, makeProgress }
import lila.user.{ User, UserRepo, HistoryRepo, Glicko, GlickoEngine, Perfs, Perf, HistoryEntry }
object GlickoMigrationFix {
def apply(
db: lila.db.Env,
gameEnv: lila.game.Env,
userEnv: lila.user.Env) = {
val oldUserColl = db("user3")
val oldUserRepo = new UserRepo {
def userTube = lila.user.tube.userTube inColl oldUserColl
}
val gameColl = lila.game.tube.gameTube.coll
val limit = Int.MaxValue
// val limit = 100000
// val limit = 1000
var nb = 0
import scala.collection.mutable
val ratings = mutable.Map.empty[String, Ratings]
val histories = mutable.Map.empty[String, mutable.ListBuffer[HistoryEntry]]
val enumerator: Enumerator[Option[Game]] = lila.game.tube.gameTube |> { implicit gameTube
import Game.gameBSONHandler
$query(lila.game.Query.rated)
// .batch(1000)
.sort($sort asc G.createdAt)
.cursor[Option[Game]].enumerate(limit, false)
}
def iteratee(isEngine: Set[String]): Iteratee[Option[Game], Unit] = {
Iteratee.foreach[Option[Game]] {
_ foreach { game
nb = nb + 1
if (nb % 1000 == 0) println(nb)
game.userIds match {
// case _ if game.source.exists(lila.game.Source.Position ==) unrate(game)
// case List(uidW, uidB) if (uidW == uidB) unrate(game)
// case List(uidW, uidB) if isEngine(uidW) || isEngine(uidB) unrate(game)
case List(uidW, uidB) {
val ratingsW = ratings.getOrElseUpdate(uidW, mkRatings)
val ratingsB = ratings.getOrElseUpdate(uidB, mkRatings)
val prevRatingW = ratingsW.global.getRating.toInt
val prevRatingB = ratingsB.global.getRating.toInt
val result = resultOf(game)
updateRatings(ratingsW.global, ratingsB.global, result, system)
updateRatings(ratingsW.white, ratingsB.black, result, system)
game.variant match {
case chess.Variant.Standard
updateRatings(ratingsW.standard, ratingsB.standard, result, system)
case chess.Variant.Chess960
updateRatings(ratingsW.chess960, ratingsB.chess960, result, system)
case _
}
chess.Speed(game.clock) match {
case chess.Speed.Bullet
updateRatings(ratingsW.bullet, ratingsB.bullet, result, system)
case chess.Speed.Blitz
updateRatings(ratingsW.blitz, ratingsB.blitz, result, system)
case chess.Speed.Slow | chess.Speed.Unlimited
updateRatings(ratingsW.slow, ratingsB.slow, result, system)
}
// histories.getOrElseUpdate(uidW, mkHistory) +=
// HistoryEntry(game.createdAt, ratingsW.global.getRating.toInt, ratingsW.global.getRatingDeviation.toInt, prevRatingB)
// histories.getOrElseUpdate(uidB, mkHistory) +=
// HistoryEntry(game.createdAt, ratingsB.global.getRating.toInt, ratingsB.global.getRatingDeviation.toInt, prevRatingW)
// gameColl.uncheckedUpdate(
// BSONDocument("_id" -> game.id),
// BSONDocument("$set" -> BSONDocument(
// "p0.e" -> prevRatingW,
// "p0.d" -> (ratingsW.global.getRating.toInt - prevRatingW),
// "p1.e" -> prevRatingB,
// "p1.d" -> (ratingsB.global.getRating.toInt - prevRatingB)
// )))
}
case _
}
}
}
}
def unrate(game: Game) {
gameColl.uncheckedUpdate(
BSONDocument("_id" -> game.id),
BSONDocument("$unset" -> BSONDocument(
"ra" -> true,
"p0.d" -> true,
"p1.d" -> true
)))
}
def mkHistory = mutable.ListBuffer(
HistoryEntry(DateTime.now, Glicko.default.intRating, Glicko.default.intDeviation, Glicko.default.intRating)
)
def mkRatings = {
def r = new Rating(system.getDefaultRating, system.getDefaultRatingDeviation, system.getDefaultVolatility, 0)
new Ratings(r, r, r, r, r, r, r, r)
}
def updateUsers(userPerfs: Map[String, Perfs]): Future[Unit] = lila.user.tube.userTube |> { implicit userTube
userTube.coll.drop() flatMap { _
oldUserColl.genericQueryBuilder.cursor[BSONDocument].enumerate() |>>> Iteratee.foreach[BSONDocument] { user
user.getAs[String]("_id") foreach { id
val perfs = userPerfs get id getOrElse Perfs.default
makeProgress(id) foreach { progress
userTube.coll insert {
writeDoc(user, Set("elo", "variantElos", "speedElos")) ++ BSONDocument(
User.BSONFields.perfs -> lila.user.Perfs.tube.handler.write(perfs),
User.BSONFields.rating -> perfs.global.glicko.intRating,
User.BSONFields.progress -> progress
)
}
}
}
}
}
}
val createdAt = new DateTime(2013, 12, 12, 0, 0)
def createArtificialUsers(userPerfs: Map[String, Perfs]): Future[Unit] = lila.user.tube.userTube |> { implicit userTube
Future.traverse(userPerfs) {
case (id, perfs) => UserRepo idExists id flatMap {
case false if id.size <= 20 => {
println(s"Create artificial user $id")
makeProgress(id) flatMap { progress
UserRepo.insertArtificialUser(id, perfs, progress, createdAt)
}
}
case _ => funit
}
} void
}
// def disown(game: Game) {
// gameColl.uncheckedUpdate(
// BSONDocument("_id" -> game.id),
// BSONDocument("$unset" -> BSONDocument(
// "ra" -> true,
// "p0.d" -> true,
// "p0.e" -> true,
// "p1.d" -> true,
// "p1.e" -> true,
// "us" -> true,
// "wid" -> true
// )))
// }
def updateHistories(histories: Iterable[(String, Iterable[HistoryEntry])]): Funit = {
userEnv.historyColl.drop() recover {
case e: Exception fuccess()
} flatMap { _
Future.traverse(histories) {
case (id, history) HistoryRepo.set(id, history)
}
}
}.void
def mkPerfs(ratings: Ratings): Perfs = Perfs(
global = mkPerf(ratings.global, None),
standard = mkPerf(ratings.standard, None),
chess960 = mkPerf(ratings.chess960, None),
bullet = mkPerf(ratings.bullet, None),
blitz = mkPerf(ratings.blitz, None),
slow = mkPerf(ratings.slow, None),
white = mkPerf(ratings.white, None),
black = mkPerf(ratings.black, None))
oldUserRepo.engineIds flatMap { engineIds
(enumerator |>>> iteratee(engineIds)) flatMap { _
val perfs = (ratings mapValues mkPerfs).toMap
// updateHistories(histories) flatMap { _
updateUsers(perfs) flatMap { _
createArtificialUsers(perfs) map { _ =>
println("Done!")
"done"
}
}
// }
}
}
}
private def writeDoc(doc: BSONDocument, drops: Set[String]) = BSONDocument(doc.elements collect {
case (k, v) if !drops(k) k -> v
})
}

View File

@ -230,7 +230,7 @@ trait GameRepo {
_.headOption flatMap extractPgnMoves
} map (~_)
def activePlayersSince(since: DateTime)(nb: Int): Fu[List[(String, Int)]] = {
def activePlayersSince(since: DateTime)(max: Int): Fu[List[(String, Int)]] = {
import reactivemongo.bson._
import reactivemongo.core.commands._
import lila.db.BSON.BSONJodaDateTimeHandler
@ -245,7 +245,8 @@ trait GameRepo {
BSONFields.playerUids -> BSONDocument("$ne" -> "")
)),
GroupField(Game.BSONFields.winnerId)("nb" -> SumValue(1)),
Sort(Seq(Descending("nb")))
Sort(Seq(Descending("nb"))),
Limit(max)
))
gameTube.coll.db.command(command) map { stream
(stream.toList map { obj

View File

@ -29,6 +29,10 @@ final class DataForm(val captcher: akka.actor.ActorSelection) extends lila.hub.C
def signupWithCaptcha = withCaptcha(signup)
val newPassword = Form(single(
"password" -> text(minLength = 4)
))
private def userExists(data: SignupData) =
$count.exists(data.username.toLowerCase)
}

View File

@ -21,7 +21,7 @@ private[setup] final class FormFactory {
"variant" -> list(variant),
"mode" -> list(rawMode(true)),
"speed" -> list(speed),
"eloRange" -> eloRange
"ratingRange" -> ratingRange
)(FilterConfig.<<)(_.>>)
)
@ -83,7 +83,7 @@ private[setup] final class FormFactory {
"increment" -> increment,
"mode" -> mode(ctx.isAuth),
"membersOnly" -> boolean,
"eloRange" -> optional(eloRange),
"ratingRange" -> optional(ratingRange),
"color" -> nonEmptyText.verifying(Color.names contains _)
)(HookConfig.<<)(_.>>)
.verifying("Invalid clock", _.validClock)

View File

@ -17,7 +17,7 @@ object Mappings {
def rawMode(isAuth: Boolean) = number
.verifying(HookConfig.modes contains _)
.verifying(m m == Mode.Casual.id || isAuth)
val eloRange = nonEmptyText.verifying(RatingRange valid _)
val ratingRange = nonEmptyText.verifying(RatingRange valid _)
val color = nonEmptyText.verifying(Color.names contains _)
val level = number.verifying(AiConfig.levels contains _)
val speed = number.verifying(Config.speeds contains _)

View File

@ -32,8 +32,8 @@ private[tournament] object Player {
.filter(p p.finished && (p contains player.id))
.foldLeft(Builder(player))(_ + _.winner)
.toPlayer
} sortBy { p
p.withdraw.fold(Int.MaxValue, 0) - p.score
} sortBy { p
p.withdraw.fold(Int.MaxValue, 0) - p.score
}
private case class Builder(
@ -46,7 +46,7 @@ private[tournament] object Player {
prevWin: Boolean = false) {
def +(winner: Option[String]) = {
val (win, loss): Pair[Boolean, Boolean] = winner.fold(false -> false) { w =>
val (win, loss): Pair[Boolean, Boolean] = winner.fold(false -> false) { w
if (w == player.id) true -> false else false -> true
}
val newWinSeq = if (win) prevWin.fold(winSeq + 1, 1) else 0

View File

@ -2,13 +2,13 @@ package lila.tournament
import akka.actor.{ ActorRef, ActorSelection }
import akka.pattern.{ ask, pipe }
import org.joda.time.DateTime
import chess.{ Mode, Variant }
import com.github.nscala_time.time.Imports._
import org.joda.time.DateTime
import play.api.libs.json._
import scalaz.NonEmptyList
import actorApi._
import chess.{ Mode, Variant }
import lila.db.api._
import lila.game.{ Game, GameRepo }
import lila.hub.actorApi.lobby.ReloadTournaments
@ -73,7 +73,7 @@ private[tournament] final class TournamentApi(
$remove(created) >>
$remove.byId[Room](created.id) >>-
reloadSiteSocket >>-
lobbyReload
lobbyReload
}
def finish(started: Started): Fu[Tournament] = started.readyToFinish.fold({

View File

@ -12,6 +12,7 @@ case class User(
progress: Int,
perfs: Perfs,
count: Count,
artificial: Boolean = false,
troll: Boolean = false,
ipBan: Boolean = false,
enabled: Boolean,
@ -67,6 +68,7 @@ object User {
val username = "username"
val rating = "rating"
val progress = "progress"
val artificial = "artificial"
val perfs = "perfs"
val count = "count"
val troll = "troll"
@ -100,6 +102,7 @@ object User {
progress = r intD progress,
perfs = r.getO[Perfs](perfs) | Perfs.default,
count = r.get[Count](count),
artificial = r boolD artificial,
troll = r boolD troll,
ipBan = r boolD ipBan,
enabled = r bool enabled,
@ -118,6 +121,7 @@ object User {
progress -> w.int(o.progress),
perfs -> o.perfs,
count -> o.count,
artificial -> w.boolO(o.artificial),
troll -> w.boolO(o.troll),
ipBan -> w.boolO(o.ipBan),
enabled -> o.enabled,

View File

@ -156,7 +156,8 @@ trait UserRepo {
}
}
def nameExists(username: String): Fu[Boolean] = $count exists normalize(username)
def nameExists(username: String): Fu[Boolean] = idExists(normalize(username))
def idExists(id: String): Fu[Boolean] = $count exists id
def engineIds: Fu[Set[String]] = $primitive(Json.obj("engine" -> true), "_id")(_.asOpt[String]) map (_.toSet)
@ -184,6 +185,7 @@ trait UserRepo {
def updateTroll(user: User) = $update.field(user.id, "troll", user.troll)
def isEngine(id: ID): Fu[Boolean] = $count.exists($select(id) ++ Json.obj("engine" -> true))
def isArtificial(id: ID): Fu[Boolean] = $count.exists($select(id) ++ Json.obj("artificial" -> true))
def setRoles(id: ID, roles: List[String]) = $update.field(id, "roles", roles)
@ -257,6 +259,33 @@ trait UserRepo {
BSONFields.seenAt -> DateTime.now)
}
def artificialSetPassword(id: String, password: String) =
passwd(id, password) >> $update($select(id), $unset("artificial") ++ $set("enabled" -> true))
def insertArtificialUser(username: String, perfs: Perfs, progress: Int, createdAt: DateTime) = $insert bson {
val password = ornicar.scalalib.Random nextString 8
val salt = ornicar.scalalib.Random nextString 32
implicit def countHandler = Count.tube.handler
implicit def perfsHandler = Perfs.tube.handler
import lila.db.BSON.BSONJodaDateTimeHandler
import User.BSONFields
BSONDocument(
BSONFields.id -> normalize(username),
BSONFields.username -> username,
"artificial" -> true,
"password" -> hash(password, salt),
"salt" -> salt,
BSONFields.perfs -> perfs,
BSONFields.rating -> perfs.global.glicko.intRating,
BSONFields.progress -> progress,
BSONFields.count -> Count.default,
BSONFields.enabled -> false,
BSONFields.createdAt -> createdAt,
BSONFields.seenAt -> createdAt)
}
private def hash(pass: String, salt: String): String = "%s{%s}".format(pass, salt).sha1
private def hash512(pass: String, salt: String): String = "%s{%s}".format(pass, salt).sha512
}

View File

@ -2402,14 +2402,14 @@ var storage = {
function ratingLog(a) {
return Math.log(a / 150 + 1);
}
var rating = Math.max(800, Math.min(2000, e || 1200));
var rating = Math.max(800, Math.min(2800, e || 1500));
var ratio;
if (rating == 1200) {
if (rating == 1500) {
ratio = 0.25;
} else if (rating > 1200) {
ratio = 0.25 + (ratingLog(rating - 1200) / ratingLog(800)) * 3 / 4;
} else if (rating > 1500) {
ratio = 0.25 + (ratingLog(rating - 1500) / ratingLog(1300)) * 3 / 4;
} else {
ratio = 0.25 - (ratingLog(1200 - rating) / ratingLog(400)) / 4;
ratio = 0.25 - (ratingLog(1500 - rating) / ratingLog(500)) / 4;
}
return Math.round(ratio * 489);
}
@ -2463,7 +2463,7 @@ var storage = {
}
$('#hooks_chart').append(
_.map([1000, 1200, 1300, 1400, 1600, 1800, 2000], function(v) {
_.map([1000, 1200, 1400, 1500, 1600, 1800, 2000, 2200, 2400, 2600, 2800], function(v) {
var b = ratingY(v);
return '<span class="y label" style="bottom:' + (b + 5) + 'px">' + v + '</span>' +
'<div class="grid horiz" style="height:' + (b + 4) + 'px"></div>';

5
todo
View File

@ -102,6 +102,11 @@ wrong analysis = move 9 should be a blunder http://en.lichess.org/analyse/pf3ruf
check "continue from position" #lichess
from position + clock = fail (due to turns != played turns)
streamed export of PGN http://en.lichess.org/forum/lichess-feedback/download-all-games#5
fix clipping in lobby chart
visual sep under standard/chess960 ratings
progress over date ranges
get compiled js back to public/compiled
rating range restriction is broken on lobby
--- deploy