implement mod module

pull/83/head
Thibault Duplessis 2013-04-06 21:38:47 -03:00
parent e6f53d613c
commit 48be1445fa
17 changed files with 197 additions and 185 deletions

View File

@ -18,6 +18,7 @@ final class Env(application: Application, val config: Config) {
}
val RendererName = config getString "core.renderer.name"
val RouterName = config getString "core.router.name"
lazy val cli = new Cli(this)

View File

@ -11,5 +11,7 @@ object Starter {
def apply(implicit app: Application) {
system.actorOf(Props(new Renderer), name = apiEnv.RendererName)
system.actorOf(Props(new Router), name = apiEnv.RouterName)
}
}

View File

@ -10,15 +10,11 @@ import play.modules.reactivemongo.Implicits._
trait InColl[A] { implicit def coll: Types.Coll }
object InColl {
def apply[A](c: Coll): InColl[A] = new InColl[A] { def coll = c }
}
case class Tube[Doc](
reader: Reads[Doc],
writer: Writes[Doc],
writeTransformer: Option[Reads[JsObject]] = None)
writeTransformer: Option[Reads[JsObject]] = None,
flags: Seq[Tube.Flag.type Tube.Flag] = Seq.empty)
extends Reads[Doc]
with Writes[Doc]
with BSONDocumentReader[Option[Doc]] {
@ -37,16 +33,25 @@ case class Tube[Doc](
case (value, _) JsError()
}
def toMongo(doc: Doc): JsResult[JsObject] =
write(doc) flatMap Tube.toMongo
def toMongo(doc: Doc): JsResult[JsObject] = flag(_.NoId)(
write(doc),
write(doc) flatMap Tube.toMongoId
)
def fromMongo(js: JsObject): JsResult[Doc] =
Tube.depath(Tube fromMongo js) flatMap read
def fromMongo(js: JsObject): JsResult[Doc] = flag(_.NoId)(
read(js),
Tube.depath(Tube fromMongoId js) flatMap read
)
def inColl(c: Coll): TubeInColl[Doc] =
def inColl(c: Coll): TubeInColl[Doc] =
new Tube[Doc](reader, writer, writeTransformer) with InColl[Doc] {
def coll = c
}
private lazy val flagSet = flags.map(_(Tube.Flag)).toSet
private def flag[A](f: Tube.Flag.type Tube.Flag)(x: A, y: A) =
flagSet contains f(Tube.Flag) fold (x, y)
}
object Tube {
@ -55,11 +60,16 @@ object Tube {
reader = __.read[JsObject],
writer = __.write[JsObject])
private val toMongoTransformer = Helpers.rename('id, '_id)
private val fromMongoTransformer = Helpers.rename('_id, 'id)
def toMongoId(js: JsValue): JsResult[JsObject] =
js transform Helpers.rename('id, '_id)
def toMongo(js: JsValue): JsResult[JsObject] = js transform toMongoTransformer
def fromMongo(js: JsValue): JsResult[JsObject] = js transform fromMongoTransformer
def fromMongoId(js: JsValue): JsResult[JsObject] =
js transform Helpers.rename('_id, 'id)
sealed trait Flag
object Flag {
case object NoId extends Flag
}
object Helpers {

View File

@ -20,6 +20,7 @@ package captcha {
package lobby {
case class TimelineEntry(rendered: String)
case class Censor(username: String)
}
package message {

View File

@ -0,0 +1,41 @@
package lila.mod
import lila.db.Types.Coll
import lila.security.{ Firewall, UserSpy }
import lila.user.EloUpdater
import akka.actor.ActorRef
import com.typesafe.config.Config
final class Env(
config: Config,
db: lila.db.Env,
eloUpdater: EloUpdater,
lobby: ActorRef,
firewall: Firewall,
userSpy: String => Fu[UserSpy]) {
private val CollectionModlog = config getString "collection.modlog"
private[mod] lazy val modlogColl = db(CollectionModlog)
val logApi = new ModlogApi
lazy val api = new ModApi(
logApi = logApi,
userSpy = userSpy,
firewall = firewall,
eloUpdater = eloUpdater,
lobby = lobby)
}
object Env {
lazy val current = "[boot] mod" describes new Env(
config = lila.common.PlayApp loadConfig "mod",
db = lila.db.Env.current,
eloUpdater = lila.user.Env.current.eloUpdater,
lobby = lila.hub.Env.current.actor.lobby,
firewall = lila.security.Env.current.firewall,
userSpy = lila.security.Env.current.userSpy)
}

View File

@ -0,0 +1,49 @@
package lila.mod
import lila.user.{ User, UserRepo, EloUpdater }
import lila.security.{ Firewall, UserSpy, Store SecurityStore }
import lila.user.tube.userTube
import lila.db.api._
import lila.hub.actorApi.lobby.Censor
import akka.actor.ActorRef
import play.api.libs.concurrent.Execution.Implicits._
final class ModApi(
logApi: ModlogApi,
userSpy: String Fu[UserSpy],
firewall: Firewall,
eloUpdater: EloUpdater,
lobby: ActorRef) {
def adjust(mod: String, username: String): Funit = withUser(username) { user
logApi.engine(mod, user.id, !user.engine) zip
UserRepo.toggleEngine(user.id) zip
eloUpdater.adjust(user)
}
def mute(mod: String, username: String): Funit = withUser(username) { user
(UserRepo toggleMute user.id) >>
censor(user) >>
logApi.mute(mod, user.id, !user.isChatBan)
}
def ban(mod: String, username: String): Funit = withUser(username) { user
userSpy(username) flatMap { spy
(spy.ips map firewall.blockIp).sequence >>
censor(user) >>
logApi.ban(mod, user.id)
}
}
def ipban(mod: String, ip: String): Funit =
(firewall blockIp ip) >> logApi.ipban(mod, ip)
private def censor(user: User) {
// TODO handle that on lobby side
if (user.canChat) lobby ! Censor(user.username)
}
private def withUser(username: String)(op: User Fu[Any]): Funit =
$find.byId[User](username) flatMap { _ zmap (u op(u).void) }
}

View File

@ -1,7 +1,4 @@
package lila.app
package mod
import user.User
package lila.mod
import org.joda.time.DateTime
import org.scala_tools.time.Imports._
@ -23,7 +20,7 @@ case class Modlog(
}
}
object Modlog extends Function5[String, Option[String], String, Option[String], DateTime, Modlog] {
object Modlog {
val engine = "engine"
val unengine = "unengine"
@ -33,7 +30,18 @@ object Modlog extends Function5[String, Option[String], String, Option[String],
val ipban = "ipban"
val deletePost = "deletePost"
import play.api.libs.json.Json
import lila.db.Tube
import Tube.Helpers._
import play.api.libs.json._
val json = mongodb.Tube(Json.reads[Modlog], Json.writes[Modlog])
private[mod] lazy val tube = Tube[Modlog](
reader = (__.json update (
merge(defaults) andThen readDate('date)
)) andThen Json.reads[Modlog],
writer = Json.writes[Modlog],
writeTransformer = (__.json update writeDate('date)).some,
flags = Seq(_.NoId)
)
private def defaults = Json.obj("details" -> none[String])
}

View File

@ -0,0 +1,34 @@
package lila.mod
import lila.db.api._
import lila.db.Implicits._
import tube.modlogTube
final class ModlogApi {
def engine(mod: String, user: String, v: Boolean) = add {
Modlog(mod, user.some, v.fold(Modlog.engine, Modlog.unengine))
}
def mute(mod: String, user: String, v: Boolean) = add {
Modlog(mod, user.some, v.fold(Modlog.mute, Modlog.unmute))
}
def ban(mod: String, user: String) = add {
Modlog(mod, user.some, Modlog.ipban)
}
def ipban(mod: String, ip: String) = add {
Modlog(mod, none, Modlog.ipban, ip.some)
}
def deletePost(mod: String, user: Option[String], author: Option[String], ip: Option[String], text: String) = add {
Modlog(mod, user, Modlog.deletePost, details = Some(
author.fold("")(_ + " ") + ip.fold("")(_ + " ") + text.take(140)
))
}
def recent = $find($query($select.all) limit 100 sort $sort.naturalDesc)
private def add(m: Modlog): Funit = $insert(m)
}

View File

@ -0,0 +1,12 @@
package lila
import lila.db.Tube
package object mod extends PackageObject with WithPlay {
object tube {
private[mod] implicit lazy val modlogTube =
Modlog.tube inColl Env.current.modlogColl
}
}

View File

@ -1,53 +0,0 @@
package lila.app
package mod
import user.{ User, UserRepo }
import elo.EloUpdater
import lobby.Messenger
import security.{ Firewall, UserSpy, Store SecurityStore }
import lila.common.Futuristic._
import scalaz.effects._
import play.api.libs.concurrent.Execution.Implicits._
final class ModApi(
logApi: ModlogApi,
userRepo: UserRepo,
userSpy: String IO[UserSpy],
firewall: Firewall,
eloUpdater: EloUpdater,
lobbyMessenger: Messenger) {
def adjust(mod: User, username: String): Funit = withUser(username) { user
logApi.engine(mod, user, !user.engine) zip
userRepo.toggleEngine(user.id) zip
eloUpdater.adjust(user) map toVoid
}
def mute(mod: User, username: String): Funit = for {
userOption userRepo byId username
_ ~userOption.map(user for {
_ userRepo toggleMute user.id
_ lobbyMessenger mute user.username doUnless user.isChatBan
_ logApi.mute(mod, user, !user.isChatBan)
} yield ())
} yield ()
def ban(mod: User, username: String): Funit = withUser(username) { user
userSpy(username) flatMap { spy
io(spy.ips foreach firewall.blockIp) >>
(lobbyMessenger mute user.username doUnless user.isChatBan) >>
logApi.ban(mod, user)
}
}
def ipban(mod: User, ip: String): Funit = for {
_ io(firewall blockIp ip)
_ logApi.ipban(mod, ip)
} yield ()
private def withUser(username: String)(op: User Funit): Funit = for {
userOption userRepo byId username
_ ~userOption.map(op)
} yield ()
}

View File

@ -1,36 +0,0 @@
package lila.app
package mod
import user.{ User, UserRepo }
import elo.EloUpdater
import lobby.Messenger
import core.Settings
import security.{ Firewall, UserSpy }
import com.mongodb.casbah.MongoCollection
import scalaz.effects.IO
final class ModEnv(
settings: Settings,
userRepo: UserRepo,
userSpy: String IO[UserSpy],
firewall: Firewall,
eloUpdater: EloUpdater,
lobbyMessenger: Messenger,
mongodb: String MongoCollection,
db: LilaDB) {
import settings._
lazy val modlogRepo = new ModlogRepo(db, ModlogCollectionModlog)
lazy val logApi = new ModlogApi(repo = modlogRepo)
lazy val api = new ModApi(
logApi = logApi,
userRepo = userRepo,
userSpy = userSpy,
firewall = firewall,
eloUpdater = eloUpdater,
lobbyMessenger = lobbyMessenger)
}

View File

@ -1,35 +0,0 @@
package lila.app
package mod
import user.User
import scala.concurrent.Future
final class ModlogApi(repo: ModlogRepo) {
def engine(mod: User, user: User, v: Boolean) = add {
Modlog(mod.id, user.id.some, v.fold(Modlog.engine, Modlog.unengine))
}
def mute(mod: User, user: User, v: Boolean) = add {
Modlog(mod.id, user.id.some, v.fold(Modlog.mute, Modlog.unmute))
}
def ban(mod: User, user: User) = add {
Modlog(mod.id, user.id.some, Modlog.ipban)
}
def ipban(mod: User, ip: String) = add {
Modlog(mod.id, none, Modlog.ipban, ip.some)
}
def deletePost(mod: User, userId: Option[String], author: Option[String], ip: Option[String], text: String) = add {
Modlog(mod.id, userId, Modlog.deletePost, details = Some(
author.fold("")(_ + " ") + ip.fold("")(_ + " ") + text.take(140)
))
}
def recent = repo recent 100
private def add(m: Modlog) = repo uncheckedInsert m
}

View File

@ -1,8 +0,0 @@
package lila.app
package mod
final class ModlogRepo(db: LilaDB, name: String)
extends mongodb.Coll[Modlog](db, name, Modlog.json) {
def recent(nb: Int): Fu[List[Modlog]] = find(query.sort(sortNaturalDesc), nb)
}

View File

@ -1,14 +0,0 @@
import play.api.libs.json._
import play.api.libs.functional.syntax._
object Bar {
case class Foo(a: Int, b: String)
object Foo // without this line, the program compiles
implicit val jsonReads = (
(__ \ 'a).read[Int] and
(__ \ 'b).read[String]
)(Foo.apply _)
}

View File

@ -41,6 +41,8 @@ final class Env(
lazy val forms = new DataForm(captcher = captcher)
lazy val userSpy = Store.userSpy _
def cli = new Cli
}

View File

@ -22,10 +22,10 @@ case class UserSpy(
object Store {
def save(sessionId: String, username: String, req: RequestHeader): Funit =
def save(sessionId: String, user: String, req: RequestHeader): Funit =
$insert(Json.obj(
"_id" -> sessionId,
"user" -> normalize(username),
"user" -> normalize(user),
"ip" -> ip(req),
"ua" -> ua(req),
"date" -> DateTime.now,
@ -42,24 +42,24 @@ object Store {
// useful when closing an account,
// we want to logout too
def deleteUsername(username: String): Funit = $update(
selectUser(username),
def deleteUsername(user: String): Funit = $update(
selectUser(user),
$set("up" -> false),
upsert = false,
multi = true)
def userSpy(username: String): Fu[UserSpy] = for {
objs $find(selectUser(username))
usernames explore(normalize(username))
def userSpy(user: String): Fu[UserSpy] = for {
objs $find(selectUser(user))
usernames explore(normalize(user))
} yield UserSpy(
ips = objs.map(_ str "ip").flatten.distinct,
uas = objs.map(_ str "ua").flatten.distinct,
otherUsernames = usernames
)
private def explore(username: String, withKnown: Set[String] = Set.empty): Fu[Set[String]] = {
val known = withKnown + username
newSiblings(username, known) flatMap { children
private def explore(user: String, withKnown: Set[String] = Set.empty): Fu[Set[String]] = {
val known = withKnown + user
newSiblings(user, known) flatMap { children
children.foldLeft(fuccess(children)) {
case (siblings, child) siblings flatMap { sibs
explore(child, known ++ sibs) map (sibs ++)
@ -68,13 +68,13 @@ object Store {
}
}
private def newSiblings(username: String, without: Set[String]): Fu[Set[String]] =
userIps(username) flatMap { ips
private def newSiblings(user: String, without: Set[String]): Fu[Set[String]] =
userIps(user) flatMap { ips
Future.traverse(ips)(usernamesByIp) map (_.flatten diff without)
}
private def userIps(username: String): Fu[Set[String]] =
$primitive(selectUser(username), "ip")(_.asOpt[String]) map (_.toSet)
private def userIps(user: String): Fu[Set[String]] =
$primitive(selectUser(user), "ip")(_.asOpt[String]) map (_.toSet)
private def usernamesByIp(ip: String): Fu[Set[String]] =
$primitive(Json.obj("ip" -> ip), "user")(_.asOpt[String]) map (_.toSet)

View File

@ -1,10 +1,8 @@
package lila.user
import scala.math.max
final class EloUpdater(floor: Int) {
def game(user: User, elo: Int, opponentElo: Int): Funit = max(elo, floor) |> { newElo
def game(user: User, elo: Int, opponentElo: Int): Funit = math.max(elo, floor) |> { newElo
UserRepo.setElo(user.id, newElo) >> HistoryRepo.addEntry(user.id, newElo, opponentElo.some)
}