User and security
This commit is contained in:
parent
a1914fbe40
commit
58946f8177
|
@ -2,7 +2,7 @@ package controllers
|
|||
|
||||
import lila._
|
||||
import user.{ User ⇒ UserModel }
|
||||
import security.{ AuthConfigImpl }
|
||||
import security.{ AuthConfigImpl, Permission }
|
||||
import http.{ Context, BodyContext, HttpEnvironment }
|
||||
import core.Global
|
||||
|
||||
|
@ -46,6 +46,33 @@ trait LilaController
|
|||
def OpenBody[A](p: BodyParser[A])(f: BodyContext ⇒ Result): Action[A] =
|
||||
Action(p)(req ⇒ f(reqToCtx(req)))
|
||||
|
||||
def Auth(f: Context ⇒ User ⇒ Result): Action[AnyContent] =
|
||||
Auth(BodyParsers.parse.anyContent)(f)
|
||||
|
||||
def Auth[A](p: BodyParser[A])(f: Context ⇒ User ⇒ Result): Action[A] =
|
||||
Action(p)(req ⇒ {
|
||||
val ctx = reqToCtx(req)
|
||||
ctx.me.fold(me ⇒ f(ctx)(me), authenticationFailed(ctx.req))
|
||||
})
|
||||
|
||||
def AuthBody(f: BodyContext ⇒ User ⇒ Result): Action[AnyContent] =
|
||||
AuthBody(BodyParsers.parse.anyContent)(f)
|
||||
|
||||
def AuthBody[A](p: BodyParser[A])(f: BodyContext ⇒ User ⇒ Result): Action[A] =
|
||||
Action(p)(req ⇒ {
|
||||
val ctx = reqToCtx(req)
|
||||
ctx.me.fold(me ⇒ f(ctx)(me), authenticationFailed(ctx.req))
|
||||
})
|
||||
|
||||
def Secure(perm: Permission)(f: Context ⇒ User ⇒ Result): Action[AnyContent] =
|
||||
Secure(BodyParsers.parse.anyContent)(perm)(f)
|
||||
|
||||
def Secure[A](p: BodyParser[A])(perm: Permission)(f: Context ⇒ User ⇒ Result): Action[A] =
|
||||
Auth(p) { ctx ⇒
|
||||
me ⇒
|
||||
ctx.isGranted(perm).fold(f(ctx)(me), authorizationFailed(ctx.req))
|
||||
}
|
||||
|
||||
def JsonOk(map: Map[String, Any]) = Ok(toJson(map)) as JSON
|
||||
|
||||
def JsonOk(list: List[String]) = Ok(Json generate list) as JSON
|
||||
|
@ -133,6 +160,11 @@ trait LilaController
|
|||
implicit def ctoInt: ContentTypeOf[Int] =
|
||||
ContentTypeOf[Int](Some(ContentTypes.TEXT))
|
||||
|
||||
implicit def wOptionString: Writeable[Option[String]] =
|
||||
Writeable[Option[String]](i ⇒ Codec toUTF8 i.getOrElse(""))
|
||||
implicit def ctoOptionString: ContentTypeOf[Option[String]] =
|
||||
ContentTypeOf[Option[String]](Some(ContentTypes.TEXT))
|
||||
|
||||
implicit def richForm[A](form: Form[A]) = new {
|
||||
def toValid: Valid[A] = form.fold(
|
||||
form ⇒ failure(nel("Invalid form", form.errors.map(_.toString).toList)),
|
||||
|
|
|
@ -2,6 +2,7 @@ package controllers
|
|||
|
||||
import lila._
|
||||
import views._
|
||||
import security.Permission
|
||||
|
||||
import play.api._
|
||||
import play.api.mvc._
|
||||
|
@ -13,23 +14,29 @@ object User extends LilaController {
|
|||
def userRepo = env.user.userRepo
|
||||
def paginator = env.user.paginator
|
||||
def gamePaginator = env.game.paginator
|
||||
def forms = user.DataForm
|
||||
|
||||
def show(username: String) = showMode(username, "all", 1)
|
||||
def show(username: String) = showFilter(username, "all", 1)
|
||||
|
||||
def showMode(username: String, mode: String, page: Int) = Open { implicit ctx ⇒
|
||||
IOptionIOk(userRepo byUsername username) { user ⇒
|
||||
user.enabled.fold(
|
||||
env.user.userInfo(user, ctx) map { info ⇒
|
||||
html.user.show(
|
||||
u = user,
|
||||
info = info,
|
||||
mode = mode,
|
||||
games = gamePaginator.userAll(user, page))
|
||||
def showFilter(username: String, filterName: String, page: Int) = Open { implicit ctx ⇒
|
||||
IOptionIOk(userRepo byUsername username) { u ⇒
|
||||
u.enabled.fold(
|
||||
env.user.userInfo(u, ctx) map { info ⇒
|
||||
val filters = user.GameFilterMenu(info, ctx.me)
|
||||
val filter = filters(filterName)
|
||||
val games = gamePaginator.userAll(u, page)
|
||||
(u.some == ctx.me).fold(
|
||||
html.user.home(u = u, info = info, games = games, filters = filters, filter = filter),
|
||||
html.user.show(u = u, info = info, games = games, filters = filters, filter = filter)
|
||||
)
|
||||
},
|
||||
io(html.user.disabled(user)))
|
||||
io(html.user.disabled(u)))
|
||||
}
|
||||
}
|
||||
|
||||
def showTemplate(user: User, me: Option[User]) =
|
||||
(user.some == me).fold(html.user.show, html.user.home)
|
||||
|
||||
def list(page: Int) = Open { implicit ctx ⇒
|
||||
IOk(onlineUsers map { html.user.list(paginator elo page, _) })
|
||||
}
|
||||
|
@ -45,6 +52,43 @@ object User extends LilaController {
|
|||
)
|
||||
}
|
||||
|
||||
val getBio = Auth { ctx ⇒ me ⇒ Ok(me.bio) }
|
||||
|
||||
val setBio = AuthBody { ctx ⇒
|
||||
me ⇒
|
||||
implicit val req = ctx.body
|
||||
IORedirect(forms.bio.bindFromRequest.fold(
|
||||
f ⇒ putStrLn(f.errors.toString) map { _ ⇒ routes.User show me.username },
|
||||
bio ⇒ userRepo.setBio(me, bio) map { _ ⇒ routes.User show me.username }
|
||||
))
|
||||
}
|
||||
|
||||
val close = Auth { implicit ctx ⇒
|
||||
me ⇒
|
||||
Ok(html.user.close(me))
|
||||
}
|
||||
|
||||
val closeConfirm = Auth { ctx ⇒
|
||||
me ⇒
|
||||
IORedirect {
|
||||
userRepo disable me map { _ ⇒ routes.User show me.username }
|
||||
}
|
||||
}
|
||||
|
||||
def engine(username: String) = Secure(Permission.MarkEngine) { _ ⇒
|
||||
_ ⇒
|
||||
IORedirect {
|
||||
userRepo toggleEngine username map { _ ⇒ routes.User show username }
|
||||
}
|
||||
}
|
||||
|
||||
def mute(username: String) = Secure(Permission.MutePlayer) { _ ⇒
|
||||
_ ⇒
|
||||
IORedirect {
|
||||
userRepo toggleMute username map { _ ⇒ routes.User show username }
|
||||
}
|
||||
}
|
||||
|
||||
val signUp = TODO
|
||||
|
||||
val stats = TODO
|
||||
|
|
|
@ -153,19 +153,26 @@ class GameRepo(collection: MongoCollection)
|
|||
|
||||
def countDrawBy(user: User): IO[Int] = io {
|
||||
count(
|
||||
("status" $in List(Status.Draw.id, Status.Stalemate.id)) ++
|
||||
("userIds" -> user.id.toString)
|
||||
("status" $in List(Status.Draw.id, Status.Stalemate.id)) ++
|
||||
("userIds" -> user.id.toString)
|
||||
).toInt
|
||||
}
|
||||
|
||||
def countLossBy(user: User): IO[Int] = io {
|
||||
count(
|
||||
("status" $in List(Status.Mate.id, Status.Resign.id, Status.Outoftime.id, Status.Timeout.id)) ++
|
||||
("userIds" -> user.id.toString) ++
|
||||
("winnerUserId" $ne user.id.toString)
|
||||
("status" $in List(Status.Mate.id, Status.Resign.id, Status.Outoftime.id, Status.Timeout.id)) ++
|
||||
("userIds" -> user.id.toString) ++
|
||||
("winnerUserId" $ne user.id.toString)
|
||||
).toInt
|
||||
}
|
||||
|
||||
def countOpponents(user1: User, user2: User): IO[Int] = io {
|
||||
count(opponentsQuery(user1, user2)).toInt
|
||||
}
|
||||
|
||||
def opponentsQuery(user1: User, user2: User) =
|
||||
"userIds" $all List(user1.id.toString, user2.id.toString)
|
||||
|
||||
def recentGames(limit: Int): IO[List[DbGame]] = io {
|
||||
find(DBObject("status" -> Status.Started.id))
|
||||
.sort(DBObject("updatedAt" -> -1))
|
||||
|
|
|
@ -2,6 +2,7 @@ package lila
|
|||
package http
|
||||
|
||||
import user.User
|
||||
import security.{ Permission, Granter }
|
||||
|
||||
import play.api.mvc.{ Request, RequestHeader }
|
||||
|
||||
|
@ -10,6 +11,9 @@ sealed abstract class Context(val req: RequestHeader, val me: Option[User]) {
|
|||
def isAuth = me.isDefined
|
||||
|
||||
def canSeeChat = me.fold(m ⇒ !m.isChatBan, false)
|
||||
|
||||
def isGranted(permission: Permission): Boolean =
|
||||
me.fold(Granter(permission), false)
|
||||
}
|
||||
|
||||
final class BodyContext(val body: Request[_], m: Option[User])
|
||||
|
|
|
@ -35,21 +35,27 @@ trait AuthConfigImpl extends AuthConfig {
|
|||
def resolveUser(id: Id): Option[User] =
|
||||
(env.user.userRepo byUsername id).unsafePerformIO
|
||||
|
||||
def logoutSucceeded[A](request: Request[A]): PlainResult =
|
||||
def logoutSucceeded[A](req: Request[A]): PlainResult =
|
||||
Redirect(routes.Lobby.home)
|
||||
|
||||
def authenticationFailed[A](request: Request[A]): PlainResult =
|
||||
Redirect(routes.Lobby.home).withSession("access_uri" -> request.uri)
|
||||
def authenticationFailed(req: RequestHeader): PlainResult =
|
||||
Redirect(routes.Lobby.home).withSession("access_uri" -> req.uri)
|
||||
|
||||
def loginSucceeded[A](request: Request[A]): PlainResult = {
|
||||
val uri = request.session.get("access_uri").getOrElse(routes.Lobby.home.url)
|
||||
request.session - "access_uri"
|
||||
def authenticationFailed[A](req: Request[A]): PlainResult =
|
||||
authenticationFailed(req)
|
||||
|
||||
def loginSucceeded[A](req: Request[A]): PlainResult = {
|
||||
val uri = req.session.get("access_uri").getOrElse(routes.Lobby.home.url)
|
||||
req.session - "access_uri"
|
||||
Redirect(uri)
|
||||
}
|
||||
|
||||
def authorizationFailed[A](request: Request[A]): PlainResult =
|
||||
def authorizationFailed(req: RequestHeader): PlainResult =
|
||||
Forbidden("no permission")
|
||||
|
||||
def authorizationFailed[A](req: Request[A]): PlainResult =
|
||||
authorizationFailed(req)
|
||||
|
||||
def authorize(user: User, authority: Authority): Boolean =
|
||||
Permission(user.roles) contains authority
|
||||
|
||||
|
|
10
app/security/Granter.scala
Normal file
10
app/security/Granter.scala
Normal file
|
@ -0,0 +1,10 @@
|
|||
package lila
|
||||
package security
|
||||
|
||||
import user.User
|
||||
|
||||
object Granter {
|
||||
|
||||
def apply(permission: Permission)(user: User): Boolean =
|
||||
Permission(user.roles) exists (_ is permission)
|
||||
}
|
|
@ -1,14 +1,26 @@
|
|||
package lila
|
||||
package security
|
||||
|
||||
sealed abstract class Permission(val name: String)
|
||||
sealed abstract class Permission(val name: String) {
|
||||
|
||||
val children: List[Permission] = Nil
|
||||
|
||||
final def is(p: Permission): Boolean =
|
||||
this == p || (children exists (_ is p))
|
||||
}
|
||||
|
||||
object Permission {
|
||||
|
||||
case object Anonymous extends Permission("ANON")
|
||||
case object Admin extends Permission("ROLE_ADMIN")
|
||||
case object SuperAdmin extends Permission("ROLE_SUPER_ADMIN")
|
||||
case object ViewTrialsInGame extends Permission("ROLE_VIEW_TRIALS_IN_GAME")
|
||||
case object ViewBlurs extends Permission("ROLE_VIEW_BLURS")
|
||||
case object MutePlayer extends Permission("ROLE_CHAT_BAN")
|
||||
case object MarkEngine extends Permission("ROLE_ADJUST_CHEATER")
|
||||
|
||||
case object Admin extends Permission("ROLE_ADMIN") {
|
||||
override val children = List(ViewBlurs, MutePlayer, MarkEngine)
|
||||
}
|
||||
case object SuperAdmin extends Permission("ROLE_SUPER_ADMIN") {
|
||||
override val children = List(Admin)
|
||||
}
|
||||
|
||||
val all: List[Permission] = List(SuperAdmin, Admin)
|
||||
val allByName: Map[String, Permission] = all map { p ⇒
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
package lila
|
||||
package security
|
||||
|
||||
import user.User
|
||||
import http.Context
|
||||
|
||||
trait SecurityHelper {
|
||||
|
||||
def isGranted(permission: Permission)(implicit ctx: Context): Boolean =
|
||||
ctx.me.fold(_ hasRole permission.name, false)
|
||||
ctx.me.fold(Granter(permission), false)
|
||||
|
||||
def isGranted(permission: Permission, user: User): Boolean =
|
||||
Granter(permission)(user)
|
||||
}
|
||||
|
|
12
app/user/DataForm.scala
Normal file
12
app/user/DataForm.scala
Normal file
|
@ -0,0 +1,12 @@
|
|||
package lila
|
||||
package user
|
||||
|
||||
import play.api.data._
|
||||
import play.api.data.Forms._
|
||||
|
||||
object DataForm {
|
||||
|
||||
val bio = Form(single(
|
||||
"bio" -> text(maxLength = 400)
|
||||
))
|
||||
}
|
31
app/user/GameFilter.scala
Normal file
31
app/user/GameFilter.scala
Normal file
|
@ -0,0 +1,31 @@
|
|||
package lila
|
||||
package user
|
||||
|
||||
import http.Context
|
||||
|
||||
import scalaz.NonEmptyLists
|
||||
|
||||
sealed abstract class GameFilter(val name: String)
|
||||
|
||||
object GameFilter {
|
||||
|
||||
case object All extends GameFilter("all")
|
||||
case object Me extends GameFilter("me")
|
||||
}
|
||||
|
||||
case class GameFilterMenu(
|
||||
info: UserInfo,
|
||||
me: Option[User]) extends NonEmptyLists {
|
||||
|
||||
import GameFilter._
|
||||
|
||||
val all = nel(All, List(
|
||||
(info.user.some != me) option Me
|
||||
).flatten)
|
||||
|
||||
def list = all.list
|
||||
|
||||
def apply(name: String) = (list find (_.name == name)) | default
|
||||
|
||||
def default = all.head
|
||||
}
|
|
@ -27,9 +27,9 @@ case class User(
|
|||
|
||||
def setting(name: String): Option[Any] = settings get name
|
||||
|
||||
def hasRole(name: String) = roles contains name
|
||||
|
||||
def nonEmptyBio = bio filter ("" !=)
|
||||
|
||||
def hasGames = nbGames > 0
|
||||
}
|
||||
|
||||
object User {
|
||||
|
|
|
@ -8,15 +8,15 @@ import http.Context
|
|||
import scalaz.effects._
|
||||
|
||||
case class UserInfo(
|
||||
user: User,
|
||||
rank: Option[(Int, Int)],
|
||||
nbWin: Int,
|
||||
nbDraw: Int,
|
||||
nbLoss: Int,
|
||||
eloWithMe: Option[List[(String, Int)]],
|
||||
eloChart: Option[EloChart],
|
||||
winChart: Option[WinChart]) {
|
||||
}
|
||||
user: User,
|
||||
rank: Option[(Int, Int)],
|
||||
nbWin: Int,
|
||||
nbDraw: Int,
|
||||
nbLoss: Int,
|
||||
nbWithMe: Option[Int],
|
||||
eloWithMe: Option[List[(String, Int)]],
|
||||
eloChart: Option[EloChart],
|
||||
winChart: Option[WinChart])
|
||||
|
||||
object UserInfo {
|
||||
|
||||
|
@ -37,6 +37,10 @@ object UserInfo {
|
|||
nbWin ← gameRepo countWinBy user
|
||||
nbDraw ← gameRepo countDrawBy user
|
||||
nbLoss ← gameRepo countLossBy user
|
||||
nbWithMe ← ctx.me.filter(user!=).fold(
|
||||
me ⇒ gameRepo.countOpponents(user, me) map (_.some),
|
||||
io(none)
|
||||
)
|
||||
eloChart ← eloChartBuilder(user)
|
||||
winChart = (user.nbGames > 0) option {
|
||||
new WinChart(nbWin, nbDraw, nbLoss)
|
||||
|
@ -53,6 +57,7 @@ object UserInfo {
|
|||
nbWin = nbWin,
|
||||
nbDraw = nbDraw,
|
||||
nbLoss = nbLoss,
|
||||
nbWithMe = nbWithMe,
|
||||
eloWithMe = eloWithMe,
|
||||
eloChart = eloChart,
|
||||
winChart = winChart)
|
||||
|
|
|
@ -94,6 +94,29 @@ class UserRepo(
|
|||
.flatten
|
||||
}
|
||||
|
||||
def toggleMute(username: String): IO[Unit] = updateIO(username) { user ⇒
|
||||
$set("isChatBan" -> !user.isChatBan)
|
||||
}
|
||||
|
||||
def toggleEngine(username: String): IO[Unit] = updateIO(username) { user ⇒
|
||||
$set("engine" -> !user.engine)
|
||||
}
|
||||
|
||||
def setBio(user: User, bio: String) = updateIO(user)($set("bio" -> bio))
|
||||
|
||||
def enable(user: User) = updateIO(user)($set("enabled" -> true))
|
||||
|
||||
def disable(user: User) = updateIO(user)($set("enabled" -> false))
|
||||
|
||||
def updateIO(username: String)(op: User ⇒ DBObject): IO[Unit] = for {
|
||||
userOption ← byUsername(username)
|
||||
_ ← userOption.fold(user ⇒ updateIO(user)(op(user)), io())
|
||||
} yield ()
|
||||
|
||||
def updateIO(user: User)(obj: DBObject): IO[Unit] = io {
|
||||
update(idSelector(user), obj)
|
||||
}
|
||||
|
||||
private def idSelector(user: User) = DBObject("_id" -> user.id)
|
||||
|
||||
private def idSelector(id: ObjectId) = DBObject("_id" -> id)
|
||||
|
|
|
@ -70,7 +70,7 @@
|
|||
@game.clock.map { c =>
|
||||
@round.clock(c, pov.color, "bottom")
|
||||
}
|
||||
@if(isGranted(Permission.ViewTrialsInGame)) {
|
||||
@if(isGranted(Permission.ViewBlurs)) {
|
||||
@game.players.map { p =>
|
||||
@if(game.playerBlurPercent(p.color) > 30) {
|
||||
<br />@playerLink(p) @p.blurs/@game.playerMoves(p.color) blurs (@game.playerBlurPercent(p.color))
|
||||
|
|
24
app/views/user/close.scala.html
Normal file
24
app/views/user/close.scala.html
Normal file
|
@ -0,0 +1,24 @@
|
|||
@(u: User)(implicit ctx: Context)
|
||||
|
||||
@title = @{ "%s - close account".format(u.username) }
|
||||
|
||||
@user.layout(
|
||||
title = title,
|
||||
robots = false) {
|
||||
<div class="content_box small_box">
|
||||
<div class="signup_box">
|
||||
<h1 class="lichess_title">Close your account</h1>
|
||||
<p class="explanation">
|
||||
Are you sure you want to close your account? You will no more be able to login!
|
||||
</p>
|
||||
<form action="@routes.User.closeConfirm" method="POST">
|
||||
<br /><br />
|
||||
<a href="@routes.User.show(u.username)">
|
||||
I changed my mind, don't close my account
|
||||
</a>
|
||||
<br /><br />
|
||||
<input type="submit" class="submit" value="Close my account" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
}
|
11
app/views/user/filterTitle.scala.html
Normal file
11
app/views/user/filterTitle.scala.html
Normal file
|
@ -0,0 +1,11 @@
|
|||
@(info: lila.user.UserInfo, filter: lila.user.GameFilter)(implicit ctx: Context)
|
||||
|
||||
@filter match {
|
||||
case lila.user.GameFilter.All => {
|
||||
@info.user.nbGames @trans.gamesPlayed()
|
||||
}
|
||||
case lila.user.GameFilter.Me => {
|
||||
@ctx.me.fold(me => "%d vs %s".format(info.nbWithMe | 0, me.username), "-")
|
||||
}
|
||||
}
|
||||
|
23
app/views/user/home.scala.html
Normal file
23
app/views/user/home.scala.html
Normal file
|
@ -0,0 +1,23 @@
|
|||
@(u: User, info: lila.user.UserInfo, games: Paginator[DbGame], filters: lila.user.GameFilterMenu, filter: lila.user.GameFilter)(implicit ctx: Context)
|
||||
|
||||
@bio = {
|
||||
<div class="editable" data-url="@routes.User.setBio">
|
||||
<span class="user_bio" data-name="bio" data-type="textarea" data-provider-url="@routes.User.getBio">
|
||||
@shorten(u.bio | "Click here to tell about yourself.", 400)
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@actions = {
|
||||
<br />
|
||||
<a class="small" href="@routes.User.close">Close your account</a>
|
||||
}
|
||||
|
||||
@user.profile(
|
||||
u = u,
|
||||
info = info,
|
||||
games = games,
|
||||
bio = bio,
|
||||
actions = actions,
|
||||
filters = filters,
|
||||
filter = filter)
|
|
@ -1,4 +1,6 @@
|
|||
@(u: User, info: lila.user.UserInfo, title: String, bio: Html)(implicit ctx: Context)
|
||||
@(u: User, info: lila.user.UserInfo, games: Paginator[DbGame], bio: Html, actions: Html, filters: lila.user.GameFilterMenu, filter: lila.user.GameFilter)(implicit ctx: Context)
|
||||
|
||||
@title = @{ "%s %s - page %d".format(u.username, filterTitle(info, filter), games.currentPage) }
|
||||
|
||||
@evenMoreJs = {
|
||||
<script src="http://www.google.com/jsapi"></script>
|
||||
|
@ -25,7 +27,7 @@ evenMoreJs = evenMoreJs) {
|
|||
}
|
||||
</div>
|
||||
<div class="content_box_content clearfix">
|
||||
@if(u.hasRole("ROLE_ADMIN")) {
|
||||
@if(isGranted(Permission.Admin, u)) {
|
||||
<div class="staff">STAFF</div>
|
||||
}
|
||||
@info.eloChart.map { eloChart =>
|
||||
|
@ -41,10 +43,26 @@ evenMoreJs = evenMoreJs) {
|
|||
@e._1.capitalize: <strong>@showNumber(e._2)</strong>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<div class="stats">
|
||||
@info.winChart.map { winChart =>
|
||||
<div class="win_stats" title="@trans.gamesPlayed(): @u.nbGames" data-columns="@winChart.columns" data-rows="@winChart.rows(trans)"></div>
|
||||
}
|
||||
@actions
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
@if(u.hasGames) {
|
||||
<div class="content_box_inter clearfix">
|
||||
@filters.list.map { f =>
|
||||
<a @(filter == f).fold("class='active' ", "")href="@routes.User.showFilter(u.username, f.name)">
|
||||
@filterTitle(info, f)
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
<div class="games infinitescroll all_games">
|
||||
<div class="pager"><a href="@routes.User.showFilter(u.username, filter.name, games.nextPage | 1)">Next</a></div>
|
||||
@game.widgets(games.currentPageResults, u.some)
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
@(u: User, info: lila.user.UserInfo, mode: String, games: Paginator[DbGame])(implicit ctx: Context)
|
||||
|
||||
@title = @{ "%s %s - page %d".format(u.username, mode, games.currentPage) }
|
||||
@(u: User, info: lila.user.UserInfo, games: Paginator[DbGame], filters: lila.user.GameFilterMenu, filter: lila.user.GameFilter)(implicit ctx: Context)
|
||||
|
||||
@bio = {
|
||||
@u.nonEmptyBio.map { bio =>
|
||||
|
@ -8,8 +6,24 @@
|
|||
}
|
||||
}
|
||||
|
||||
@actions = {
|
||||
@if(isGranted(Permission.MarkEngine)) {
|
||||
<form method="post" action="@routes.User.engine(u.username)">
|
||||
<input class="confirm" type="submit" value="Mark as engine" />
|
||||
</form>
|
||||
}
|
||||
@if(isGranted(Permission.MutePlayer)) {
|
||||
<form method="post" action="@routes.User.mute(u.username)">
|
||||
<input class="confirm" type="submit" value="@u.isChatBan.fold("Unmute", "Mute")" />
|
||||
</form>
|
||||
}
|
||||
}
|
||||
|
||||
@user.profile(
|
||||
u = u,
|
||||
info = info,
|
||||
title = title,
|
||||
bio = bio)
|
||||
games = games,
|
||||
bio = bio,
|
||||
actions = actions,
|
||||
filters = filters,
|
||||
filter = filter)
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
package lila.cli
|
||||
|
||||
import scalaz.effects._
|
||||
|
||||
trait Command {
|
||||
|
||||
def apply(): IO[Unit]
|
||||
}
|
|
@ -3,9 +3,9 @@ package lila.cli
|
|||
import lila.game.GameRepo
|
||||
import scalaz.effects._
|
||||
|
||||
case class IndexDb(gameRepo: GameRepo) extends Command {
|
||||
case class Games(gameRepo: GameRepo) {
|
||||
|
||||
def apply: IO[Unit] = for {
|
||||
def index: IO[Unit] = for {
|
||||
_ ← putStrLn("- Drop indexes")
|
||||
_ ← gameRepo.dropIndexes
|
||||
_ ← putStrLn("- Ensure indexes")
|
|
@ -1,16 +0,0 @@
|
|||
package lila.cli
|
||||
|
||||
import lila.core.CoreEnv
|
||||
import scalaz.effects._
|
||||
|
||||
case class Info(env: CoreEnv) extends Command {
|
||||
|
||||
def apply: IO[Unit] = for {
|
||||
nb <- nbGames
|
||||
_ ← putStrLn("%d games in DB" format nb)
|
||||
} yield ()
|
||||
|
||||
def nbGames = io {
|
||||
env.game.gameRepo.count()
|
||||
}
|
||||
}
|
|
@ -3,9 +3,9 @@ package lila.cli
|
|||
import lila.core.CoreEnv
|
||||
import scalaz.effects._
|
||||
|
||||
case class AverageElo(env: CoreEnv) extends Command {
|
||||
case class Infos(env: CoreEnv) {
|
||||
|
||||
def apply: IO[Unit] = for {
|
||||
def averageElo: IO[Unit] = for {
|
||||
avg ← env.user.userRepo.averageElo
|
||||
_ ← putStrLn("Average elo is %f" format avg)
|
||||
} yield ()
|
|
@ -16,24 +16,24 @@ object Main {
|
|||
|
||||
lazy val env = CoreEnv(app)
|
||||
|
||||
lazy val users = Users(env.user.userRepo)
|
||||
lazy val games = Games(env.game.gameRepo)
|
||||
lazy val infos = Infos(env)
|
||||
|
||||
def main(args: Array[String]): Unit = sys exit {
|
||||
|
||||
val command: Command = args.toList match {
|
||||
case "info" :: Nil ⇒ Info(env)
|
||||
case "average-elo" :: Nil ⇒ AverageElo(env)
|
||||
case "index" :: Nil ⇒ IndexDb(env.game.gameRepo)
|
||||
val op: IO[Unit] = args.toList match {
|
||||
case "average-elo" :: Nil ⇒ infos.averageElo
|
||||
case "game-index" :: Nil ⇒ games.index
|
||||
case "trans-js-dump" :: Nil ⇒ TransJsDump(
|
||||
path = new File(env.app.path.getCanonicalPath + "/public/trans"),
|
||||
pool = env.i18n.pool,
|
||||
keys = env.i18n.keys)
|
||||
case "finish" :: Nil ⇒ new Command {
|
||||
def apply() = env.gameFinishCommand.apply()
|
||||
}
|
||||
case _ ⇒ new Command {
|
||||
def apply() = putStrLn("Usage: run command args")
|
||||
}
|
||||
keys = env.i18n.keys).apply
|
||||
case "finish" :: Nil ⇒ env.gameFinishCommand.apply
|
||||
case "user-enable" :: username :: Nil ⇒ users enable username
|
||||
case "user-disable" :: username :: Nil ⇒ users disable username
|
||||
case _ ⇒ putStrLn("Usage: run command args")
|
||||
}
|
||||
command.apply.unsafePerformIO
|
||||
0
|
||||
op.map(_ ⇒ 0).unsafePerformIO
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import scalaz.effects._
|
|||
case class TransJsDump(
|
||||
path: File,
|
||||
pool: I18nPool,
|
||||
keys: I18nKeys) extends Command {
|
||||
keys: I18nKeys) {
|
||||
|
||||
val messages = List(
|
||||
keys.unlimited,
|
||||
|
|
22
cli/src/main/scala/Users.scala
Normal file
22
cli/src/main/scala/Users.scala
Normal file
|
@ -0,0 +1,22 @@
|
|||
package lila.cli
|
||||
|
||||
import lila.user.{ User, UserRepo }
|
||||
import scalaz.effects._
|
||||
|
||||
case class Users(userRepo: UserRepo) {
|
||||
|
||||
def enable(username: String): IO[Unit] =
|
||||
perform(username, "Enable", userRepo.enable)
|
||||
|
||||
def disable(username: String): IO[Unit] =
|
||||
perform(username, "Disable", userRepo.disable)
|
||||
|
||||
def perform(username: String, action: String, op: User ⇒ IO[Unit]) = for {
|
||||
_ ← putStrLn(action + " " + username)
|
||||
userOption ← userRepo byUsername username
|
||||
_ ← userOption.fold(
|
||||
u ⇒ op(u) flatMap { _ ⇒ putStrLn("Success") },
|
||||
putStrLn("Not found")
|
||||
)
|
||||
} yield ()
|
||||
}
|
|
@ -61,13 +61,19 @@ GET /logout controllers.Auth.logout
|
|||
|
||||
# User
|
||||
GET /@/:username/export controllers.User.export(username: String)
|
||||
GET /@/:username/:mode controllers.User.showMode(username: String, mode: String, page: Int ?= 1)
|
||||
GET /@/:username/:filterName controllers.User.showFilter(username: String, filterName: String, page: Int ?= 1)
|
||||
GET /@/:username controllers.User.show(username: String)
|
||||
POST /@/:username/engine controllers.User.engine(username: String)
|
||||
POST /@/:username/mute controllers.User.mute(username: String)
|
||||
GET /signup controllers.User.signUp
|
||||
GET /people controllers.User.list(page: Int ?= 1)
|
||||
GET /people/stats controllers.User.stats
|
||||
GET /people/autocomplete controllers.User.autocomplete
|
||||
GET /people/online controllers.User.online
|
||||
GET /account/bio controllers.User.getBio
|
||||
POST /account/bio controllers.User.setBio
|
||||
GET /account/close controllers.User.close
|
||||
POST /account/closeConfirm controllers.User.closeConfirm
|
||||
|
||||
# Wiki
|
||||
GET /wiki controllers.Wiki.home
|
||||
|
|
Loading…
Reference in a new issue