API to immediately accept a challenge
parent
de8169864f
commit
8d0b466418
|
@ -1,6 +1,7 @@
|
|||
package controllers
|
||||
|
||||
import play.api.mvc.Result
|
||||
import play.api.libs.json.Json
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import lila.api.Context
|
||||
|
@ -9,6 +10,7 @@ import lila.challenge.{ Challenge => ChallengeModel }
|
|||
import lila.common.{ HTTPRequest, IpAddress }
|
||||
import lila.game.{ AnonCookie, Pov }
|
||||
import lila.socket.Socket.SocketVersion
|
||||
import lila.user.{ User => UserModel }
|
||||
import views.html
|
||||
|
||||
final class Challenge(
|
||||
|
@ -193,41 +195,71 @@ final class Challenge(
|
|||
ChallengeUserRateLimit(me.id) {
|
||||
env.user.repo enabledById userId.toLowerCase flatMap {
|
||||
destUser =>
|
||||
destUser ?? { env.challenge.granter(me.some, _, config.perfType) } flatMap {
|
||||
case Some(denied) =>
|
||||
BadRequest(jsonError(lila.challenge.ChallengeDenied.translated(denied))).fuccess
|
||||
import lila.challenge.Challenge._
|
||||
val challenge = lila.challenge.Challenge.make(
|
||||
variant = config.variant,
|
||||
initialFen = config.position,
|
||||
timeControl = config.clock map { c =>
|
||||
TimeControl.Clock(c)
|
||||
} orElse config.days.map {
|
||||
TimeControl.Correspondence.apply
|
||||
} getOrElse TimeControl.Unlimited,
|
||||
mode = config.mode,
|
||||
color = config.color.name,
|
||||
challenger = Right(me),
|
||||
destUser = destUser,
|
||||
rematchOf = none
|
||||
)
|
||||
(destUser, config.acceptByToken) match {
|
||||
case (Some(dest), Some(strToken)) => apiChallengeAccept(dest, challenge, strToken)
|
||||
case _ =>
|
||||
import lila.challenge.Challenge._
|
||||
val challenge = lila.challenge.Challenge.make(
|
||||
variant = config.variant,
|
||||
initialFen = config.position,
|
||||
timeControl = config.clock map { c =>
|
||||
TimeControl.Clock(c)
|
||||
} orElse config.days.map {
|
||||
TimeControl.Correspondence.apply
|
||||
} getOrElse TimeControl.Unlimited,
|
||||
mode = config.mode,
|
||||
color = config.color.name,
|
||||
challenger = Right(me),
|
||||
destUser = destUser,
|
||||
rematchOf = none
|
||||
)
|
||||
(env.challenge.api create challenge) map {
|
||||
case true =>
|
||||
JsonOk(
|
||||
env.challenge.jsonView
|
||||
.show(challenge, SocketVersion(0), lila.challenge.Direction.Out.some)
|
||||
)
|
||||
case false =>
|
||||
BadRequest(jsonError("Challenge not created"))
|
||||
}
|
||||
} map (_ as JSON)
|
||||
destUser ?? { env.challenge.granter(me.some, _, config.perfType) } flatMap {
|
||||
case Some(denied) =>
|
||||
BadRequest(jsonError(lila.challenge.ChallengeDenied.translated(denied))).fuccess
|
||||
case _ =>
|
||||
(env.challenge.api create challenge) map {
|
||||
case true =>
|
||||
JsonOk(
|
||||
env.challenge.jsonView
|
||||
.show(challenge, SocketVersion(0), lila.challenge.Direction.Out.some)
|
||||
)
|
||||
case false =>
|
||||
BadRequest(jsonError("Challenge not created"))
|
||||
}
|
||||
} map (_ as JSON)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private def apiChallengeAccept(
|
||||
dest: UserModel,
|
||||
challenge: lila.challenge.Challenge,
|
||||
strToken: String
|
||||
) =
|
||||
env.security.api.oauthScoped(
|
||||
lila.oauth.AccessToken.Id(strToken),
|
||||
List(lila.oauth.OAuthScope.Challenge.Write)
|
||||
) flatMap {
|
||||
_.fold(
|
||||
err => BadRequest(jsonError(err.message)).fuccess,
|
||||
scoped =>
|
||||
if (scoped.user is dest)
|
||||
env.challenge.api.oauthAccept(dest, challenge) map {
|
||||
case None => BadRequest(jsonError("Couldn't create game"))
|
||||
case Some(g) =>
|
||||
Ok(
|
||||
Json.obj(
|
||||
"game" -> env.game.jsonView(g, challenge.initialFen)
|
||||
)
|
||||
)
|
||||
}
|
||||
else BadRequest(jsonError("dest and accept user don't match")).fuccess
|
||||
)
|
||||
}
|
||||
|
||||
def rematchOf(gameId: String) = Auth { implicit ctx => me =>
|
||||
OptionFuResult(env.game.gameRepo game gameId) { g =>
|
||||
Pov.opponentOfUserId(g, me.id).flatMap(_.userId) ?? env.user.repo.byId flatMap {
|
||||
|
|
|
@ -89,6 +89,9 @@ final class ChallengeApi(
|
|||
lila.common.Future.applySequentially(cs)(remove).void
|
||||
}
|
||||
|
||||
def oauthAccept(dest: User, challenge: Challenge): Fu[Option[Game]] =
|
||||
joiner(challenge, dest.some).map2(_.game)
|
||||
|
||||
private def isLimitedByMaxPlaying(c: Challenge) =
|
||||
if (c.hasClock) fuFalse
|
||||
else
|
||||
|
|
|
@ -21,19 +21,24 @@ final class OAuthServer(
|
|||
import OAuthServer._
|
||||
|
||||
def auth(req: RequestHeader, scopes: List[OAuthScope]): Fu[AuthResult] =
|
||||
reqToTokenId(req).fold[Fu[AuthResult]](fufail(MissingAuthorizationHeader)) { tokenId =>
|
||||
accessTokenCache.get(tokenId) orFailWith NoSuchToken flatMap {
|
||||
case at if scopes.nonEmpty && !scopes.exists(at.scopes.contains) => fufail(MissingScope(at.scopes))
|
||||
case at =>
|
||||
userRepo enabledById at.userId flatMap {
|
||||
case None => fufail(NoSuchUser)
|
||||
case Some(u) => fuccess(OAuthScope.Scoped(u, at.scopes))
|
||||
}
|
||||
} dmap Right.apply
|
||||
reqToTokenId(req).fold[Fu[AuthResult]](fufail(MissingAuthorizationHeader)) {
|
||||
auth(_, scopes)
|
||||
} recover {
|
||||
case e: AuthError => Left(e)
|
||||
}
|
||||
|
||||
def auth(tokenId: AccessToken.Id, scopes: List[OAuthScope]): Fu[AuthResult] =
|
||||
accessTokenCache.get(tokenId) orFailWith NoSuchToken flatMap {
|
||||
case at if scopes.nonEmpty && !scopes.exists(at.scopes.contains) => fufail(MissingScope(at.scopes))
|
||||
case at =>
|
||||
userRepo enabledById at.userId flatMap {
|
||||
case None => fufail(NoSuchUser)
|
||||
case Some(u) => fuccess(OAuthScope.Scoped(u, at.scopes))
|
||||
}
|
||||
} dmap Right.apply recover {
|
||||
case e: AuthError => Left(e)
|
||||
}
|
||||
|
||||
def fetchAppAuthor(req: RequestHeader): Fu[Option[User.ID]] =
|
||||
reqToTokenId(req) ?? { tokenId =>
|
||||
tokenColl {
|
||||
|
|
|
@ -13,7 +13,7 @@ import scala.concurrent.duration._
|
|||
import lila.common.{ ApiVersion, EmailAddress, IpAddress }
|
||||
import lila.db.BSON.BSONJodaDateTimeHandler
|
||||
import lila.db.dsl._
|
||||
import lila.oauth.OAuthServer
|
||||
import lila.oauth.{ AccessToken, OAuthServer }
|
||||
import lila.user.{ User, UserRepo }
|
||||
import User.LoginCandidate
|
||||
|
||||
|
@ -121,6 +121,15 @@ final class SecurityApi(
|
|||
case Some(server) => server.auth(req, scopes)
|
||||
}
|
||||
|
||||
def oauthScoped(
|
||||
tokenId: AccessToken.Id,
|
||||
scopes: List[lila.oauth.OAuthScope]
|
||||
): Fu[lila.oauth.OAuthServer.AuthResult] =
|
||||
tryOauthServer().flatMap {
|
||||
case None => fuccess(Left(OAuthServer.ServerOffline))
|
||||
case Some(server) => server.auth(tokenId, scopes)
|
||||
}
|
||||
|
||||
def locatedOpenSessions(userId: User.ID, nb: Int): Fu[List[LocatedSession]] =
|
||||
store.openSessions(userId, nb) map {
|
||||
_.map { session =>
|
||||
|
|
|
@ -13,12 +13,13 @@ final case class ApiConfig(
|
|||
days: Option[Int],
|
||||
rated: Boolean,
|
||||
color: Color,
|
||||
position: Option[FEN] = None
|
||||
position: Option[FEN] = None,
|
||||
acceptByToken: Option[String] = None
|
||||
) extends {
|
||||
|
||||
val strictFen = false
|
||||
|
||||
def >> = (variant.key.some, clock, days, rated, color.name.some, position.map(_.value)).some
|
||||
def >> = (variant.key.some, clock, days, rated, color.name.some, position.map(_.value), acceptByToken).some
|
||||
|
||||
def perfType: Option[PerfType] = PerfPicker.perfType(chess.Speed(clock), variant, days)
|
||||
|
||||
|
@ -41,7 +42,8 @@ object ApiConfig extends BaseHumanConfig {
|
|||
d: Option[Int],
|
||||
r: Boolean,
|
||||
c: Option[String],
|
||||
pos: Option[String]
|
||||
pos: Option[String],
|
||||
tok: Option[String]
|
||||
) =
|
||||
new ApiConfig(
|
||||
variant = chess.variant.Variant.orDefault(~v),
|
||||
|
@ -49,6 +51,7 @@ object ApiConfig extends BaseHumanConfig {
|
|||
days = d,
|
||||
rated = r,
|
||||
color = Color.orDefault(~c),
|
||||
position = pos map FEN
|
||||
position = pos map FEN,
|
||||
acceptByToken = tok
|
||||
)
|
||||
}
|
||||
|
|
|
@ -132,10 +132,11 @@ final class FormFactory(
|
|||
"increment" -> increment
|
||||
)(chess.Clock.Config.apply)(chess.Clock.Config.unapply)
|
||||
),
|
||||
"days" -> optional(days),
|
||||
"rated" -> boolean,
|
||||
"color" -> optional(color),
|
||||
"fen" -> fen
|
||||
"days" -> optional(days),
|
||||
"rated" -> boolean,
|
||||
"color" -> optional(color),
|
||||
"fen" -> fen,
|
||||
"acceptByToken" -> optional(nonEmptyText)
|
||||
)(ApiConfig.<<)(_.>>).verifying("invalidFen", _.validFen)
|
||||
)
|
||||
|
||||
|
|
Loading…
Reference in New Issue