implement /api/challenge/{username} endpoint - closes #4771

pull/4762/head
Thibault Duplessis 2018-12-11 17:06:45 +07:00
parent e5167f7116
commit 419c977d42
7 changed files with 150 additions and 52 deletions

View File

@ -147,6 +147,43 @@ object Challenge extends LilaController {
}
}
def apiCreate(userId: String) = ScopedBody() { implicit req => me =>
implicit val lang = lila.i18n.I18nLangPicker(req, me.some)
Setup.PostRateLimit(HTTPRequest lastRemoteAddress req) {
Env.setup.forms.api.bindFromRequest.fold(
jsonFormErrorDefaultLang,
config => UserRepo enabledById userId flatMap { destUser =>
destUser ?? { Env.challenge.granter(me.some, _, config.perfType) } flatMap {
case Some(denied) =>
BadRequest(jsonError(lila.challenge.ChallengeDenied.translated(denied))).fuccess
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 =>
Ok(env.jsonView.show(challenge, SocketVersion(0), lila.challenge.Direction.Out.some))
case false =>
BadRequest(jsonError("Challenge not created"))
}
}
}
)
}
}
def rematchOf(gameId: String) = Auth { implicit ctx => me =>
OptionFuResult(GameRepo game gameId) { g =>
Pov.opponentOfUserId(g, me.id).flatMap(_.userId) ?? UserRepo.byId flatMap {

View File

@ -20,7 +20,7 @@ object Setup extends LilaController with TheftPrevention {
private def env = Env.setup
private val PostRateLimit = new lila.memo.RateLimit[IpAddress](5, 1 minute,
private[controllers] val PostRateLimit = new lila.memo.RateLimit[IpAddress](5, 1 minute,
name = "setup post",
key = "setup_post")
@ -66,47 +66,46 @@ object Setup extends LilaController with TheftPrevention {
err => negotiate(
html = Lobby.renderHome(Results.BadRequest),
api = _ => jsonFormError(err)
), {
case config => userId ?? UserRepo.byId flatMap { destUser =>
destUser ?? { Env.challenge.granter(ctx.me, _, config.perfType) } flatMap {
case Some(denied) =>
val message = lila.challenge.ChallengeDenied.translated(denied)
negotiate(
html = BadRequest(message).fuccess,
api = _ => BadRequest(jsonError(message)).fuccess
)
case None =>
import lila.challenge.Challenge._
val challenge = lila.challenge.Challenge.make(
variant = config.variant,
initialFen = config.fen,
timeControl = config.makeClock map { c =>
TimeControl.Clock(c)
} orElse config.makeDaysPerTurn.map {
TimeControl.Correspondence.apply
} getOrElse TimeControl.Unlimited,
mode = config.mode,
color = config.color.name,
challenger = (ctx.me, HTTPRequest sid req) match {
case (Some(user), _) => Right(user)
case (_, Some(sid)) => Left(sid)
case _ => Left("no_sid")
},
destUser = destUser,
rematchOf = none
)
env.processor.saveFriendConfig(config) >>
(Env.challenge.api create challenge) flatMap {
case true => negotiate(
html = fuccess(Redirect(routes.Round.watcher(challenge.id, "white"))),
api = _ => Challenge showChallenge challenge
)
case false => negotiate(
html = fuccess(Redirect(routes.Lobby.home)),
api = _ => fuccess(BadRequest(jsonError("Challenge not created")))
)
}
}
),
config => userId ?? UserRepo.enabledById flatMap { destUser =>
destUser ?? { Env.challenge.granter(ctx.me, _, config.perfType) } flatMap {
case Some(denied) =>
val message = lila.challenge.ChallengeDenied.translated(denied)
negotiate(
html = BadRequest(message).fuccess,
api = _ => BadRequest(jsonError(message)).fuccess
)
case None =>
import lila.challenge.Challenge._
val challenge = lila.challenge.Challenge.make(
variant = config.variant,
initialFen = config.fen,
timeControl = config.makeClock map { c =>
TimeControl.Clock(c)
} orElse config.makeDaysPerTurn.map {
TimeControl.Correspondence.apply
} getOrElse TimeControl.Unlimited,
mode = config.mode,
color = config.color.name,
challenger = (ctx.me, HTTPRequest sid req) match {
case (Some(user), _) => Right(user)
case (_, Some(sid)) => Left(sid)
case _ => Left("no_sid")
},
destUser = destUser,
rematchOf = none
)
env.processor.saveFriendConfig(config) >>
(Env.challenge.api create challenge) flatMap {
case true => negotiate(
html = fuccess(Redirect(routes.Round.watcher(challenge.id, "white"))),
api = _ => Challenge showChallenge challenge
)
case false => negotiate(
html = fuccess(Redirect(routes.Lobby.home)),
api = _ => fuccess(BadRequest(jsonError("Challenge not created")))
)
}
}
}
)

View File

@ -512,6 +512,7 @@ GET /api/account/email controllers.Account.apiEmail
GET /api/account/kid controllers.Account.apiKid
POST /api/account/kid controllers.Account.apiKidPost
GET /api/account/preferences controllers.Pref.apiGet
POST /api/challenge/:user controllers.Challenge.apiCreate(user: String)
POST /api/challenge/$id<\w{8}>/accept controllers.Challenge.apiAccept(id: String)
POST /api/challenge/$id<\w{8}>/decline controllers.Challenge.apiDecline(id: String)

View File

@ -4,7 +4,7 @@ import lila.i18n.I18nKeys
import lila.pref.Pref
import lila.rating.PerfType
import lila.relation.{ Relation, Block, Follow }
import lila.user.{ User, UserContext }
import lila.user.User
case class ChallengeDenied(dest: User, reason: ChallengeDenied.Reason)
@ -21,7 +21,7 @@ object ChallengeDenied {
case object FriendsOnly extends Reason
}
def translated(d: ChallengeDenied)(implicit ctx: UserContext) = d.reason match {
def translated(d: ChallengeDenied)(implicit lang: lila.common.Lang) = d.reason match {
case Reason.YouAreAnon => I18nKeys.registerToSendChallenges.txt()
case Reason.YouAreBlocked => I18nKeys.youCannotChallengeX.txt(d.dest.titleUsername)
case Reason.TheyDontAcceptChallenges => I18nKeys.xDoesNotAcceptChallenges.txt(d.dest.titleUsername)

View File

@ -2,7 +2,6 @@ package lila.i18n
import play.twirl.api.Html
import lila.user.UserContext
import lila.common.Lang
sealed trait I18nKey {
@ -20,19 +19,19 @@ sealed trait I18nKey {
/* Implicit context convenience functions */
// literal
def apply(args: Any*)(implicit ctx: UserContext): Html = literalHtmlTo(ctx.lang, args)
def apply(args: Any*)(implicit lang: Lang): Html = literalHtmlTo(lang, args)
def plural(count: Count, args: Any*)(implicit ctx: UserContext): Html = pluralHtmlTo(ctx.lang, count, args)
def plural(count: Count, args: Any*)(implicit lang: Lang): Html = pluralHtmlTo(lang, count, args)
// literalTxt
def txt(args: Any*)(implicit ctx: UserContext): String = literalTxtTo(ctx.lang, args)
def txt(args: Any*)(implicit lang: Lang): String = literalTxtTo(lang, args)
def pluralTxt(count: Count, args: Any*)(implicit ctx: UserContext): String = pluralTxtTo(ctx.lang, count, args)
def pluralTxt(count: Count, args: Any*)(implicit lang: Lang): String = pluralTxtTo(lang, count, args)
// reuses the count as the single argument
// allows `plural(nb)` instead of `plural(nb, nb)`
def pluralSame(count: Int)(implicit ctx: UserContext): Html = plural(count, count)
def pluralSameTxt(count: Int)(implicit ctx: UserContext): String = pluralTxt(count, count)
def pluralSame(count: Int)(implicit lang: Lang): Html = plural(count, count)
def pluralSameTxt(count: Int)(implicit lang: Lang): String = pluralTxt(count, count)
}
final class Translated(val key: String, val db: I18nDb.Ref) extends I18nKey {

View File

@ -0,0 +1,47 @@
package lila.setup
import scala.collection.breakOut
import chess.Clock
import chess.format.{ FEN, Forsyth }
import chess.variant.FromPosition
import lila.lobby.Color
import lila.rating.PerfType
import lila.game.PerfPicker
case class ApiConfig(
variant: chess.variant.Variant,
clock: Option[Clock.Config],
days: Option[Int],
rated: Boolean,
color: Color,
position: Option[FEN] = None
) extends {
val strictFen = false
def >> = (variant.key.some, clock, days, rated, color.name.some, position.map(_.value)).some
def perfType: Option[PerfType] = PerfPicker.perfType(chess.Speed(clock), variant, days)
def validFen = variant != FromPosition || {
position ?? { f => ~(Forsyth <<< f.value).map(_.situation playable strictFen) }
}
def mode = chess.Mode(rated)
}
object ApiConfig extends BaseHumanConfig {
lazy val clockLimitSeconds: Set[Int] = Set(0, 15, 30, 45, 60, 90) ++ (2 to 180).map(60*)(breakOut)
def <<(v: Option[String], cl: Option[Clock.Config], d: Option[Int], r: Boolean, c: Option[String], pos: Option[String]) =
new ApiConfig(
variant = chess.variant.Variant.orDefault(~v),
clock = cl,
days = d,
rated = r,
color = Color.orDefault(~c),
position = pos map FEN
)
}

View File

@ -1,6 +1,7 @@
package lila.setup
import chess.format.FEN
import chess.variant.Variant
import lila.lobby.Color
import lila.user.UserContext
import play.api.data._
@ -83,7 +84,7 @@ private[setup] final class FormFactory {
"days" -> days,
"mode" -> mode(ctx.isAuth),
"ratingRange" -> optional(ratingRange),
"color" -> text.verifying(Color.names contains _)
"color" -> color
)(HookConfig.<<)(_.>>)
.verifying("Invalid clock", _.validClock)
.verifying("Can't create rated unlimited in lobby", _.noRatedUnlimited)
@ -91,6 +92,20 @@ private[setup] final class FormFactory {
def hookConfig(implicit ctx: UserContext): Fu[HookConfig] = savedConfig map (_.hook)
lazy val api = Form(
mapping(
"variant" -> optional(text.verifying(Variant.byKey.contains _)),
"clock" -> optional(mapping(
"limit" -> number.verifying(ApiConfig.clockLimitSeconds.contains _),
"increment" -> increment
)(chess.Clock.Config.apply)(chess.Clock.Config.unapply)),
"days" -> optional(days),
"rated" -> boolean,
"color" -> optional(color),
"fen" -> fen
)(ApiConfig.<<)(_.>>).verifying("invalidFen", _.validFen)
)
def savedConfig(implicit ctx: UserContext): Fu[UserConfig] =
ctx.me.fold(AnonConfigRepo config ctx.req)(UserConfigRepo.config)
}