typesafe ApiVersion
This commit is contained in:
parent
98cc5a8a32
commit
605f4a46b0
|
@ -15,10 +15,10 @@ object Api extends LilaController {
|
||||||
val api = lila.api.Mobile.Api
|
val api = lila.api.Mobile.Api
|
||||||
Ok(Json.obj(
|
Ok(Json.obj(
|
||||||
"api" -> Json.obj(
|
"api" -> Json.obj(
|
||||||
"current" -> api.currentVersion,
|
"current" -> api.currentVersion.value,
|
||||||
"olds" -> api.oldVersions.map { old =>
|
"olds" -> api.oldVersions.map { old =>
|
||||||
Json.obj(
|
Json.obj(
|
||||||
"version" -> old.version,
|
"version" -> old.version.value,
|
||||||
"deprecatedAt" -> old.deprecatedAt,
|
"deprecatedAt" -> old.deprecatedAt,
|
||||||
"unsupportedAt" -> old.unsupportedAt)
|
"unsupportedAt" -> old.unsupportedAt)
|
||||||
})
|
})
|
||||||
|
|
|
@ -12,7 +12,7 @@ import scalaz.Monoid
|
||||||
|
|
||||||
import lila.api.{ PageData, Context, HeaderContext, BodyContext, TokenBucket }
|
import lila.api.{ PageData, Context, HeaderContext, BodyContext, TokenBucket }
|
||||||
import lila.app._
|
import lila.app._
|
||||||
import lila.common.{ LilaCookie, HTTPRequest }
|
import lila.common.{ LilaCookie, HTTPRequest, ApiVersion }
|
||||||
import lila.notify.Notification.Notifies
|
import lila.notify.Notification.Notifies
|
||||||
import lila.security.{ Permission, Granter, FingerprintedUser }
|
import lila.security.{ Permission, Granter, FingerprintedUser }
|
||||||
import lila.user.{ UserContext, User => UserModel }
|
import lila.user.{ UserContext, User => UserModel }
|
||||||
|
@ -277,7 +277,7 @@ private[controllers] trait LilaController
|
||||||
res,
|
res,
|
||||||
res withCookies LilaCookie.makeSessionId(req))
|
res withCookies LilaCookie.makeSessionId(req))
|
||||||
|
|
||||||
protected def negotiate(html: => Fu[Result], api: Int => Fu[Result])(implicit ctx: Context): Fu[Result] =
|
protected def negotiate(html: => Fu[Result], api: ApiVersion => Fu[Result])(implicit ctx: Context): Fu[Result] =
|
||||||
(lila.api.Mobile.Api.requestVersion(ctx.req) match {
|
(lila.api.Mobile.Api.requestVersion(ctx.req) match {
|
||||||
case Some(v) => api(v) map (_ as JSON)
|
case Some(v) => api(v) map (_ as JSON)
|
||||||
case _ => html
|
case _ => html
|
||||||
|
|
|
@ -8,7 +8,7 @@ import play.twirl.api.Html
|
||||||
|
|
||||||
import lila.api.Context
|
import lila.api.Context
|
||||||
import lila.app._
|
import lila.app._
|
||||||
import lila.common.HTTPRequest
|
import lila.common.{ HTTPRequest, ApiVersion }
|
||||||
import lila.game.{ Pov, PlayerRef, GameRepo, Game => GameModel }
|
import lila.game.{ Pov, PlayerRef, GameRepo, Game => GameModel }
|
||||||
import lila.hub.actorApi.map.Tell
|
import lila.hub.actorApi.map.Tell
|
||||||
import lila.round.actorApi.round._
|
import lila.round.actorApi.round._
|
||||||
|
@ -31,7 +31,9 @@ object Round extends LilaController with TheftPrevention {
|
||||||
uid = uid,
|
uid = uid,
|
||||||
user = ctx.me,
|
user = ctx.me,
|
||||||
ip = ctx.ip,
|
ip = ctx.ip,
|
||||||
userTv = get("userTv"))
|
userTv = get("userTv"),
|
||||||
|
apiVersion = lila.api.Mobile.Api.currentVersion // yeah it should be in the URL
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,7 +43,7 @@ object Round extends LilaController with TheftPrevention {
|
||||||
if (isTheft(pov)) fuccess(Left(theftResponse))
|
if (isTheft(pov)) fuccess(Left(theftResponse))
|
||||||
else get("sri") match {
|
else get("sri") match {
|
||||||
case Some(uid) => requestAiMove(pov) >> env.socketHandler.player(
|
case Some(uid) => requestAiMove(pov) >> env.socketHandler.player(
|
||||||
pov, uid, ~get("ran"), ctx.me, ctx.ip
|
pov, uid, ~get("ran"), ctx.me, ctx.ip, ApiVersion(apiVersion)
|
||||||
) map Right.apply
|
) map Right.apply
|
||||||
case None => fuccess(Left(NotFound))
|
case None => fuccess(Left(NotFound))
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
@jsTag("challenge.js")
|
@jsTag("challenge.js")
|
||||||
@embedJs {
|
@embedJs {
|
||||||
lichess.startChallenge(document.getElementById('challenge'), {
|
lichess.startChallenge(document.getElementById('challenge'), {
|
||||||
socketUrl: '@routes.Challenge.websocket(c.id, apiVersion)',
|
socketUrl: '@routes.Challenge.websocket(c.id, apiVersion.value)',
|
||||||
xhrUrl: '@routes.Challenge.show(c.id)',
|
xhrUrl: '@routes.Challenge.show(c.id)',
|
||||||
owner: @owner,
|
owner: @owner,
|
||||||
data: @Html(toJson(json))
|
data: @Html(toJson(json))
|
||||||
|
|
|
@ -16,7 +16,7 @@ explorer: {
|
||||||
endpoint: "@explorerEndpoint",
|
endpoint: "@explorerEndpoint",
|
||||||
tablebaseEndpoint: "@tablebaseEndpoint"
|
tablebaseEndpoint: "@tablebaseEndpoint"
|
||||||
},
|
},
|
||||||
socketUrl: "@routes.Study.websocket(s.id, apiVersion)",
|
socketUrl: "@routes.Study.websocket(s.id, apiVersion.value)",
|
||||||
socketVersion: @socketVersion
|
socketVersion: @socketVersion
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,39 +4,41 @@ import org.joda.time.DateTime
|
||||||
import play.api.http.HeaderNames
|
import play.api.http.HeaderNames
|
||||||
import play.api.mvc.RequestHeader
|
import play.api.mvc.RequestHeader
|
||||||
|
|
||||||
|
import lila.common.ApiVersion
|
||||||
|
|
||||||
object Mobile {
|
object Mobile {
|
||||||
|
|
||||||
object Api {
|
object Api {
|
||||||
|
|
||||||
case class Old(
|
case class Old(
|
||||||
version: Int,
|
version: ApiVersion,
|
||||||
// date when a newer version was released
|
// date when a newer version was released
|
||||||
deprecatedAt: DateTime,
|
deprecatedAt: DateTime,
|
||||||
// date when the server stops accepting requests
|
// date when the server stops accepting requests
|
||||||
unsupportedAt: DateTime)
|
unsupportedAt: DateTime)
|
||||||
|
|
||||||
val currentVersion = 2
|
val currentVersion = ApiVersion(2)
|
||||||
|
|
||||||
val acceptedVersionNumbers = Set(1, 2)
|
val acceptedVersions: Set[ApiVersion] = Set(1, 2) map ApiVersion.apply
|
||||||
|
|
||||||
val oldVersions: List[Old] = List(
|
val oldVersions: List[Old] = List(
|
||||||
Old( // chat messages are html escaped
|
Old( // chat messages are html escaped
|
||||||
version = 1,
|
version = ApiVersion(1),
|
||||||
deprecatedAt = new DateTime("2016-08-13"),
|
deprecatedAt = new DateTime("2016-08-13"),
|
||||||
unsupportedAt = new DateTime("2016-11-13"))
|
unsupportedAt = new DateTime("2016-11-13"))
|
||||||
)
|
)
|
||||||
|
|
||||||
private val PathPattern = """^.+/socket/v(\d+)$""".r
|
private val PathPattern = """^.+/socket/v(\d+)$""".r
|
||||||
|
|
||||||
def requestVersion(req: RequestHeader): Option[Int] = {
|
def requestVersion(req: RequestHeader): Option[ApiVersion] = {
|
||||||
val accepts = ~req.headers.get(HeaderNames.ACCEPT)
|
val accepts = ~req.headers.get(HeaderNames.ACCEPT)
|
||||||
if (accepts contains "application/vnd.lichess.v2+json") Some(2)
|
if (accepts contains "application/vnd.lichess.v2+json") Some(ApiVersion(2))
|
||||||
else if (accepts contains "application/vnd.lichess.v1+json") Some(1)
|
else if (accepts contains "application/vnd.lichess.v1+json") Some(ApiVersion(1))
|
||||||
else req.path match {
|
else req.path match {
|
||||||
case PathPattern(version) => parseIntOption(version)
|
case PathPattern(version) => parseIntOption(version) map ApiVersion.apply
|
||||||
case _ => None
|
case _ => None
|
||||||
}
|
}
|
||||||
} filter acceptedVersionNumbers.contains
|
} filter acceptedVersions.contains
|
||||||
|
|
||||||
def requested(req: RequestHeader) = requestVersion(req).isDefined
|
def requested(req: RequestHeader) = requestVersion(req).isDefined
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,8 @@ package lila.api
|
||||||
import play.api.libs.json._
|
import play.api.libs.json._
|
||||||
|
|
||||||
import lila.analyse.{ JsonView => analysisJson, Analysis, Info }
|
import lila.analyse.{ JsonView => analysisJson, Analysis, Info }
|
||||||
import lila.common.LightUser
|
|
||||||
import lila.common.PimpedJson._
|
import lila.common.PimpedJson._
|
||||||
|
import lila.common.{ LightUser, ApiVersion }
|
||||||
import lila.game.{ Pov, Game, GameRepo }
|
import lila.game.{ Pov, Game, GameRepo }
|
||||||
import lila.pref.Pref
|
import lila.pref.Pref
|
||||||
import lila.round.{ JsonView, Forecast }
|
import lila.round.{ JsonView, Forecast }
|
||||||
|
@ -21,7 +21,7 @@ private[api] final class RoundApi(
|
||||||
getSimul: Simul.ID => Fu[Option[Simul]],
|
getSimul: Simul.ID => Fu[Option[Simul]],
|
||||||
lightUser: String => Option[LightUser]) {
|
lightUser: String => Option[LightUser]) {
|
||||||
|
|
||||||
def player(pov: Pov, apiVersion: Int)(implicit ctx: Context): Fu[JsObject] =
|
def player(pov: Pov, apiVersion: ApiVersion)(implicit ctx: Context): Fu[JsObject] =
|
||||||
GameRepo.initialFen(pov.game) flatMap { initialFen =>
|
GameRepo.initialFen(pov.game) flatMap { initialFen =>
|
||||||
jsonView.playerJson(pov, ctx.pref, apiVersion, ctx.me,
|
jsonView.playerJson(pov, ctx.pref, apiVersion, ctx.me,
|
||||||
withBlurs = ctx.me ?? Granter(_.ViewBlurs),
|
withBlurs = ctx.me ?? Granter(_.ViewBlurs),
|
||||||
|
@ -40,7 +40,7 @@ private[api] final class RoundApi(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def watcher(pov: Pov, apiVersion: Int, tv: Option[lila.round.OnTv],
|
def watcher(pov: Pov, apiVersion: ApiVersion, tv: Option[lila.round.OnTv],
|
||||||
initialFenO: Option[Option[String]] = None)(implicit ctx: Context): Fu[JsObject] =
|
initialFenO: Option[Option[String]] = None)(implicit ctx: Context): Fu[JsObject] =
|
||||||
initialFenO.fold(GameRepo initialFen pov.game)(fuccess) flatMap { initialFen =>
|
initialFenO.fold(GameRepo initialFen pov.game)(fuccess) flatMap { initialFen =>
|
||||||
jsonView.watcherJson(pov, ctx.pref, apiVersion, ctx.me, tv,
|
jsonView.watcherJson(pov, ctx.pref, apiVersion, ctx.me, tv,
|
||||||
|
@ -60,7 +60,7 @@ private[api] final class RoundApi(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def review(pov: Pov, apiVersion: Int, tv: Option[lila.round.OnTv],
|
def review(pov: Pov, apiVersion: ApiVersion, tv: Option[lila.round.OnTv],
|
||||||
analysis: Option[Analysis] = None,
|
analysis: Option[Analysis] = None,
|
||||||
initialFenO: Option[Option[String]] = None,
|
initialFenO: Option[Option[String]] = None,
|
||||||
withMoveTimes: Boolean = false,
|
withMoveTimes: Boolean = false,
|
||||||
|
|
|
@ -5,6 +5,7 @@ import akka.pattern.{ ask, pipe }
|
||||||
import play.api.libs.json.JsObject
|
import play.api.libs.json.JsObject
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
|
import lila.common.ApiVersion
|
||||||
import lila.analyse.Analysis
|
import lila.analyse.Analysis
|
||||||
import lila.game.Pov
|
import lila.game.Pov
|
||||||
import lila.pref.Pref
|
import lila.pref.Pref
|
||||||
|
@ -20,11 +21,11 @@ private[api] final class RoundApiBalancer(
|
||||||
|
|
||||||
implicit val timeout = makeTimeout seconds 20
|
implicit val timeout = makeTimeout seconds 20
|
||||||
|
|
||||||
case class Player(pov: Pov, apiVersion: Int, ctx: Context)
|
case class Player(pov: Pov, apiVersion: ApiVersion, ctx: Context)
|
||||||
case class Watcher(pov: Pov, apiVersion: Int, tv: Option[lila.round.OnTv],
|
case class Watcher(pov: Pov, apiVersion: ApiVersion, tv: Option[lila.round.OnTv],
|
||||||
initialFenO: Option[Option[String]] = None,
|
initialFenO: Option[Option[String]] = None,
|
||||||
ctx: Context)
|
ctx: Context)
|
||||||
case class Review(pov: Pov, apiVersion: Int, tv: Option[lila.round.OnTv],
|
case class Review(pov: Pov, apiVersion: ApiVersion, tv: Option[lila.round.OnTv],
|
||||||
analysis: Option[Analysis] = None,
|
analysis: Option[Analysis] = None,
|
||||||
initialFenO: Option[Option[String]] = None,
|
initialFenO: Option[Option[String]] = None,
|
||||||
withMoveTimes: Boolean = false,
|
withMoveTimes: Boolean = false,
|
||||||
|
@ -58,7 +59,7 @@ private[api] final class RoundApiBalancer(
|
||||||
|
|
||||||
import implementation._
|
import implementation._
|
||||||
|
|
||||||
def player(pov: Pov, apiVersion: Int)(implicit ctx: Context): Fu[JsObject] = {
|
def player(pov: Pov, apiVersion: ApiVersion)(implicit ctx: Context): Fu[JsObject] = {
|
||||||
router ? Player(pov, apiVersion, ctx) mapTo manifest[JsObject] addFailureEffect { e =>
|
router ? Player(pov, apiVersion, ctx) mapTo manifest[JsObject] addFailureEffect { e =>
|
||||||
logger.error(pov.toString, e)
|
logger.error(pov.toString, e)
|
||||||
}
|
}
|
||||||
|
@ -67,12 +68,12 @@ private[api] final class RoundApiBalancer(
|
||||||
.logIfSlow(500, logger) { _ => s"outer player $pov" }
|
.logIfSlow(500, logger) { _ => s"outer player $pov" }
|
||||||
.result
|
.result
|
||||||
|
|
||||||
def watcher(pov: Pov, apiVersion: Int, tv: Option[lila.round.OnTv],
|
def watcher(pov: Pov, apiVersion: ApiVersion, tv: Option[lila.round.OnTv],
|
||||||
initialFenO: Option[Option[String]] = None)(implicit ctx: Context): Fu[JsObject] = {
|
initialFenO: Option[Option[String]] = None)(implicit ctx: Context): Fu[JsObject] = {
|
||||||
router ? Watcher(pov, apiVersion, tv, initialFenO, ctx) mapTo manifest[JsObject]
|
router ? Watcher(pov, apiVersion, tv, initialFenO, ctx) mapTo manifest[JsObject]
|
||||||
}.mon(_.round.api.watcher)
|
}.mon(_.round.api.watcher)
|
||||||
|
|
||||||
def review(pov: Pov, apiVersion: Int, tv: Option[lila.round.OnTv],
|
def review(pov: Pov, apiVersion: ApiVersion, tv: Option[lila.round.OnTv],
|
||||||
analysis: Option[Analysis] = None,
|
analysis: Option[Analysis] = None,
|
||||||
initialFenO: Option[Option[String]] = None,
|
initialFenO: Option[Option[String]] = None,
|
||||||
withMoveTimes: Boolean = false,
|
withMoveTimes: Boolean = false,
|
||||||
|
|
|
@ -13,6 +13,10 @@ trait PackageObject extends Steroids with WithFuture {
|
||||||
def value: String
|
def value: String
|
||||||
override def toString = value
|
override def toString = value
|
||||||
}
|
}
|
||||||
|
trait IntValue extends Any {
|
||||||
|
def value: Int
|
||||||
|
override def toString = value.toString
|
||||||
|
}
|
||||||
|
|
||||||
def !![A](msg: String): Valid[A] = msg.failureNel[A]
|
def !![A](msg: String): Valid[A] = msg.failureNel[A]
|
||||||
|
|
||||||
|
|
3
modules/common/src/main/model.scala
Normal file
3
modules/common/src/main/model.scala
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
package lila.common
|
||||||
|
|
||||||
|
case class ApiVersion(value: Int) extends AnyVal with IntValue
|
|
@ -7,6 +7,7 @@ import org.apache.commons.lang3.StringEscapeUtils.escapeHtml4
|
||||||
import play.api.libs.json._
|
import play.api.libs.json._
|
||||||
|
|
||||||
import lila.common.PimpedJson._
|
import lila.common.PimpedJson._
|
||||||
|
import lila.common.ApiVersion
|
||||||
import lila.game.JsonView._
|
import lila.game.JsonView._
|
||||||
import lila.game.{ Pov, Game, PerfPicker, Source, GameRepo, CorrespondenceClock }
|
import lila.game.{ Pov, Game, PerfPicker, Source, GameRepo, CorrespondenceClock }
|
||||||
import lila.pref.Pref
|
import lila.pref.Pref
|
||||||
|
@ -34,7 +35,7 @@ final class JsonView(
|
||||||
def playerJson(
|
def playerJson(
|
||||||
pov: Pov,
|
pov: Pov,
|
||||||
pref: Pref,
|
pref: Pref,
|
||||||
apiVersion: Int,
|
apiVersion: ApiVersion,
|
||||||
playerUser: Option[User],
|
playerUser: Option[User],
|
||||||
initialFen: Option[String],
|
initialFen: Option[String],
|
||||||
withBlurs: Boolean): Fu[JsObject] =
|
withBlurs: Boolean): Fu[JsObject] =
|
||||||
|
@ -119,7 +120,7 @@ final class JsonView(
|
||||||
def watcherJson(
|
def watcherJson(
|
||||||
pov: Pov,
|
pov: Pov,
|
||||||
pref: Pref,
|
pref: Pref,
|
||||||
apiVersion: Int,
|
apiVersion: ApiVersion,
|
||||||
user: Option[User],
|
user: Option[User],
|
||||||
tv: Option[OnTv],
|
tv: Option[OnTv],
|
||||||
withBlurs: Boolean,
|
withBlurs: Boolean,
|
||||||
|
|
|
@ -141,9 +141,9 @@ private[round] final class Socket(
|
||||||
blackIsGone = blackIsGone)
|
blackIsGone = blackIsGone)
|
||||||
} pipeTo sender
|
} pipeTo sender
|
||||||
|
|
||||||
case Join(uid, user, color, playerId, ip, userTv) =>
|
case Join(uid, user, color, playerId, ip, userTv, apiVersion) =>
|
||||||
val (enumerator, channel) = Concurrent.broadcast[JsValue]
|
val (enumerator, channel) = Concurrent.broadcast[JsValue]
|
||||||
val member = Member(channel, user, color, playerId, ip, userTv = userTv)
|
val member = Member(channel, user, color, playerId, ip, userTv = userTv, apiVersion = apiVersion)
|
||||||
addMember(uid, member)
|
addMember(uid, member)
|
||||||
notifyCrowd
|
notifyCrowd
|
||||||
playerDo(color, _.ping)
|
playerDo(color, _.ping)
|
||||||
|
|
|
@ -10,6 +10,7 @@ import chess.format.Uci
|
||||||
import play.api.libs.json.{ JsObject, Json }
|
import play.api.libs.json.{ JsObject, Json }
|
||||||
|
|
||||||
import actorApi._, round._
|
import actorApi._, round._
|
||||||
|
import lila.common.ApiVersion
|
||||||
import lila.common.PimpedJson._
|
import lila.common.PimpedJson._
|
||||||
import lila.game.{ Game, Pov, PovRef, PlayerRef, GameRepo }
|
import lila.game.{ Game, Pov, PovRef, PlayerRef, GameRepo }
|
||||||
import lila.hub.actorApi.map._
|
import lila.hub.actorApi.map._
|
||||||
|
@ -105,9 +106,10 @@ private[round] final class SocketHandler(
|
||||||
uid: String,
|
uid: String,
|
||||||
user: Option[User],
|
user: Option[User],
|
||||||
ip: String,
|
ip: String,
|
||||||
userTv: Option[String]): Fu[Option[JsSocketHandler]] =
|
userTv: Option[String],
|
||||||
|
apiVersion: ApiVersion): Fu[Option[JsSocketHandler]] =
|
||||||
GameRepo.pov(gameId, colorName) flatMap {
|
GameRepo.pov(gameId, colorName) flatMap {
|
||||||
_ ?? { join(_, none, uid, "", user, ip, userTv = userTv) map some }
|
_ ?? { join(_, none, uid, "", user, ip, userTv = userTv, apiVersion) map some }
|
||||||
}
|
}
|
||||||
|
|
||||||
def player(
|
def player(
|
||||||
|
@ -115,8 +117,9 @@ private[round] final class SocketHandler(
|
||||||
uid: String,
|
uid: String,
|
||||||
token: String,
|
token: String,
|
||||||
user: Option[User],
|
user: Option[User],
|
||||||
ip: String): Fu[JsSocketHandler] =
|
ip: String,
|
||||||
join(pov, Some(pov.playerId), uid, token, user, ip, userTv = none)
|
apiVersion: ApiVersion): Fu[JsSocketHandler] =
|
||||||
|
join(pov, Some(pov.playerId), uid, token, user, ip, userTv = none, apiVersion)
|
||||||
|
|
||||||
private def join(
|
private def join(
|
||||||
pov: Pov,
|
pov: Pov,
|
||||||
|
@ -125,14 +128,16 @@ private[round] final class SocketHandler(
|
||||||
token: String,
|
token: String,
|
||||||
user: Option[User],
|
user: Option[User],
|
||||||
ip: String,
|
ip: String,
|
||||||
userTv: Option[String]): Fu[JsSocketHandler] = {
|
userTv: Option[String],
|
||||||
|
apiVersion: ApiVersion): Fu[JsSocketHandler] = {
|
||||||
val join = Join(
|
val join = Join(
|
||||||
uid = uid,
|
uid = uid,
|
||||||
user = user,
|
user = user,
|
||||||
color = pov.color,
|
color = pov.color,
|
||||||
playerId = playerId,
|
playerId = playerId,
|
||||||
ip = ip,
|
ip = ip,
|
||||||
userTv = userTv)
|
userTv = userTv,
|
||||||
|
apiVersion = apiVersion)
|
||||||
socketHub ? Get(pov.gameId) mapTo manifest[ActorRef] flatMap { socket =>
|
socketHub ? Get(pov.gameId) mapTo manifest[ActorRef] flatMap { socket =>
|
||||||
Handler(hub, socket, uid, join, user map (_.id)) {
|
Handler(hub, socket, uid, join, user map (_.id)) {
|
||||||
case Connected(enum, member) =>
|
case Connected(enum, member) =>
|
||||||
|
|
|
@ -7,6 +7,7 @@ import scala.concurrent.Promise
|
||||||
import chess.Color
|
import chess.Color
|
||||||
import chess.format.Uci
|
import chess.format.Uci
|
||||||
|
|
||||||
|
import lila.common.ApiVersion
|
||||||
import lila.game.{ Game, Event, PlayerRef }
|
import lila.game.{ Game, Event, PlayerRef }
|
||||||
import lila.socket.SocketMember
|
import lila.socket.SocketMember
|
||||||
import lila.user.User
|
import lila.user.User
|
||||||
|
@ -20,6 +21,7 @@ sealed trait Member extends SocketMember {
|
||||||
val troll: Boolean
|
val troll: Boolean
|
||||||
val ip: String
|
val ip: String
|
||||||
val userTv: Option[String]
|
val userTv: Option[String]
|
||||||
|
val apiVersion: ApiVersion
|
||||||
|
|
||||||
def owner = playerIdOption.isDefined
|
def owner = playerIdOption.isDefined
|
||||||
def watcher = !owner
|
def watcher = !owner
|
||||||
|
@ -34,11 +36,12 @@ object Member {
|
||||||
color: Color,
|
color: Color,
|
||||||
playerIdOption: Option[String],
|
playerIdOption: Option[String],
|
||||||
ip: String,
|
ip: String,
|
||||||
userTv: Option[String]): Member = {
|
userTv: Option[String],
|
||||||
|
apiVersion: ApiVersion): Member = {
|
||||||
val userId = user map (_.id)
|
val userId = user map (_.id)
|
||||||
val troll = user.??(_.troll)
|
val troll = user.??(_.troll)
|
||||||
playerIdOption.fold[Member](Watcher(channel, userId, color, troll, ip, userTv)) { playerId =>
|
playerIdOption.fold[Member](Watcher(channel, userId, color, troll, ip, userTv, apiVersion)) { playerId =>
|
||||||
Owner(channel, userId, playerId, color, troll, ip)
|
Owner(channel, userId, playerId, color, troll, ip, apiVersion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,7 +52,8 @@ case class Owner(
|
||||||
playerId: String,
|
playerId: String,
|
||||||
color: Color,
|
color: Color,
|
||||||
troll: Boolean,
|
troll: Boolean,
|
||||||
ip: String) extends Member {
|
ip: String,
|
||||||
|
apiVersion: ApiVersion) extends Member {
|
||||||
|
|
||||||
val playerIdOption = playerId.some
|
val playerIdOption = playerId.some
|
||||||
val userTv = none
|
val userTv = none
|
||||||
|
@ -61,7 +65,8 @@ case class Watcher(
|
||||||
color: Color,
|
color: Color,
|
||||||
troll: Boolean,
|
troll: Boolean,
|
||||||
ip: String,
|
ip: String,
|
||||||
userTv: Option[String]) extends Member {
|
userTv: Option[String],
|
||||||
|
apiVersion: ApiVersion) extends Member {
|
||||||
|
|
||||||
val playerIdOption = none
|
val playerIdOption = none
|
||||||
}
|
}
|
||||||
|
@ -72,7 +77,8 @@ case class Join(
|
||||||
color: Color,
|
color: Color,
|
||||||
playerId: Option[String],
|
playerId: Option[String],
|
||||||
ip: String,
|
ip: String,
|
||||||
userTv: Option[String])
|
userTv: Option[String],
|
||||||
|
apiVersion: ApiVersion)
|
||||||
case class Connected(enumerator: JsEnumerator, member: Member)
|
case class Connected(enumerator: JsEnumerator, member: Member)
|
||||||
case class Bye(color: Color)
|
case class Bye(color: Color)
|
||||||
case class IsGone(color: Color)
|
case class IsGone(color: Color)
|
||||||
|
|
|
@ -7,6 +7,7 @@ import play.api.data.Forms._
|
||||||
import play.api.mvc.RequestHeader
|
import play.api.mvc.RequestHeader
|
||||||
import reactivemongo.bson._
|
import reactivemongo.bson._
|
||||||
|
|
||||||
|
import lila.common.ApiVersion
|
||||||
import lila.db.BSON.BSONJodaDateTimeHandler
|
import lila.db.BSON.BSONJodaDateTimeHandler
|
||||||
import lila.db.dsl._
|
import lila.db.dsl._
|
||||||
import lila.user.{ User, UserRepo }
|
import lila.user.{ User, UserRepo }
|
||||||
|
@ -46,7 +47,7 @@ final class Api(
|
||||||
private def authenticateCandidate(candidate: Option[User.LoginCandidate])(username: String, password: String): Option[User] =
|
private def authenticateCandidate(candidate: Option[User.LoginCandidate])(username: String, password: String): Option[User] =
|
||||||
candidate ?? { _(password) }
|
candidate ?? { _(password) }
|
||||||
|
|
||||||
def saveAuthentication(userId: String, apiVersion: Option[Int])(implicit req: RequestHeader): Fu[String] =
|
def saveAuthentication(userId: String, apiVersion: Option[ApiVersion])(implicit req: RequestHeader): Fu[String] =
|
||||||
UserRepo mustConfirmEmail userId flatMap {
|
UserRepo mustConfirmEmail userId flatMap {
|
||||||
case true => fufail(Api MustConfirmEmail userId)
|
case true => fufail(Api MustConfirmEmail userId)
|
||||||
case false =>
|
case false =>
|
||||||
|
|
|
@ -6,10 +6,10 @@ import org.joda.time.DateTime
|
||||||
import play.api.mvc.RequestHeader
|
import play.api.mvc.RequestHeader
|
||||||
import reactivemongo.bson.Macros
|
import reactivemongo.bson.Macros
|
||||||
|
|
||||||
import lila.db.dsl._
|
import lila.common.{ HTTPRequest, ApiVersion }
|
||||||
import lila.db.BSON.BSONJodaDateTimeHandler
|
import lila.db.BSON.BSONJodaDateTimeHandler
|
||||||
|
import lila.db.dsl._
|
||||||
import lila.user.{ User, UserRepo }
|
import lila.user.{ User, UserRepo }
|
||||||
import lila.common.HTTPRequest
|
|
||||||
|
|
||||||
object Store {
|
object Store {
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ object Store {
|
||||||
sessionId: String,
|
sessionId: String,
|
||||||
userId: String,
|
userId: String,
|
||||||
req: RequestHeader,
|
req: RequestHeader,
|
||||||
apiVersion: Option[Int]): Funit =
|
apiVersion: Option[ApiVersion]): Funit =
|
||||||
coll.insert($doc(
|
coll.insert($doc(
|
||||||
"_id" -> sessionId,
|
"_id" -> sessionId,
|
||||||
"user" -> userId,
|
"user" -> userId,
|
||||||
|
@ -28,13 +28,16 @@ object Store {
|
||||||
"ua" -> HTTPRequest.userAgent(req).|("?"),
|
"ua" -> HTTPRequest.userAgent(req).|("?"),
|
||||||
"date" -> DateTime.now,
|
"date" -> DateTime.now,
|
||||||
"up" -> true,
|
"up" -> true,
|
||||||
"api" -> apiVersion
|
"api" -> apiVersion.map(_.value)
|
||||||
)).void
|
)).void
|
||||||
|
|
||||||
|
private val userIdProjection = $doc("user" -> true, "_id" -> false)
|
||||||
|
private val userIdFingerprintProjection = $doc("user" -> true, "fp" -> true, "_id" -> false)
|
||||||
|
|
||||||
def userId(sessionId: String): Fu[Option[String]] =
|
def userId(sessionId: String): Fu[Option[String]] =
|
||||||
coll.find(
|
coll.find(
|
||||||
$doc("_id" -> sessionId, "up" -> true),
|
$doc("_id" -> sessionId, "up" -> true),
|
||||||
$doc("user" -> true, "_id" -> false)
|
userIdProjection
|
||||||
).uno[Bdoc] map { _ flatMap (_.getAs[String]("user")) }
|
).uno[Bdoc] map { _ flatMap (_.getAs[String]("user")) }
|
||||||
|
|
||||||
case class UserIdAndFingerprint(user: String, fp: Option[String])
|
case class UserIdAndFingerprint(user: String, fp: Option[String])
|
||||||
|
@ -43,7 +46,7 @@ object Store {
|
||||||
def userIdAndFingerprint(sessionId: String): Fu[Option[UserIdAndFingerprint]] =
|
def userIdAndFingerprint(sessionId: String): Fu[Option[UserIdAndFingerprint]] =
|
||||||
coll.find(
|
coll.find(
|
||||||
$doc("_id" -> sessionId, "up" -> true),
|
$doc("_id" -> sessionId, "up" -> true),
|
||||||
$doc("user" -> true, "fp" -> true, "_id" -> false)
|
userIdFingerprintProjection
|
||||||
).uno[UserIdAndFingerprint]
|
).uno[UserIdAndFingerprint]
|
||||||
|
|
||||||
def delete(sessionId: String): Funit =
|
def delete(sessionId: String): Funit =
|
||||||
|
|
|
@ -5,6 +5,7 @@ import org.joda.time.DateTime
|
||||||
import reactivemongo.api._
|
import reactivemongo.api._
|
||||||
import reactivemongo.bson._
|
import reactivemongo.bson._
|
||||||
|
|
||||||
|
import lila.common.ApiVersion
|
||||||
import lila.db.BSON.BSONJodaDateTimeHandler
|
import lila.db.BSON.BSONJodaDateTimeHandler
|
||||||
import lila.db.dsl._
|
import lila.db.dsl._
|
||||||
import lila.rating.{ Glicko, Perf, PerfType }
|
import lila.rating.{ Glicko, Perf, PerfType }
|
||||||
|
@ -219,7 +220,7 @@ object UserRepo {
|
||||||
def getPasswordHash(id: ID): Fu[Option[String]] =
|
def getPasswordHash(id: ID): Fu[Option[String]] =
|
||||||
coll.primitiveOne[String]($id(id), "password")
|
coll.primitiveOne[String]($id(id), "password")
|
||||||
|
|
||||||
def create(username: String, password: String, email: Option[String], blind: Boolean, mobileApiVersion: Option[Int]): Fu[Option[User]] =
|
def create(username: String, password: String, email: Option[String], blind: Boolean, mobileApiVersion: Option[ApiVersion]): Fu[Option[User]] =
|
||||||
!nameExists(username) flatMap {
|
!nameExists(username) flatMap {
|
||||||
_ ?? {
|
_ ?? {
|
||||||
val doc = newUser(username, password, email, blind, mobileApiVersion) ++
|
val doc = newUser(username, password, email, blind, mobileApiVersion) ++
|
||||||
|
@ -347,7 +348,7 @@ object UserRepo {
|
||||||
|
|
||||||
def setEmailConfirmed(id: String): Funit = coll.update($id(id), $unset(F.mustConfirmEmail)).void
|
def setEmailConfirmed(id: String): Funit = coll.update($id(id), $unset(F.mustConfirmEmail)).void
|
||||||
|
|
||||||
private def newUser(username: String, password: String, email: Option[String], blind: Boolean, mobileApiVersion: Option[Int]) = {
|
private def newUser(username: String, password: String, email: Option[String], blind: Boolean, mobileApiVersion: Option[ApiVersion]) = {
|
||||||
|
|
||||||
val salt = ornicar.scalalib.Random nextStringUppercase 32
|
val salt = ornicar.scalalib.Random nextStringUppercase 32
|
||||||
implicit def countHandler = Count.countBSONHandler
|
implicit def countHandler = Count.countBSONHandler
|
||||||
|
@ -365,7 +366,7 @@ object UserRepo {
|
||||||
F.count -> Count.default,
|
F.count -> Count.default,
|
||||||
F.enabled -> true,
|
F.enabled -> true,
|
||||||
F.createdAt -> DateTime.now,
|
F.createdAt -> DateTime.now,
|
||||||
F.createdWithApiVersion -> mobileApiVersion,
|
F.createdWithApiVersion -> mobileApiVersion.map(_.value),
|
||||||
F.seenAt -> DateTime.now) ++ {
|
F.seenAt -> DateTime.now) ++ {
|
||||||
if (blind) $doc("blind" -> true) else $empty
|
if (blind) $doc("blind" -> true) else $empty
|
||||||
}
|
}
|
||||||
|
|
|
@ -2007,6 +2007,10 @@ body.base .crosstable .loss {
|
||||||
.crosstable .user {
|
.crosstable .user {
|
||||||
padding-left: 7px;
|
padding-left: 7px;
|
||||||
}
|
}
|
||||||
|
.crosstable .unavailable {
|
||||||
|
margin-top: 40px;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
.bookmark {
|
.bookmark {
|
||||||
float: right;
|
float: right;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
Loading…
Reference in a new issue