appeal WIP
parent
c52a7d5f78
commit
8929e375ae
|
@ -91,10 +91,9 @@ final class Env(
|
|||
|
||||
def net = common.netConfig
|
||||
|
||||
val isProd = mode == Mode.Prod
|
||||
val isProdReally = isProd && net.isProd
|
||||
val isProd = mode == Mode.Prod && net.isProd
|
||||
val isDev = mode == Mode.Dev
|
||||
val isStage = config.get[Boolean]("app.stage")
|
||||
val isStage = mode == Mode.Prod && !net.isProd
|
||||
val explorerEndpoint = config.get[String]("explorer.endpoint")
|
||||
val tablebaseEndpoint = config.get[String]("explorer.tablebase.endpoint")
|
||||
|
||||
|
@ -121,7 +120,8 @@ final class Env(
|
|||
lazy val prizeTournamentMakers = memo.settingStore[UserIds](
|
||||
"prizeTournamentMakers ",
|
||||
default = UserIds(Nil),
|
||||
text = "User IDs who can make prize tournaments (arena & swiss) without a warning. Separated by commas.".some
|
||||
text =
|
||||
"User IDs who can make prize tournaments (arena & swiss) without a warning. Separated by commas.".some
|
||||
)
|
||||
|
||||
lazy val preloader = wire[mashup.Preload]
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
package controllers
|
||||
|
||||
import play.api.mvc.Result
|
||||
import lila.app._
|
||||
import lila.api.Context
|
||||
import views._
|
||||
import lila.report.Suspect
|
||||
|
||||
final class Appeal(env: Env) extends LilaController(env) {
|
||||
|
||||
|
@ -22,8 +25,59 @@ final class Appeal(env: Env) extends LilaController(env) {
|
|||
env.appeal.api.mine(me) map { appeal =>
|
||||
BadRequest(html.appeal2.home(appeal, err))
|
||||
},
|
||||
text =>
|
||||
env.appeal.api.post(text, me) inject Redirect(routes.Appeal.home()).flashSuccess
|
||||
text => env.appeal.api.post(text, me) inject Redirect(routes.Appeal.home()).flashSuccess
|
||||
)
|
||||
}
|
||||
|
||||
def show(username: String) =
|
||||
Secure(_.Appeals) { implicit ctx => me =>
|
||||
asMod(username) { (appeal, suspect) =>
|
||||
Ok(html.appeal2.show(appeal, suspect, env.appeal.forms.text)).fuccess
|
||||
}
|
||||
}
|
||||
|
||||
def reply(username: String) =
|
||||
SecureBody(_.Appeals) { implicit ctx => me =>
|
||||
asMod(username) { (appeal, suspect) =>
|
||||
implicit val req = ctx.body
|
||||
env.appeal.forms.text
|
||||
.bindFromRequest()
|
||||
.fold(
|
||||
err => BadRequest(html.appeal2.show(appeal, suspect, err)).fuccess,
|
||||
text =>
|
||||
for {
|
||||
_ <- env.appeal.api.reply(text, appeal, me)
|
||||
_ <- env.security.automaticEmail.onAppealReply(suspect.user)
|
||||
} yield Redirect(routes.Appeal.show(username)).flashSuccess
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
def act(username: String, action: String) =
|
||||
Secure(_.Appeals) { implicit ctx => me =>
|
||||
asMod(username) { (appeal, suspect) =>
|
||||
val res = action match {
|
||||
case "close" => env.appeal.api.close(appeal)
|
||||
case "mute" => env.appeal.api.mute(appeal)
|
||||
case "open" => env.appeal.api.open(appeal)
|
||||
case _ => funit
|
||||
}
|
||||
res inject Redirect(routes.Appeal.show(username)).flashSuccess
|
||||
}
|
||||
}
|
||||
|
||||
private def asMod(
|
||||
username: String
|
||||
)(f: (lila.appeal.Appeal, Suspect) => Fu[Result])(implicit ctx: Context): Fu[Result] =
|
||||
env.user.repo named username flatMap {
|
||||
_ ?? { user =>
|
||||
env.appeal.api get user flatMap {
|
||||
_ ?? { appeal =>
|
||||
f(appeal, Suspect(user)) dmap some
|
||||
}
|
||||
}
|
||||
}
|
||||
} flatMap {
|
||||
_.fold(notFound)(fuccess)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -549,7 +549,7 @@ abstract private[controllers] class LilaController(val env: Env)
|
|||
type RestoredUser = (Option[FingerPrintedUser], Option[UserModel])
|
||||
private def restoreUser(req: RequestHeader): Fu[RestoredUser] =
|
||||
env.security.api restoreUser req dmap {
|
||||
case Some(d) if !env.isProdReally =>
|
||||
case Some(d) if !env.isProd =>
|
||||
d.copy(user =
|
||||
d.user
|
||||
.addRole(lila.security.Permission.Beta.dbKey)
|
||||
|
|
|
@ -7,22 +7,17 @@ import lila.common.String.html.richText
|
|||
import play.api.data.Form
|
||||
import lila.appeal.Appeal
|
||||
import controllers.routes
|
||||
import lila.report.Suspect
|
||||
import lila.user.User
|
||||
import play.api.i18n.Lang
|
||||
|
||||
object appeal2 {
|
||||
|
||||
def home(appeal: Option[Appeal], textForm: Form[_])(implicit ctx: Context) =
|
||||
views.html.base.layout(
|
||||
moreCss = frag(
|
||||
cssTag("form3"),
|
||||
cssTag("appeal")
|
||||
),
|
||||
title = "Appeal"
|
||||
) {
|
||||
layout("Appeal") {
|
||||
main(cls := "page-small box box-pad page appeal")(
|
||||
appeal match {
|
||||
case Some(a) => myAppeal(a, textForm)
|
||||
case Some(a) => renderAppeal(a, textForm)
|
||||
case None => newAppeal(textForm)
|
||||
}
|
||||
)
|
||||
|
@ -33,11 +28,30 @@ object appeal2 {
|
|||
h1("Appeal a moderation decision"),
|
||||
renderHelp,
|
||||
div(cls := "body")(
|
||||
renderForm(textForm)
|
||||
renderForm(textForm, isNew = true)
|
||||
)
|
||||
)
|
||||
|
||||
def myAppeal(appeal: Appeal, textForm: Form[_])(implicit ctx: Context) =
|
||||
def show(appeal: Appeal, suspect: Suspect, textForm: Form[_])(implicit ctx: Context) =
|
||||
layout(s"Appeal by ${suspect.user.username}") {
|
||||
main(cls := "page-small box box-pad page appeal")(
|
||||
renderAppeal(appeal, textForm),
|
||||
postForm(action := routes.Appeal.close(suspect.user.username))(
|
||||
submitButton("Close")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private def layout(title: String)(body: Frag)(implicit ctx: Context) =
|
||||
views.html.base.layout(
|
||||
moreCss = frag(
|
||||
cssTag("form3"),
|
||||
cssTag("appeal")
|
||||
),
|
||||
title = title
|
||||
)(body)
|
||||
|
||||
private def renderAppeal(appeal: Appeal, textForm: Form[_])(implicit ctx: Context) =
|
||||
frag(
|
||||
h1(if (appeal.isOpen) "Ongoing appeal" else "Closed appeal"),
|
||||
standardFlash(),
|
||||
|
@ -52,7 +66,8 @@ object appeal2 {
|
|||
),
|
||||
div(cls := "appeal__msg__text")(richText(msg.text))
|
||||
)
|
||||
}
|
||||
},
|
||||
renderForm(textForm, isNew = false)
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -90,12 +105,12 @@ object appeal2 {
|
|||
private def renderUser(appeal: Appeal, userId: User.ID)(implicit lang: Lang) =
|
||||
userIdLink((if (appeal isAbout userId) userId else User.lichessId).some)
|
||||
|
||||
private def renderForm(form: Form[_])(implicit ctx: Context) =
|
||||
private def renderForm(form: Form[_], isNew: Boolean)(implicit ctx: Context) =
|
||||
postForm(action := routes.Appeal.post())(
|
||||
form3.globalError(form),
|
||||
form3.group(form("text"), trans.description())(
|
||||
form3.textarea(_)(rows := 8)
|
||||
form3.group(form("text"), if (isNew) "Create an appeal" else "Add something to the appeal")(
|
||||
form3.textarea(_)(rows := 6)
|
||||
),
|
||||
form3.action(form3.submit(trans.send()))
|
||||
form3.submit(trans.send())
|
||||
)
|
||||
}
|
||||
|
|
|
@ -172,7 +172,7 @@ object layout {
|
|||
metaThemeColor,
|
||||
st.headTitle {
|
||||
if (ctx.blind) "lichess"
|
||||
else if (isProd && !isStage) fullTitle | s"$title • lichess.org"
|
||||
else if (isProd) fullTitle | s"$title • lichess.org"
|
||||
else s"[dev] ${fullTitle | s"$title • lichess.dev"}"
|
||||
},
|
||||
cssTag("site"),
|
||||
|
@ -307,7 +307,7 @@ object layout {
|
|||
else ctx.isBot option botImage,
|
||||
a(href := "/")(
|
||||
"lichess",
|
||||
span(if (isProd && !isStage) ".org" else ".dev")
|
||||
span(if (isProd) ".org" else ".dev")
|
||||
)
|
||||
),
|
||||
ctx.blind option h2("Navigation"),
|
||||
|
|
|
@ -540,6 +540,9 @@ GET /report/:username/cheat-inquiry controllers.Report.currentCheatInquiry(us
|
|||
# Appeal
|
||||
GET /appeal controllers.Appeal.home
|
||||
POST /appeal controllers.Appeal.post
|
||||
GET /appeal/:username controllers.Appeal.show(username: String)
|
||||
POST /appeal/:username controllers.Appeal.reply(username: String)
|
||||
POST /appeal/:username/act/:action controllers.Appeal.act(username: String, action: String)
|
||||
|
||||
# Stats
|
||||
GET /stat/rating/distribution/:perf controllers.Stat.ratingDistribution(perf: String)
|
||||
|
|
|
@ -10,8 +10,8 @@ case class Appeal(
|
|||
createdAt: DateTime,
|
||||
updatedAt: DateTime
|
||||
) {
|
||||
def id = _id
|
||||
def isOpen = status != Appeal.Status.Closed
|
||||
def id = _id
|
||||
def isOpen = status != Appeal.Status.Closed
|
||||
def isAbout(userId: User.ID) = _id == userId
|
||||
|
||||
def post(text: String, by: User) =
|
||||
|
@ -24,6 +24,10 @@ case class Appeal(
|
|||
updatedAt = DateTime.now,
|
||||
status = if (by.id == _id && status == Appeal.Status.Read) Appeal.Status.Unread else status
|
||||
)
|
||||
|
||||
def close = copy(status = Appeal.Status.Closed)
|
||||
def open = copy(status = Appeal.Status.Read)
|
||||
def mute = copy(status = Appeal.Status.Muted)
|
||||
}
|
||||
|
||||
object Appeal {
|
||||
|
|
|
@ -12,6 +12,8 @@ final class AppealApi(
|
|||
|
||||
def mine(me: User): Fu[Option[Appeal]] = coll.byId[Appeal](me.id)
|
||||
|
||||
def get(user: User) = coll.byId[Appeal](user.id)
|
||||
|
||||
def post(text: String, me: User) =
|
||||
mine(me) flatMap {
|
||||
case None =>
|
||||
|
@ -34,4 +36,18 @@ final class AppealApi(
|
|||
val appeal = prev.post(text, me)
|
||||
coll.update.one($id(appeal.id), appeal) inject appeal
|
||||
}
|
||||
|
||||
def reply(text: String, prev: Appeal, mod: User) = {
|
||||
val appeal = prev.post(text, mod)
|
||||
coll.update.one($id(appeal.id), appeal) inject appeal
|
||||
}
|
||||
|
||||
def close(appeal: Appeal) =
|
||||
coll.update.one($id(appeal.id), appeal.close).void
|
||||
|
||||
def open(appeal: Appeal) =
|
||||
coll.update.one($id(appeal.id), appeal.open).void
|
||||
|
||||
def mute(appeal: Appeal) =
|
||||
coll.update.one($id(appeal.id), appeal.mute).void
|
||||
}
|
||||
|
|
|
@ -130,5 +130,17 @@ ${Mailgun.txt.serviceNote}
|
|||
)
|
||||
}
|
||||
|
||||
def onAppealReply(user: User): Funit = {
|
||||
val body = s"""Hello,
|
||||
|
||||
Your appeal has received a response from the moderation team: ${baseUrl}/appeal
|
||||
|
||||
$regards
|
||||
"""
|
||||
|
||||
lila.common.Bus.publish(SystemMsg(user.id, body), "msgSystemSend")
|
||||
funit
|
||||
}
|
||||
|
||||
private def userLang(user: User) = user.realLang | lila.i18n.defaultLang
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ object Permission {
|
|||
case object SetTitle extends Permission("SET_TITLE", List(UserSpy), "Set/unset title")
|
||||
case object SetEmail extends Permission("SET_EMAIL", List(UserSpy), "Set email address")
|
||||
case object SeeReport extends Permission("SEE_REPORT", "See reports")
|
||||
case object Appeals extends Permission("APPEAL", "Handle appeals")
|
||||
case object ModLog extends Permission("MOD_LOG", "See mod log")
|
||||
case object SeeInsight extends Permission("SEE_INSIGHT", "View player insights")
|
||||
case object PracticeConfig extends Permission("PRACTICE_CONFIG", "Configure practice")
|
||||
|
@ -80,6 +81,7 @@ object Permission {
|
|||
UserSpy,
|
||||
UserEvaluate,
|
||||
SeeReport,
|
||||
Appeals,
|
||||
ModLog,
|
||||
SeeInsight,
|
||||
UserSearch,
|
||||
|
@ -185,6 +187,7 @@ object Permission {
|
|||
),
|
||||
"Misc mod" -> List(
|
||||
SeeReport,
|
||||
Appeals,
|
||||
UserSearch,
|
||||
MonitoredMod,
|
||||
ModNote,
|
||||
|
|
Loading…
Reference in New Issue