appeal WIP

pull/7068/head
Thibault Duplessis 2020-07-25 19:50:11 +02:00
parent 51efdd2d34
commit c52a7d5f78
16 changed files with 237 additions and 92 deletions

View File

@ -71,6 +71,7 @@ final class LilaComponents(ctx: ApplicationLoader.Context)
lazy val account: Account = wire[Account] lazy val account: Account = wire[Account]
lazy val analyse: Analyse = wire[Analyse] lazy val analyse: Analyse = wire[Analyse]
lazy val api: Api = wire[Api] lazy val api: Api = wire[Api]
lazy val appeal: Appeal = wire[Appeal]
lazy val auth: Auth = wire[Auth] lazy val auth: Auth = wire[Auth]
lazy val blog: Blog = wire[Blog] lazy val blog: Blog = wire[Blog]
lazy val bookmark: Bookmark = wire[Bookmark] lazy val bookmark: Bookmark = wire[Bookmark]

View File

@ -1,20 +1,29 @@
package controllers package controllers
import play.api.data._
import play.api.data.Forms._
import play.api.mvc._
import lila.api.Context
import lila.app._ import lila.app._
import views._ import views._
final class Appeal(env: Env) extends LilaController(env) { final class Appeal(env: Env) extends LilaController(env) {
// def form = def home =
// Auth { implicit ctx => _ => Auth { implicit ctx => me =>
// env.appeal.forms.create map { env.appeal.api.mine(me) map { appeal =>
// case (form, captcha) => Ok(html.report.form(form, user, captcha)) Ok(html.appeal2.home(appeal, env.appeal.forms.text))
// } }
// } }
// }
def post =
AuthBody { implicit ctx => me =>
implicit val req = ctx.body
env.appeal.forms.text
.bindFromRequest()
.fold(
err =>
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
)
}
} }

View File

@ -17,8 +17,6 @@ trait StringHelper { self: NumberHelper =>
def urlencode(str: String): String = java.net.URLEncoder.encode(str, "US-ASCII") def urlencode(str: String): String = java.net.URLEncoder.encode(str, "US-ASCII")
def when(cond: Boolean, str: String) = cond ?? str
private val NumberFirstRegex = """(\d++)\s(.+)""".r private val NumberFirstRegex = """(\d++)\s(.+)""".r
private val NumberLastRegex = """\s(\d++)$""".r.unanchored private val NumberLastRegex = """\s(\d++)$""".r.unanchored

View File

@ -0,0 +1,101 @@
package views.html
import lila.api.Context
import lila.app.templating.Environment._
import lila.app.ui.ScalatagsTemplate._
import lila.common.String.html.richText
import play.api.data.Form
import lila.appeal.Appeal
import controllers.routes
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"
) {
main(cls := "page-small box box-pad page appeal")(
appeal match {
case Some(a) => myAppeal(a, textForm)
case None => newAppeal(textForm)
}
)
}
def newAppeal(textForm: Form[_])(implicit ctx: Context) =
frag(
h1("Appeal a moderation decision"),
renderHelp,
div(cls := "body")(
renderForm(textForm)
)
)
def myAppeal(appeal: Appeal, textForm: Form[_])(implicit ctx: Context) =
frag(
h1(if (appeal.isOpen) "Ongoing appeal" else "Closed appeal"),
standardFlash(),
renderHelp,
div(cls := "body")(
renderStatus(appeal),
appeal.msgs.map { msg =>
div(cls := "appeal__msg")(
div(cls := "appeal__msg__header")(
renderUser(appeal, msg.by),
momentFromNowOnce(msg.at)
),
div(cls := "appeal__msg__text")(richText(msg.text))
)
}
)
)
private def renderStatus(appeal: Appeal) =
div(cls := "appeal__status")(
appeal.status match {
case Appeal.Status.Closed => "The appeal is closed."
case Appeal.Status.Unread => "The appeal is being reviewed by the moderation team."
case Appeal.Status.Read => "The appeal is open and awaiting your input."
case Appeal.Status.Muted => "The appeal is on hold."
}
)
private def renderHelp =
div(cls := "appeal__help")(
p(
"If your account has been restricted for violation of ",
a(href := routes.Page.tos())("the Lichess rules"),
", and you are absolutely certain that you did not break ",
a(href := routes.Page.tos())("said rules"),
", then you may file an appeal here."
),
p(
"If you did break ",
a(href := routes.Page.tos())("the Lichess rules"),
", even once, then your account is lost. We don't have the luxury of being forgiving."
),
p(
strong("Do not lie in an appeal"),
". It would result in a lifetime ban, ",
"and the automatic termination of any future account you make."
)
)
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) =
postForm(action := routes.Appeal.post())(
form3.globalError(form),
form3.group(form("text"), trans.description())(
form3.textarea(_)(rows := 8)
),
form3.action(form3.submit(trans.send()))
)
}

View File

@ -50,7 +50,7 @@ lazy val modules = Seq(
analyse, mod, round, pool, lobby, setup, analyse, mod, round, pool, lobby, setup,
importer, tournament, simul, relation, report, pref, importer, tournament, simul, relation, report, pref,
evaluation, chat, puzzle, tv, coordinate, blog, evaluation, chat, puzzle, tv, coordinate, blog,
history, video, shutup, push, history, video, shutup, push, appeal,
playban, insight, perfStat, slack, quote, challenge, playban, insight, perfStat, slack, quote, challenge,
study, studySearch, fishnet, explorer, learn, plan, study, studySearch, fishnet, explorer, learn, plan,
event, coach, practice, evalCache, irwin, event, coach, practice, evalCache, irwin,
@ -392,6 +392,11 @@ lazy val report = module("report",
reactivemongo.bundle reactivemongo.bundle
) )
lazy val appeal = module("appeal",
Seq(common, db, user),
reactivemongo.bundle
)
lazy val explorer = module("explorer", lazy val explorer = module("explorer",
Seq(common, db, game, importer), Seq(common, db, game, importer),
reactivemongo.bundle reactivemongo.bundle

View File

@ -538,7 +538,8 @@ POST /report/:id/xfiles controllers.Report.xfiles(id: String)
GET /report/:username/cheat-inquiry controllers.Report.currentCheatInquiry(username: String) GET /report/:username/cheat-inquiry controllers.Report.currentCheatInquiry(username: String)
# Appeal # Appeal
GET /appeal controllers.Appeal.form GET /appeal controllers.Appeal.home
POST /appeal controllers.Appeal.post
# Stats # Stats
GET /stat/rating/distribution/:perf controllers.Stat.ratingDistribution(perf: String) GET /stat/rating/distribution/:perf controllers.Stat.ratingDistribution(perf: String)

View File

@ -4,12 +4,27 @@ import lila.user.User
import org.joda.time.DateTime import org.joda.time.DateTime
case class Appeal( case class Appeal(
_id: User.ID, _id: User.ID,
msgs: List[AppealMsg], msgs: Vector[AppealMsg],
status: Appeal.Status, status: Appeal.Status,
createdAt: DateTime, createdAt: DateTime,
updatedAt: DateTime updatedAt: DateTime
) ) {
def id = _id
def isOpen = status != Appeal.Status.Closed
def isAbout(userId: User.ID) = _id == userId
def post(text: String, by: User) =
copy(
msgs = msgs :+ AppealMsg(
by = by.id,
text = text,
at = DateTime.now
),
updatedAt = DateTime.now,
status = if (by.id == _id && status == Appeal.Status.Read) Appeal.Status.Unread else status
)
}
object Appeal { object Appeal {
@ -18,17 +33,17 @@ object Appeal {
} }
object Status { object Status {
case object Unread extends Status case object Unread extends Status
case object Read extends Status case object Read extends Status
case object Closed extends Status case object Closed extends Status
case object Muted extends Status case object Muted extends Status
val all = List[Status](Unread, Read, Closed, Muted) val all = List[Status](Unread, Read, Closed, Muted)
def apply(key: String) = all.find(_.key == key) def apply(key: String) = all.find(_.key == key)
} }
} }
case class AppealMsg( case class AppealMsg(
by: User.ID, by: User.ID,
text: String, text: String,
at: DateTime at: DateTime
) )

View File

@ -1,9 +1,37 @@
package lila.appeal package lila.appeal
import lila.db.dsl._ import lila.db.dsl._
import lila.user.User
import org.joda.time.DateTime
final class AppealApi( final class AppealApi(
coll: Coll coll: Coll
)(implicit ec: scala.concurrent.ExecutionContext) { )(implicit ec: scala.concurrent.ExecutionContext) {
import BsonHandlers._
def mine(me: User): Fu[Option[Appeal]] = coll.byId[Appeal](me.id)
def post(text: String, me: User) =
mine(me) flatMap {
case None =>
val appeal =
Appeal(
_id = me.id,
msgs = Vector(
AppealMsg(
by = me.id,
text = text,
at = DateTime.now
)
),
status = Appeal.Status.Unread,
createdAt = DateTime.now,
updatedAt = DateTime.now
)
coll.insert.one(appeal) inject appeal
case Some(prev) =>
val appeal = prev.post(text, me)
coll.update.one($id(appeal.id), appeal) inject appeal
}
} }

View File

@ -2,66 +2,10 @@ package lila.appeal
import play.api.data._ import play.api.data._
import play.api.data.Forms._ import play.api.data.Forms._
import play.api.data.validation._
import scala.concurrent.duration._
import lila.user.User final class AppealForm {
final class DataForm { val text = Form(
single("text" -> nonEmptyText)
// val create = Form( )
// mapping(
// "username" -> lila.user.DataForm.historicalUsernameField.verifying(
// "Unknown username", {
// blockingFetchUser(_).isDefined
// }
// ),
// "reason" -> text.verifying("error.required", Reason.keys contains _),
// "text" -> text(minLength = 5, maxLength = 2000),
// "gameId" -> text,
// "move" -> text
// )({
// case (username, reason, text, gameId, move) =>
// ReportSetup(
// user = blockingFetchUser(username) err "Unknown username " + username,
// reason = reason,
// text = text,
// gameId = gameId,
// move = move
// )
// })(_.export.some).verifying(captchaFailMessage, validateCaptcha _).verifying(cheatLinkConstraint)
// )
// def createWithCaptcha = withCaptcha(create)
// val flag = Form(
// mapping(
// "username" -> lila.user.DataForm.historicalUsernameField,
// "resource" -> nonEmptyText,
// "text" -> text(minLength = 3, maxLength = 140)
// )(ReportFlag.apply)(ReportFlag.unapply)
// )
// private def blockingFetchUser(username: String) =
// lightUserAsync(User normalize username).await(1 second, "reportUser")
// }
// private[report] case class ReportFlag(
// username: String,
// resource: String,
// text: String
// )
// private[report] case class ReportSetup(
// user: LightUser,
// reason: String,
// text: String,
// gameId: String,
// move: String
// ) {
// def suspect = SuspectId(user.id)
// def export = (user.name, reason, text, gameId, move)
} }

View File

@ -15,6 +15,6 @@ private[appeal] object BsonHandlers {
s => BSONString(s.key) s => BSONString(s.key)
) )
// implicit val appealMsgHandler = Macros.handler[AppealMsg] implicit val appealMsgHandler = Macros.handler[AppealMsg]
implicit val appealHandler = Macros.handler[Appeal] implicit val appealHandler = Macros.handler[Appeal]
} }

View File

@ -12,7 +12,7 @@ final class Env(
private val coll = db(CollName("appeal")) private val coll = db(CollName("appeal"))
// lazy val forms = wire[ClasForm] lazy val forms = wire[AppealForm]
lazy val api: AppealApi = wire[AppealApi] lazy val api: AppealApi = wire[AppealApi]
} }

View File

@ -0,0 +1,35 @@
.appeal {
&__help {
margin-bottom: 2em;
}
&__status {
@extend %box-radius;
padding: 1em 1.5em;
margin-bottom: 2em;
background: $c-bg-zebra;
&::before {
@extend %data-icon;
content: '';
font-size: 2em;
margin-right: .4em;
}
}
&__msg {
padding: 1em 1.5em;
margin-bottom: 2em;
border-left: 10px solid $c-bg-zebra;
&__header {
@extend %flex-between;
margin-bottom: 2em;
}
&__ text {}
}
}

View File

@ -0,0 +1,2 @@
@import "../../../common/css/plugin";
@import "../appeal";

View File

@ -0,0 +1,2 @@
@import '../../../common/css/theme/dark';
@import 'appeal';

View File

@ -0,0 +1,2 @@
@import '../../../common/css/theme/light';
@import 'appeal';

View File

@ -0,0 +1,2 @@
@import '../../../common/css/theme/transp';
@import 'appeal';