From 58946f817755b88cea205f46bde457e98f66a0e3 Mon Sep 17 00:00:00 2001 From: Thibault Duplessis Date: Fri, 25 May 2012 00:01:47 +0200 Subject: [PATCH] User and security --- app/controllers/LilaController.scala | 34 +++++++++- app/controllers/User.scala | 66 +++++++++++++++---- app/game/GameRepo.scala | 17 +++-- app/http/Context.scala | 4 ++ app/security/AuthConfigImpl.scala | 20 ++++-- app/security/Granter.scala | 10 +++ app/security/Permission.scala | 22 +++++-- app/security/SecurityHelper.scala | 6 +- app/user/DataForm.scala | 12 ++++ app/user/GameFilter.scala | 31 +++++++++ app/user/User.scala | 4 +- app/user/UserInfo.scala | 23 ++++--- app/user/UserRepo.scala | 23 +++++++ app/views/round/watcher.scala.html | 2 +- app/views/user/close.scala.html | 24 +++++++ app/views/user/filterTitle.scala.html | 11 ++++ app/views/user/home.scala.html | 23 +++++++ app/views/user/profile.scala.html | 26 ++++++-- app/views/user/show.scala.html | 24 +++++-- cli/src/main/scala/Command.scala | 8 --- .../main/scala/{IndexDb.scala => Games.scala} | 4 +- cli/src/main/scala/Info.scala | 16 ----- .../scala/{AverageElo.scala => Infos.scala} | 4 +- cli/src/main/scala/Main.scala | 26 ++++---- cli/src/main/scala/TransJsDump.scala | 2 +- cli/src/main/scala/Users.scala | 22 +++++++ conf/routes | 8 ++- 27 files changed, 378 insertions(+), 94 deletions(-) create mode 100644 app/security/Granter.scala create mode 100644 app/user/DataForm.scala create mode 100644 app/user/GameFilter.scala create mode 100644 app/views/user/close.scala.html create mode 100644 app/views/user/filterTitle.scala.html create mode 100644 app/views/user/home.scala.html delete mode 100644 cli/src/main/scala/Command.scala rename cli/src/main/scala/{IndexDb.scala => Games.scala} (72%) delete mode 100644 cli/src/main/scala/Info.scala rename cli/src/main/scala/{AverageElo.scala => Infos.scala} (67%) create mode 100644 cli/src/main/scala/Users.scala diff --git a/app/controllers/LilaController.scala b/app/controllers/LilaController.scala index a7020cd8d3..39cbdb1d7f 100644 --- a/app/controllers/LilaController.scala +++ b/app/controllers/LilaController.scala @@ -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)), diff --git a/app/controllers/User.scala b/app/controllers/User.scala index 8759ac75d1..5362e8cf45 100644 --- a/app/controllers/User.scala +++ b/app/controllers/User.scala @@ -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 diff --git a/app/game/GameRepo.scala b/app/game/GameRepo.scala index 0c07e2ca82..c74babe0f7 100644 --- a/app/game/GameRepo.scala +++ b/app/game/GameRepo.scala @@ -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)) diff --git a/app/http/Context.scala b/app/http/Context.scala index 08e756e02a..7174bc0102 100644 --- a/app/http/Context.scala +++ b/app/http/Context.scala @@ -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]) diff --git a/app/security/AuthConfigImpl.scala b/app/security/AuthConfigImpl.scala index da1afdfef5..5666e7e5ea 100644 --- a/app/security/AuthConfigImpl.scala +++ b/app/security/AuthConfigImpl.scala @@ -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 diff --git a/app/security/Granter.scala b/app/security/Granter.scala new file mode 100644 index 0000000000..4248ce5853 --- /dev/null +++ b/app/security/Granter.scala @@ -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) +} diff --git a/app/security/Permission.scala b/app/security/Permission.scala index 27bfed5e91..0cc0926948 100644 --- a/app/security/Permission.scala +++ b/app/security/Permission.scala @@ -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 ⇒ diff --git a/app/security/SecurityHelper.scala b/app/security/SecurityHelper.scala index 9def1ab0b9..c6b0c37f90 100644 --- a/app/security/SecurityHelper.scala +++ b/app/security/SecurityHelper.scala @@ -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) } diff --git a/app/user/DataForm.scala b/app/user/DataForm.scala new file mode 100644 index 0000000000..955a6d8496 --- /dev/null +++ b/app/user/DataForm.scala @@ -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) + )) +} diff --git a/app/user/GameFilter.scala b/app/user/GameFilter.scala new file mode 100644 index 0000000000..0c31f720f3 --- /dev/null +++ b/app/user/GameFilter.scala @@ -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 +} diff --git a/app/user/User.scala b/app/user/User.scala index 6d8127a189..ac490aa5e4 100644 --- a/app/user/User.scala +++ b/app/user/User.scala @@ -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 { diff --git a/app/user/UserInfo.scala b/app/user/UserInfo.scala index 70976193f8..b349b42db0 100644 --- a/app/user/UserInfo.scala +++ b/app/user/UserInfo.scala @@ -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) diff --git a/app/user/UserRepo.scala b/app/user/UserRepo.scala index a2303d7d3e..301d8bb0c5 100644 --- a/app/user/UserRepo.scala +++ b/app/user/UserRepo.scala @@ -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) diff --git a/app/views/round/watcher.scala.html b/app/views/round/watcher.scala.html index c44d6e1662..77e8e71672 100644 --- a/app/views/round/watcher.scala.html +++ b/app/views/round/watcher.scala.html @@ -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) {
@playerLink(p) @p.blurs/@game.playerMoves(p.color) blurs (@game.playerBlurPercent(p.color)) diff --git a/app/views/user/close.scala.html b/app/views/user/close.scala.html new file mode 100644 index 0000000000..34af6af2d1 --- /dev/null +++ b/app/views/user/close.scala.html @@ -0,0 +1,24 @@ +@(u: User)(implicit ctx: Context) + +@title = @{ "%s - close account".format(u.username) } + +@user.layout( +title = title, +robots = false) { +
+ +
+} diff --git a/app/views/user/filterTitle.scala.html b/app/views/user/filterTitle.scala.html new file mode 100644 index 0000000000..2b51ef939f --- /dev/null +++ b/app/views/user/filterTitle.scala.html @@ -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), "-") +} +} + diff --git a/app/views/user/home.scala.html b/app/views/user/home.scala.html new file mode 100644 index 0000000000..2e91476697 --- /dev/null +++ b/app/views/user/home.scala.html @@ -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 = { +
+ + @shorten(u.bio | "Click here to tell about yourself.", 400) + +
+} + +@actions = { +
+Close your account +} + +@user.profile( +u = u, +info = info, +games = games, +bio = bio, +actions = actions, +filters = filters, +filter = filter) diff --git a/app/views/user/profile.scala.html b/app/views/user/profile.scala.html index defbf275af..e789fd034d 100644 --- a/app/views/user/profile.scala.html +++ b/app/views/user/profile.scala.html @@ -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 = { @@ -25,7 +27,7 @@ evenMoreJs = evenMoreJs) { }
- @if(u.hasRole("ROLE_ADMIN")) { + @if(isGranted(Permission.Admin, u)) {
STAFF
} @info.eloChart.map { eloChart => @@ -41,10 +43,26 @@ evenMoreJs = evenMoreJs) { @e._1.capitalize: @showNumber(e._2) }
+ }
@info.winChart.map { winChart =>
} + @actions
- } - } + + @if(u.hasGames) { +
+ @filters.list.map { f => + + @filterTitle(info, f) + + } +
+
+ + @game.widgets(games.currentPageResults, u.some) +
+ } + +} diff --git a/app/views/user/show.scala.html b/app/views/user/show.scala.html index c95a7db35e..ede23088dc 100644 --- a/app/views/user/show.scala.html +++ b/app/views/user/show.scala.html @@ -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)) { +
+ +
+} +@if(isGranted(Permission.MutePlayer)) { +
+ +
+} +} + @user.profile( u = u, info = info, -title = title, -bio = bio) +games = games, +bio = bio, +actions = actions, +filters = filters, +filter = filter) diff --git a/cli/src/main/scala/Command.scala b/cli/src/main/scala/Command.scala deleted file mode 100644 index 0ce94e8b14..0000000000 --- a/cli/src/main/scala/Command.scala +++ /dev/null @@ -1,8 +0,0 @@ -package lila.cli - -import scalaz.effects._ - -trait Command { - - def apply(): IO[Unit] -} diff --git a/cli/src/main/scala/IndexDb.scala b/cli/src/main/scala/Games.scala similarity index 72% rename from cli/src/main/scala/IndexDb.scala rename to cli/src/main/scala/Games.scala index 6789356442..f198f2d695 100644 --- a/cli/src/main/scala/IndexDb.scala +++ b/cli/src/main/scala/Games.scala @@ -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") diff --git a/cli/src/main/scala/Info.scala b/cli/src/main/scala/Info.scala deleted file mode 100644 index 601fa0cf19..0000000000 --- a/cli/src/main/scala/Info.scala +++ /dev/null @@ -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() - } -} diff --git a/cli/src/main/scala/AverageElo.scala b/cli/src/main/scala/Infos.scala similarity index 67% rename from cli/src/main/scala/AverageElo.scala rename to cli/src/main/scala/Infos.scala index 076b29dccb..1850028577 100644 --- a/cli/src/main/scala/AverageElo.scala +++ b/cli/src/main/scala/Infos.scala @@ -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 () diff --git a/cli/src/main/scala/Main.scala b/cli/src/main/scala/Main.scala index d25f452fa2..97c31edb16 100644 --- a/cli/src/main/scala/Main.scala +++ b/cli/src/main/scala/Main.scala @@ -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 } } diff --git a/cli/src/main/scala/TransJsDump.scala b/cli/src/main/scala/TransJsDump.scala index b2dd1b143e..81004d87df 100644 --- a/cli/src/main/scala/TransJsDump.scala +++ b/cli/src/main/scala/TransJsDump.scala @@ -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, diff --git a/cli/src/main/scala/Users.scala b/cli/src/main/scala/Users.scala new file mode 100644 index 0000000000..20386e1549 --- /dev/null +++ b/cli/src/main/scala/Users.scala @@ -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 () +} diff --git a/conf/routes b/conf/routes index 01a8d7f63d..f8d60c27ee 100644 --- a/conf/routes +++ b/conf/routes @@ -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