Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
99fae7b
title verification form WIP
ornicar Jun 6, 2024
40b233b
title handlers
ornicar Jun 6, 2024
695597e
Merge branch 'master' into title-form
ornicar Jun 6, 2024
abe59ee
title request form WIP
ornicar Jun 6, 2024
307257f
better CMS internal API, create a new page with a preset key
ornicar Jun 7, 2024
7f5d45a
Merge branch 'master' into title-form
ornicar Jun 7, 2024
ccc4477
offer to create missing CMS page
ornicar Jun 7, 2024
7449f5d
title verification WIP
ornicar Jun 7, 2024
c68ac77
remove unused css classes
ornicar Jun 7, 2024
537875c
title verification WIP
ornicar Jun 7, 2024
416640f
verify title WIP
ornicar Jun 7, 2024
c6a267a
rename federationUrl
ornicar Jun 7, 2024
5d9e35e
refactor form string formatters
ornicar Jun 7, 2024
9040ab5
verify title WIP
ornicar Jun 7, 2024
5d35bd6
log the cropDialog upload error
ornicar Jun 7, 2024
223e06d
title verify image upload WIP
ornicar Jun 7, 2024
64490e7
title request form complete
ornicar Jun 7, 2024
2ca7673
title request cancel
ornicar Jun 7, 2024
30bb841
remove flashSuccess
ornicar Jun 8, 2024
cf20293
Merge branch 'master' into title-form
ornicar Jun 8, 2024
ed0220e
title requests for mods WIP, refactor PendingCounts
ornicar Jun 8, 2024
3d7b9eb
title verify mod WIP
ornicar Jun 8, 2024
b0f4838
simplify relay paginator json output
ornicar Jun 8, 2024
5770244
title verify mod WIP
ornicar Jun 8, 2024
f900025
title verify mod WIP
ornicar Jun 8, 2024
d5aebaf
better title request status and history
ornicar Jun 9, 2024
a4a5405
title request feedback PM
ornicar Jun 9, 2024
73cb520
title request sets mod permission
ornicar Jun 9, 2024
04b5b1d
move User.mapPlan where it's needed
ornicar Jun 9, 2024
1d3abfa
write a note on title confirm
ornicar Jun 9, 2024
50ffc45
title confirm flow tweaks
ornicar Jun 9, 2024
fb48c6b
delete title confirm pics after one month
ornicar Jun 9, 2024
439ea1d
mandatory feedback text
ornicar Jun 9, 2024
f409928
html-only form validation
ornicar Jun 9, 2024
e365d45
jump to next title confirm request
ornicar Jun 9, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/Env.scala
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ final class Env(
val challenge: lila.challenge.Env = wire[lila.challenge.Env]
val explorer: lila.explorer.Env = wire[lila.explorer.Env]
val fide: lila.fide.Env = wire[lila.fide.Env]
val title: lila.title.Env = wire[lila.title.Env]
val study: lila.study.Env = wire[lila.study.Env]
val studySearch: lila.studySearch.Env = wire[lila.studySearch.Env]
val learn: lila.learn.Env = wire[lila.learn.Env]
Expand Down
1 change: 1 addition & 0 deletions app/LilaComponents.scala
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ final class LilaComponents(
lazy val opening: Opening = wire[Opening]
lazy val cms: Cms = wire[Cms]
lazy val fide: Fide = wire[Fide]
lazy val titleVerify: TitleVerify = wire[TitleVerify]

// eagerly wire up all controllers
private val appealRouter: _root_.router.appeal.Routes = wire[_root_.router.appeal.Routes]
Expand Down
16 changes: 7 additions & 9 deletions app/controllers/Appeal.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ final class Appeal(env: Env, reportC: => report.Report, userC: => User) extends

def landing = Auth { ctx ?=> _ ?=>
if ctx.isAppealUser || isGranted(_.Appeals) then
FoundPage(env.api.cmsRender(lila.core.id.CmsPageKey("appeal-landing"))):
FoundPage(env.cms.renderKey("appeal-landing")):
views.site.page.lone
else notFound
}
Expand Down Expand Up @@ -47,14 +47,12 @@ final class Appeal(env: Env, reportC: => report.Report, userC: => User) extends
def queue(filterStr: Option[String] = None) = Secure(_.Appeals) { ctx ?=> me ?=>
val filter = env.appeal.api.modFilter.fromQuery(filterStr)
for
appeals <- env.appeal.api.myQueue(filter)
inquiries <- env.report.api.inquiries.allBySuspect
((scores, streamers), nbAppeals) <- reportC.getScores
_ <- env.user.lightUserApi.preloadMany(appeals.map(_.user.id))
markedByMap <- env.mod.logApi.wereMarkedBy(appeals.map(_.user.id))
page <- renderPage(
views.appeal.queue(appeals, inquiries, filter, markedByMap, scores, streamers, nbAppeals)
)
appeals <- env.appeal.api.myQueue(filter)
inquiries <- env.report.api.inquiries.allBySuspect
(scores, pending) <- reportC.getScores
_ <- env.user.lightUserApi.preloadMany(appeals.map(_.user.id))
markedByMap <- env.mod.logApi.wereMarkedBy(appeals.map(_.user.id))
page <- renderPage(views.appeal.queue(appeals, inquiries, filter, markedByMap, scores, pending))
yield Ok(page)
}

Expand Down
31 changes: 21 additions & 10 deletions app/controllers/Cms.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ final class Cms(env: Env) extends LilaController(env):
renderedPage <- renderPage(views.cms.index(pages))
yield Ok(renderedPage)

def createForm = Secure(_.Pages) { _ ?=> _ ?=>
Ok.async(views.cms.create(env.cms.form.create))
def createForm(key: Option[CmsPageKey]) = Secure(_.Pages) { _ ?=> _ ?=>
Ok.async(views.cms.create(env.cms.form.create, key))
}

def create = SecureBody(_.Pages) { _ ?=> me ?=>
bindForm(env.cms.form.create)(
err => BadRequest.async(views.cms.create(err)),
err => BadRequest.async(views.cms.create(err, none)),
data =>
val page = data.create(me)
api.create(page).inject(Redirect(routes.Cms.edit(page.id)).flashSuccess)
Expand Down Expand Up @@ -60,27 +60,38 @@ final class Cms(env: Env) extends LilaController(env):
val master = menuPage(CmsPageKey("master"))

def page(key: CmsPageKey, active: Option[String])(using Context) =
FoundPage(env.api.cmsRender(key)): p =>
FoundPage(env.cms.render(key)): p =>
active match
case None => views.site.page.lone(p)
case Some(name) => views.site.page.withMenu(name, p)

def lonePage(key: CmsPageKey) = Open:
Found(env.api.cmsRender(key)): p =>
p.canonicalPath.filter(_ != req.path && req.path == s"/page/$key") match
orCreateOrNotFound(key): page =>
page.canonicalPath.filter(_ != req.path && req.path == s"/page/$key") match
case Some(path) => Redirect(path)
case None =>
pageHit
Ok.async(views.site.page.lone(p))
Ok.async(views.site.page.lone(page))

def orCreateOrNotFound(key: CmsPageKey)(f: CmsPage.Render => Fu[Result])(using Context): Fu[Result] =
env.cms
.render(key)
.flatMap:
case Some(page) => f(page)
case None =>
import lila.ui.Context.ctxMe // no idea why this is needed here
if isGrantedOpt(_.Pages)
then Ok.async(views.cms.create(env.cms.form.create, key.some))
else notFound

def menuPage(key: CmsPageKey) = Open:
pageHit
FoundPage(env.api.cmsRender(key)):
FoundPage(env.cms.render(key)):
views.site.page.withMenu(key.value, _)

def source = Open:
pageHit
FoundPage(env.api.cmsRenderKey("source")):
FoundPage(env.cms.renderKey("source")):
views.site.page.source

def variantHome = Open:
Expand All @@ -94,5 +105,5 @@ final class Cms(env: Env) extends LilaController(env):
(for
variant <- Variant(key)
perfKey <- PerfKey.byVariant(variant)
yield FoundPage(env.api.cmsRenderKey(s"variant-${variant.key}")): p =>
yield FoundPage(env.cms.renderKey(s"variant-${variant.key}")): p =>
views.site.variant.show(p, variant, perfKey)) | notFound
2 changes: 1 addition & 1 deletion app/controllers/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ final class Main(

private def serveMobile(using Context) =
pageHit
FoundPage(env.api.cmsRenderKey("mobile-apk"))(views.mobile)
FoundPage(env.cms.renderKey("mobile-apk"))(views.mobile)

def dailyPuzzleSlackApp = Open:
Ok.page(views.site.ui.dailyPuzzleSlackApp)
Expand Down
11 changes: 6 additions & 5 deletions app/controllers/Mod.scala
Original file line number Diff line number Diff line change
Expand Up @@ -159,15 +159,16 @@ final class Mod(
bindForm(lila.user.UserForm.title)(
_ => redirect(username, mod = true),
title =>
for
_ <- modApi.setTitle(username, title)
_ <- title.isDefined.so(env.mailer.automaticEmail.onTitleSet(username))
yield
env.user.lightUserApi.invalidate(username.id)
doSetTitle(username.id, title).inject:
redirect(username, mod = false)
)
}

protected[controllers] def doSetTitle(userId: UserId, title: Option[chess.PlayerTitle])(using Me) = for
_ <- modApi.setTitle(userId, title)
_ <- title.isDefined.so(env.mailer.automaticEmail.onTitleSet(userId))
yield env.user.lightUserApi.invalidate(userId)

def setEmail(username: UserStr) = SecureBody(_.SetEmail) { ctx ?=> me ?=>
Found(env.user.repo.byId(username)): user =>
bindForm(env.security.forms.modEmail(user))(
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/RelayTour.scala
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ final class RelayTour(env: Env, apiC: => Api) extends LilaController(env):

private def page(key: String, menu: String) = Open:
pageHit
FoundPage(env.api.cmsRender(lila.core.id.CmsPageKey(key))): p =>
FoundPage(env.cms.renderKey(key)): p =>
views.relay.tour.page(p.title, views.cms.render(p), menu)

def form = Auth { ctx ?=> _ ?=>
Expand Down
22 changes: 14 additions & 8 deletions app/controllers/Report.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import lila.app.{ *, given }
import lila.common.HTTPRequest
import lila.core.id.ReportId
import lila.report.{ Mod as AsMod, Report as ReportModel, Reporter, Room, Suspect }
import lila.report.Room.Scores
import lila.mod.ui.PendingCounts

final class Report(env: Env, userC: => User, modC: => Mod) extends LilaController(env):

Expand All @@ -28,16 +30,20 @@ final class Report(env: Env, userC: => User, modC: => Mod) extends LilaControlle
else notFound
}

protected[controllers] def getScores =
api.maxScores.zip(env.streamer.api.approval.countRequests).zip(env.appeal.api.countUnread)
protected[controllers] def getScores: Future[(Scores, PendingCounts)] = (
api.maxScores,
env.streamer.api.approval.countRequests,
env.appeal.api.countUnread,
env.title.api.countPending
).mapN: (scores, streamers, appeals, titles) =>
(scores, PendingCounts(streamers, appeals, titles))

private def renderList(room: String)(using Context, Me) =
api.openAndRecentWithFilter(12, Room(room)).zip(getScores).flatMap {
case (reports, ((scores, streamers), appeals)) =>
env.user.lightUserApi.preloadMany(reports.flatMap(_.report.userIds)) >>
Ok.page:
val filteredReports = reports.filter(r => lila.report.Reason.isGranted(r.report.reason))
views.report.list(filteredReports, room, scores, streamers, appeals)
api.openAndRecentWithFilter(12, Room(room)).zip(getScores).flatMap { case (reports, (scores, pending)) =>
env.user.lightUserApi.preloadMany(reports.flatMap(_.report.userIds)) >>
Ok.page:
val filteredReports = reports.filter(r => lila.report.Reason.isGranted(r.report.reason))
views.report.list(filteredReports, room, scores, pending)
}

def inquiry(reportOrAppealId: String) = Secure(_.SeeReport) { _ ?=> me ?=>
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/Study.scala
Original file line number Diff line number Diff line change
Expand Up @@ -539,7 +539,7 @@ final class Study(

def staffPicks = Open:
pageHit
FoundPage(env.api.cmsRenderKey("studies-staff-picks")):
FoundPage(env.cms.renderKey("studies-staff-picks")):
views.study.staffPicks

def privateUnauthorizedText = Unauthorized("This study is now private")
Expand Down
130 changes: 130 additions & 0 deletions app/controllers/TitleVerify.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package controllers

import play.api.libs.json.*

import lila.app.{ *, given }
import lila.title.TitleRequest
import lila.core.id.{ TitleRequestId, CmsPageKey }

final class TitleVerify(env: Env, cmsC: => Cms, reportC: => report.Report, userC: => User, modC: => Mod)
extends LilaController(env):

private def api = env.title.api
private def ui = views.title.ui

def index = Auth { _ ?=> me ?=>
cmsC.orCreateOrNotFound(CmsPageKey("title-verify-index")): page =>
api.getCurrent.flatMap:
case Some(req) => Redirect(routes.TitleVerify.show(req.id))
case None => Ok.async(ui.index(page.title, views.site.page.pageContent(page)))
}

def form = Auth { _ ?=> _ ?=>
Ok.async(ui.create(env.title.form.create))
}

def create = AuthBody { _ ?=> _ ?=>
bindForm(env.title.form.create)(
err => BadRequest.async(ui.create(err)),
data =>
api
.create(data)
.map: req =>
Redirect(routes.TitleVerify.show(req.id))
)
}

def show(id: TitleRequestId) = Auth { _ ?=> me ?=>
Found(api.getForMe(id)): req =>
if req.userId.is(me)
then Ok.async(ui.edit(env.title.form.edit(req.data), req))
else
for
data <- getModData(req)
page <- renderPage(views.title.mod.show(req, data))
yield Ok(page)
}

private def getModData(req: TitleRequest)(using Context)(using me: Me) =
for
user <- env.user.api.byId(req.userId).orFail(s"User ${req.userId} not found")
users <- env.security.userLogins(user, 100)
logins <- userC.loginsTableData(user, users, 100)
fide <- req.data.fideId.so(env.fide.playerApi.fetch)
yield views.title.mod.ModData(
mod = me,
user = user,
fide = fide,
logins = logins,
renderIp = env.mod.ipRender.apply
)

def update(id: TitleRequestId) = AuthBody { _ ?=> me ?=>
Found(api.getForMe(id)): req =>
bindForm(env.title.form.create)(
err => BadRequest.async(ui.edit(err, req)),
data =>
api
.update(req, data)
.map: req =>
val redir = Redirect(routes.TitleVerify.show(req.id))
if req.status == TitleRequest.Status.building then redir
else redir.flashSuccess
)
}

def cancel(id: TitleRequestId) = Auth { _ ?=> me ?=>
Found(api.getForMe(id)): req =>
api
.delete(req)
.inject:
Redirect(routes.TitleVerify.index).flashSuccess
}

def image(id: TitleRequestId, tag: String) = AuthBody(parse.multipartFormData) { ctx ?=> me ?=>
Found(api.getForMe(id)): req =>
ctx.body.body.file("image") match
case Some(image) =>
limit.imageUpload(ctx.ip, rateLimited):
api.image
.upload(req, image, tag)
.inject(Ok)
.recover { case e: Exception =>
BadRequest(e.getMessage)
}
case None => api.image.delete(req, tag) >> Ok
}

def queue = Secure(_.SetTitle) { ctx ?=> me ?=>
for
reqs <- api.queue(30)
(scores, pending) <- reportC.getScores
page <- renderPage(views.title.mod.queue(reqs, scores, pending))
yield Ok(page)
}

def process(id: TitleRequestId) = SecureBody(_.SetTitle) { ctx ?=> me ?=>
Found(api.getForMe(id)): req =>
bindForm(env.title.form.process)(
err => Redirect(routes.TitleVerify.show(req.id)).flashFailure(err.toString),
data =>
for
req <- api.process(req, data)
_ <- req.approved.so(onApproved(req))
next <- api.queue(1).map(_.headOption)
yield Redirect(routes.TitleVerify.show((next | req).id)).flashSuccess
)
}

private def onApproved(req: TitleRequest)(using Context, Me) =
for
user <- env.user.api.byId(req.userId).orFail(s"User ${req.userId} not found")
_ <- modC.doSetTitle(user.id, req.data.title.some)
url = s"${env.net.baseUrl}${routes.TitleVerify.show(req.id)}"
note = s"Title verified: ${req.data.title}. Public: ${if req.data.public then "Yes" else "No"}. $url"
_ <- env.user.noteApi.write(user.id, note, modOnly = true, dox = false)
_ <- req.data.coach.so:
env.user.repo.addPermission(user.id, lila.core.perm.Permission.Coach)
_ <- req.data.coach.so:
env.mailer.automaticEmail.onBecomeCoach(user)
yield ()
9 changes: 4 additions & 5 deletions app/views/appeal/queue.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ package views.appeal
import lila.app.UiEnv.{ *, given }

import lila.appeal.Appeal
import lila.appeal.Appeal.Filter
import lila.report.Report.Inquiry

import Appeal.Filter
import lila.mod.ui.PendingCounts

object queue:

Expand All @@ -15,10 +15,9 @@ object queue:
filter: Option[Filter],
markedByMe: Set[UserId],
scores: lila.report.Room.Scores,
streamers: Int,
nbAppeals: Int
pending: PendingCounts
)(using Context) =
views.report.layout("appeal", scores, streamers, nbAppeals):
views.report.layout("appeal", scores, pending):
table(cls := "slist slist-pad see appeal-queue")(
thead(
tr(
Expand Down
Loading