lila/app/controllers/LilaController.scala

295 lines
12 KiB
Scala
Raw Normal View History

2012-03-17 15:28:07 -06:00
package controllers
import ornicar.scalalib.Zero
2012-05-26 06:31:05 -06:00
import play.api.data.Form
import play.api.http._
import play.api.libs.iteratee.{ Iteratee, Enumerator }
2014-01-02 10:46:51 -07:00
import play.api.libs.json.{ Json, JsValue, JsObject, JsArray, Writes }
import play.api.mvc._, Results._
import play.api.mvc.WebSocket.FrameFormatter
2014-06-01 15:22:17 -06:00
import play.twirl.api.Html
2013-09-18 10:55:19 -06:00
import scalaz.Monoid
2014-01-02 10:46:51 -07:00
import lila.api.{ PageData, Context, HeaderContext, BodyContext }
import lila.app._
import lila.common.{ LilaCookie, HTTPRequest }
import lila.security.{ Permission, Granter, FingerprintedUser }
2014-07-11 00:31:37 -06:00
import lila.user.{ UserContext, User => UserModel }
2012-03-17 15:28:07 -06:00
2013-05-06 11:46:12 -06:00
private[controllers] trait LilaController
extends Controller
with ContentTypes
with RequestGetter
2013-10-28 12:23:37 -06:00
with ResponseWriter {
import Results._
protected implicit val LilaResultZero = Zero.instance[Result](Results.NotFound)
2013-09-18 10:55:19 -06:00
protected implicit val LilaHtmlMonoid = lila.app.templating.Environment.LilaHtmlMonoid
2013-09-18 10:55:19 -06:00
protected implicit final class LilaPimpedResult(result: Result) {
def fuccess = scala.concurrent.Future successful result
}
protected implicit def LilaHtmlToResult(content: Html): Result = Ok(content)
protected implicit def LilaFunitToResult(funit: Funit): Fu[Result] = funit inject Ok("ok")
2012-03-17 15:28:07 -06:00
implicit def lang(implicit req: RequestHeader) = Env.i18n.pool lang req
2012-05-12 12:06:02 -06:00
2015-06-19 09:36:31 -06:00
protected def NoCache(res: Result): Result = res.withHeaders(
CACHE_CONTROL -> "no-cache", PRAGMA -> "no-cache"
)
2014-02-17 02:12:19 -07:00
protected def Socket[A: FrameFormatter](f: Context => Fu[(Iteratee[A, _], Enumerator[A])]) =
WebSocket.tryAccept[A] { req => reqToCtx(req) flatMap f map scala.util.Right.apply }
2013-05-08 19:27:13 -06:00
protected def SocketEither[A: FrameFormatter](f: Context => Fu[Either[Result, (Iteratee[A, _], Enumerator[A])]]) =
2015-01-22 01:33:19 -07:00
WebSocket.tryAccept[A] { req => reqToCtx(req) flatMap f }
protected def SocketOption[A: FrameFormatter](f: Context => Fu[Option[(Iteratee[A, _], Enumerator[A])]]) =
WebSocket.tryAccept[A] { req =>
reqToCtx(req) flatMap f map {
case None => Left(NotFound(Json.obj("error" -> "socket resource not found")))
case Some(pair) => Right(pair)
}
}
2015-01-22 01:33:19 -07:00
protected def Open(f: Context => Fu[Result]): Action[Unit] =
Open(BodyParsers.parse.empty)(f)
protected def Open[A](p: BodyParser[A])(f: Context => Fu[Result]): Action[A] =
Action.async(p) { req =>
reqToCtx(req) flatMap { ctx =>
Env.i18n.requestHandler.forUser(req, ctx.me).fold(f(ctx))(fuccess)
}
}
protected def OpenBody(f: BodyContext => Fu[Result]): Action[AnyContent] =
2012-05-13 11:03:06 -06:00
OpenBody(BodyParsers.parse.anyContent)(f)
protected def OpenBody[A](p: BodyParser[A])(f: BodyContext => Fu[Result]): Action[A] =
2014-02-17 02:12:19 -07:00
Action.async(p)(req => reqToCtx(req) flatMap f)
2012-05-13 11:03:06 -06:00
protected def OpenNoCtx(f: RequestHeader => Fu[Result]): Action[AnyContent] =
Action.async(f)
protected def Auth(f: Context => UserModel => Fu[Result]): Action[Unit] =
Auth(BodyParsers.parse.empty)(f)
2013-03-14 12:16:36 -06:00
protected def Auth[A](p: BodyParser[A])(f: Context => UserModel => Fu[Result]): Action[A] =
2014-02-17 02:12:19 -07:00
Action.async(p) { req =>
2014-08-01 03:39:14 -06:00
reqToCtx(req) flatMap { implicit ctx =>
ctx.me.fold(authenticationFailed) { me =>
Env.i18n.requestHandler.forUser(req, ctx.me).fold(f(ctx)(me))(fuccess)
}
}
}
2013-03-14 12:16:36 -06:00
protected def AuthBody(f: BodyContext => UserModel => Fu[Result]): Action[AnyContent] =
2013-05-06 14:49:12 -06:00
AuthBody(BodyParsers.parse.anyContent)(f)
2013-03-14 12:16:36 -06:00
protected def AuthBody[A](p: BodyParser[A])(f: BodyContext => UserModel => Fu[Result]): Action[A] =
2014-02-17 02:12:19 -07:00
Action.async(p) { req =>
2014-08-01 03:39:14 -06:00
reqToCtx(req) flatMap { implicit ctx =>
ctx.me.fold(authenticationFailed)(me => f(ctx)(me))
2013-05-06 14:49:12 -06:00
}
}
2013-03-14 12:16:36 -06:00
protected def Secure(perm: Permission.type => Permission)(f: Context => UserModel => Fu[Result]): Action[AnyContent] =
2013-05-06 11:46:12 -06:00
Secure(perm(Permission))(f)
protected def Secure(perm: Permission)(f: Context => UserModel => Fu[Result]): Action[AnyContent] =
Secure(BodyParsers.parse.anyContent)(perm)(f)
2013-03-14 12:16:36 -06:00
protected def Secure[A](p: BodyParser[A])(perm: Permission)(f: Context => UserModel => Fu[Result]): Action[A] =
2014-02-17 02:12:19 -07:00
Auth(p) { implicit ctx =>
me =>
isGranted(perm).fold(f(ctx)(me), fuccess(authorizationFailed(ctx.req)))
2015-01-19 08:17:36 -07:00
}
protected def SecureBody[A](p: BodyParser[A])(perm: Permission)(f: BodyContext => UserModel => Fu[Result]): Action[A] =
AuthBody(p) { implicit ctx =>
me =>
isGranted(perm).fold(f(ctx)(me), fuccess(authorizationFailed(ctx.req)))
}
2013-03-14 12:16:36 -06:00
2015-01-19 08:37:42 -07:00
protected def SecureBody(perm: Permission.type => Permission)(f: BodyContext => UserModel => Fu[Result]): Action[AnyContent] =
SecureBody(BodyParsers.parse.anyContent)(perm(Permission))(f)
protected def Firewall[A <: Result](a: => Fu[A])(implicit ctx: Context): Fu[Result] =
2013-05-04 17:12:53 -06:00
Env.security.firewall.accepts(ctx.req) flatMap {
_ fold (a, {
fuccess { Redirect(routes.Lobby.home()) }
})
}
2012-06-15 06:05:58 -06:00
protected def NoEngine[A <: Result](a: => Fu[A])(implicit ctx: Context): Fu[Result] =
2015-06-21 12:40:19 -06:00
ctx.me.??(_.engine).fold(Forbidden(views.html.site.noEngine()).fuccess, a)
2012-09-22 05:47:47 -06:00
protected def NoBooster[A <: Result](a: => Fu[A])(implicit ctx: Context): Fu[Result] =
2015-06-21 12:40:19 -06:00
ctx.me.??(_.booster).fold(Forbidden(views.html.site.noBooster()).fuccess, a)
protected def NoLame[A <: Result](a: => Fu[A])(implicit ctx: Context): Fu[Result] =
NoEngine(NoBooster(a))
2015-04-26 04:08:13 -06:00
protected def NoPlayban(a: => Fu[Result])(implicit ctx: Context): Fu[Result] =
ctx.userId.??(Env.playban.api.currentBan) flatMap {
_.fold(a) { ban =>
negotiate(
html = Lobby.renderHome(Results.Forbidden),
api = _ => fuccess {
Forbidden(Json.obj(
"error" -> s"Banned from playing for ${ban.remainingMinutes} minutes. Reason: Too many aborts or unplayed games"
)) as JSON
}
)
}
}
2014-02-17 02:12:19 -07:00
protected def JsonOk[A: Writes](fua: Fu[A]) = fua map { a =>
2013-05-06 15:52:48 -06:00
Ok(Json toJson a) as JSON
}
2012-05-05 05:27:51 -06:00
2013-05-07 17:44:26 -06:00
protected def JsonOptionOk[A: Writes](fua: Fu[Option[A]])(implicit ctx: Context) = fua flatMap {
2014-02-17 02:12:19 -07:00
_.fold(notFound(ctx))(a => fuccess(Ok(Json toJson a) as JSON))
2013-05-07 17:44:26 -06:00
}
2014-02-17 02:12:19 -07:00
protected def JsonOptionFuOk[A, B: Writes](fua: Fu[Option[A]])(op: A => Fu[B])(implicit ctx: Context) =
fua flatMap { _.fold(notFound(ctx))(a => op(a) map { b => Ok(Json toJson b) as JSON }) }
2013-12-24 03:47:52 -07:00
2013-05-07 17:44:26 -06:00
protected def JsOk(fua: Fu[String], headers: (String, String)*) =
2014-02-17 02:12:19 -07:00
fua map { a => Ok(a) as JAVASCRIPT withHeaders (headers: _*) }
protected def FormResult[A](form: Form[A])(op: A => Fu[Result])(implicit req: Request[_]): Fu[Result] =
2013-04-09 12:58:34 -06:00
form.bindFromRequest.fold(
2014-02-17 02:12:19 -07:00
form => fuccess(BadRequest(form.errors mkString "\n")),
2013-04-09 12:58:34 -06:00
op)
2012-05-13 11:03:06 -06:00
protected def FormFuResult[A, B: Writeable: ContentTypeOf](form: Form[A])(err: Form[A] => Fu[B])(op: A => Fu[Result])(implicit req: Request[_]) =
2013-05-08 09:41:12 -06:00
form.bindFromRequest.fold(
2014-02-17 02:12:19 -07:00
form => err(form) map { BadRequest(_) },
data => op(data)
2013-05-08 09:41:12 -06:00
)
2013-05-08 20:00:13 -06:00
protected def FuRedirect(fua: Fu[Call]) = fua map { Redirect(_) }
protected def OptionOk[A, B: Writeable: ContentTypeOf](fua: Fu[Option[A]])(op: A => B)(implicit ctx: Context): Fu[Result] =
2014-02-17 02:12:19 -07:00
OptionFuOk(fua) { a => fuccess(op(a)) }
2012-05-17 08:49:47 -06:00
2014-02-17 02:12:19 -07:00
protected def OptionFuOk[A, B: Writeable: ContentTypeOf](fua: Fu[Option[A]])(op: A => Fu[B])(implicit ctx: Context) =
fua flatMap { _.fold(notFound(ctx))(a => op(a) map { Ok(_) }) }
2012-05-20 15:56:36 -06:00
2014-02-17 02:12:19 -07:00
protected def OptionFuRedirect[A](fua: Fu[Option[A]])(op: A => Fu[Call])(implicit ctx: Context) =
2013-05-12 09:02:45 -06:00
fua flatMap {
2014-02-17 02:12:19 -07:00
_.fold(notFound(ctx))(a => op(a) map { b => Redirect(b) })
2013-05-12 09:02:45 -06:00
}
2012-05-19 10:56:16 -06:00
2014-02-17 02:12:19 -07:00
protected def OptionFuRedirectUrl[A](fua: Fu[Option[A]])(op: A => Fu[String])(implicit ctx: Context) =
2013-05-06 14:49:12 -06:00
fua flatMap {
2014-02-17 02:12:19 -07:00
_.fold(notFound(ctx))(a => op(a) map { b => Redirect(b) })
2013-05-06 14:49:12 -06:00
}
2013-05-06 11:46:12 -06:00
protected def OptionResult[A](fua: Fu[Option[A]])(op: A => Result)(implicit ctx: Context) =
2014-02-17 02:12:19 -07:00
OptionFuResult(fua) { a => fuccess(op(a)) }
2012-12-18 08:58:19 -07:00
protected def OptionFuResult[A](fua: Fu[Option[A]])(op: A => Fu[Result])(implicit ctx: Context) =
2014-02-17 02:12:19 -07:00
fua flatMap { _.fold(notFound(ctx))(a => op(a)) }
2012-05-19 07:37:10 -06:00
2014-12-11 06:41:55 -07:00
def notFound(implicit ctx: Context): Fu[Result] = negotiate(
html =
if (HTTPRequest isSynchronousHttp ctx.req) Lobby renderHome Results.NotFound
else fuccess(Results.NotFound("Resource not found")),
api = _ => fuccess(Results.NotFound(Json.obj("error" -> "Resource not found")))
)
2012-05-15 17:31:57 -06:00
2015-07-24 15:40:50 -06:00
def notFoundJson(msg: String = "Not found"): Fu[Result] = fuccess {
NotFound(Json.obj("error" -> msg))
}
protected def notFoundReq(req: RequestHeader): Fu[Result] =
2014-02-17 02:12:19 -07:00
reqToCtx(req) flatMap (x => notFound(x))
2013-12-29 02:51:40 -07:00
2014-02-17 02:12:19 -07:00
protected def isGranted(permission: Permission.type => Permission)(implicit ctx: Context): Boolean =
isGranted(permission(Permission))
protected def isGranted(permission: Permission)(implicit ctx: Context): Boolean =
ctx.me ?? Granter(permission)
2014-08-01 03:39:14 -06:00
protected def authenticationFailed(implicit ctx: Context): Fu[Result] =
negotiate(
html = fuccess {
implicit val req = ctx.req
Redirect(routes.Auth.signup) withCookies LilaCookie.session(Env.security.api.AccessUri, req.uri)
},
2014-12-03 12:22:52 -07:00
api = _ => Unauthorized(Json.obj("error" -> "Login required")).fuccess
2014-08-01 03:39:14 -06:00
)
protected def authorizationFailed(req: RequestHeader): Result =
Forbidden("no permission")
protected def negotiate(html: => Fu[Result], api: Int => Fu[Result])(implicit ctx: Context): Fu[Result] =
(lila.api.Mobile.Api.requestVersion(ctx.req) match {
case Some(1) => api(1) map (_ as JSON)
case _ => html
}) map (_.withHeaders("Vary" -> "Accept"))
2014-05-09 12:01:33 -06:00
2014-01-28 02:57:59 -07:00
protected def reqToCtx(req: RequestHeader): Fu[HeaderContext] =
restoreUser(req) flatMap { d =>
val ctx = UserContext(req, d.map(_.user))
pageDataBuilder(ctx, d.??(_.hasFingerprint)) map { Context(ctx, _) }
2013-12-27 15:12:20 -07:00
}
2013-03-18 17:36:22 -06:00
protected def reqToCtx(req: Request[_]): Fu[BodyContext] =
restoreUser(req) flatMap { d =>
val ctx = UserContext(req, d.map(_.user))
pageDataBuilder(ctx, d.??(_.hasFingerprint)) map { Context(ctx, _) }
2013-12-27 15:12:20 -07:00
}
2013-03-18 17:36:22 -06:00
private def pageDataBuilder(ctx: UserContext, hasFingerprint: Boolean): Fu[PageData] =
2014-07-11 00:31:37 -06:00
ctx.me.fold(fuccess(PageData anon blindMode(ctx))) { me =>
2014-01-02 10:46:51 -07:00
val isPage = HTTPRequest.isSynchronousHttp(ctx.req)
2014-01-28 02:57:59 -07:00
(Env.pref.api getPref me) zip {
2014-01-02 10:46:51 -07:00
isPage ?? {
2013-12-29 14:22:29 -07:00
import lila.hub.actorApi.relation._
import akka.pattern.ask
import makeTimeout.short
2014-04-18 03:51:19 -06:00
(Env.hub.actor.relation ? GetOnlineFriends(me.id) map {
2014-04-16 16:01:24 -06:00
case OnlineFriends(users) => users
2014-05-31 15:36:27 -06:00
} recover { case _ => Nil }) zip
Env.team.api.nbRequests(me.id) zip
Env.message.api.unreadIds(me.id)
2014-01-02 10:46:51 -07:00
}
2013-12-29 14:22:29 -07:00
} map {
2014-05-31 15:36:27 -06:00
case (pref, ((friends, teamNbRequests), messageIds)) =>
PageData(friends, teamNbRequests, messageIds.size, pref,
blindMode = blindMode(ctx),
hasFingerprint = hasFingerprint)
2013-12-27 15:12:20 -07:00
}
}
2014-07-11 00:31:37 -06:00
private def blindMode(implicit ctx: UserContext) =
ctx.req.cookies.get(Env.api.Accessibility.blindCookieName) ?? { c =>
c.value.nonEmpty && c.value == Env.api.Accessibility.hash
}
2014-06-06 06:33:04 -06:00
private def restoreUser(req: RequestHeader): Fu[Option[FingerprintedUser]] =
Env.security.api restoreUser req addEffect {
_ ifTrue (HTTPRequest isSynchronousHttp req) foreach { d =>
2013-07-26 16:27:25 -06:00
val lang = Env.i18n.pool.lang(req).language
Env.current.bus.publish(lila.user.User.Active(d.user, lang), 'userActive)
}
2013-03-18 17:36:22 -06:00
}
2014-09-28 08:41:55 -06:00
protected def XhrOnly(res: => Fu[Result])(implicit ctx: Context) =
if (HTTPRequest isXhr ctx.req) res
else notFound
protected def Reasonable(page: Int, max: Int = 40)(result: => Fu[Result]): Fu[Result] =
2013-05-09 17:00:27 -06:00
(page < max).fold(result, BadRequest("resource too old").fuccess)
2015-04-10 02:47:00 -06:00
protected def NotForKids(f: => Fu[Result])(implicit ctx: Context) =
if (ctx.kid) notFound else f
2012-03-17 15:28:07 -06:00
}