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 analyse: Analyse = wire[Analyse]
lazy val api: Api = wire[Api]
lazy val appeal: Appeal = wire[Appeal]
lazy val auth: Auth = wire[Auth]
lazy val blog: Blog = wire[Blog]
lazy val bookmark: Bookmark = wire[Bookmark]

View File

@ -1,20 +1,29 @@
package controllers
import play.api.data._
import play.api.data.Forms._
import play.api.mvc._
import lila.api.Context
import lila.app._
import views._
final class Appeal(env: Env) extends LilaController(env) {
// def form =
// Auth { implicit ctx => _ =>
// env.appeal.forms.create map {
// case (form, captcha) => Ok(html.report.form(form, user, captcha))
// }
// }
// }
def home =
Auth { implicit ctx => me =>
env.appeal.api.mine(me) map { appeal =>
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 when(cond: Boolean, str: String) = cond ?? str
private val NumberFirstRegex = """(\d++)\s(.+)""".r
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,
importer, tournament, simul, relation, report, pref,
evaluation, chat, puzzle, tv, coordinate, blog,
history, video, shutup, push,
history, video, shutup, push, appeal,
playban, insight, perfStat, slack, quote, challenge,
study, studySearch, fishnet, explorer, learn, plan,
event, coach, practice, evalCache, irwin,
@ -392,6 +392,11 @@ lazy val report = module("report",
reactivemongo.bundle
)
lazy val appeal = module("appeal",
Seq(common, db, user),
reactivemongo.bundle
)
lazy val explorer = module("explorer",
Seq(common, db, game, importer),
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)
# Appeal
GET /appeal controllers.Appeal.form
GET /appeal controllers.Appeal.home
POST /appeal controllers.Appeal.post
# Stats
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
case class Appeal(
_id: User.ID,
msgs: List[AppealMsg],
status: Appeal.Status,
createdAt: DateTime,
updatedAt: DateTime
)
_id: User.ID,
msgs: Vector[AppealMsg],
status: Appeal.Status,
createdAt: 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 {
@ -18,17 +33,17 @@ object Appeal {
}
object Status {
case object Unread extends Status
case object Read extends Status
case object Read extends Status
case object Closed extends Status
case object Muted extends Status
val all = List[Status](Unread, Read, Closed, Muted)
case object Muted extends Status
val all = List[Status](Unread, Read, Closed, Muted)
def apply(key: String) = all.find(_.key == key)
}
}
case class AppealMsg(
by: User.ID,
text: String,
at: DateTime
by: User.ID,
text: String,
at: DateTime
)

View File

@ -1,9 +1,37 @@
package lila.appeal
import lila.db.dsl._
import lila.user.User
import org.joda.time.DateTime
final class AppealApi(
coll: Coll
coll: Coll
)(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.Forms._
import play.api.data.validation._
import scala.concurrent.duration._
import lila.user.User
final class AppealForm {
final class DataForm {
// 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)
val text = Form(
single("text" -> nonEmptyText)
)
}

View File

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

View File

@ -12,7 +12,7 @@ final class Env(
private val coll = db(CollName("appeal"))
// lazy val forms = wire[ClasForm]
lazy val forms = wire[AppealForm]
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';