implement mod module
parent
e6f53d613c
commit
48be1445fa
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ package captcha {
|
|||
|
||||
package lobby {
|
||||
case class TimelineEntry(rendered: String)
|
||||
case class Censor(username: String)
|
||||
}
|
||||
|
||||
package message {
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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) }
|
||||
}
|
|
@ -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])
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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 ()
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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 _)
|
||||
}
|
|
@ -41,6 +41,8 @@ final class Env(
|
|||
|
||||
lazy val forms = new DataForm(captcher = captcher)
|
||||
|
||||
lazy val userSpy = Store.userSpy _
|
||||
|
||||
def cli = new Cli
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue