refine AccountInfo/UserModView permissions

This commit is contained in:
Thibault Duplessis
2026-04-20 17:59:02 +02:00
parent f2085d8d16
commit 236637cf63
12 changed files with 150 additions and 149 deletions
+1 -1
View File
@@ -104,7 +104,7 @@ final class Clas(env: Env, authC: Auth) extends LilaController(env):
)
yield Ok(page),
orDefault = _ =>
isGranted(_.UserModView).so(FoundPage(env.clas.api.clas.byId(id)): clas =>
isGranted(_.AccountInfo).so(FoundPage(env.clas.api.clas.byId(id)): clas =>
env.clas.api.student.allWithUsers(clas).flatMap { students =>
env.user.api.withPerfsAndEmails(students.map(_.user)).map {
views.mod.search.clas(clas, _)
+5 -4
View File
@@ -163,9 +163,9 @@ final class User(
.redirect(username.value)
.flatMap:
case Some(url) => Redirect(url).toFuccess
case None if isGrantedOpt(_.UserModView) => ctx.useMe(modC.searchTerm(username.value))
case None if isGrantedOpt(_.AccountInfo) => ctx.useMe(modC.searchTerm(username.value))
case None => notFound(true)
case Some(u) if u.enabled.yes || isGrantedOpt(_.UserModView) => f(u)
case Some(u) if u.enabled.yes || isGrantedOpt(_.AccountInfo) => f(u)
case u => notFound(u.isEmpty)
def showMini(username: UserStr) = Open:
@@ -173,7 +173,7 @@ final class User(
ctx.userId
.so(relationApi.fetchRelation(_, user.id))
.flatMap: relation =>
if user.enabled.yes || isGrantedOpt(_.UserModView)
if user.enabled.yes || isGrantedOpt(_.AccountInfo)
then
(
ctx.userId.so(relationApi.fetchBlocks(user.id, _)),
@@ -415,7 +415,8 @@ final class User(
.preloadMany(as.games.flatMap(_.userIds))
.inject(ui.assessments(user, as))
val oauthTokens = env.oAuth.tokenApi.modRelevantTokens(user.id).map(views.user.mod.oauthTokens)
val oauthTokens = isGranted(_.AccountInfo).so:
env.oAuth.tokenApi.modRelevantTokens(user.id).map(views.user.mod.oauthTokens)
val teacher = isGranted(_.AccountInfo).so:
env.clas.api.clas.countOf(user).map(ui.teacher(user))
+1 -1
View File
@@ -22,7 +22,7 @@ def communication(
.js(isGranted(_.UserModView).option(Esm("mod.user"))):
main(id := "communication", cls := "box box-pad")(
commUi.commsHeader(u, priv),
isGranted(_.UserModView).option:
isGranted(_.AccountInfo).option:
frag(
div(cls := "mod-zone mod-zone-full none"),
views.user.mod.otherUsers(u, logins, appeals)(cls := "mod-zone communication__logins")
+3 -3
View File
@@ -144,9 +144,9 @@ object header:
case _ =>
val profile = u.profileOrDefault
val hideTroll = u.marks.troll && ctx.isnt(u)
val showProfile = ctx.kid.no && u.kid.no && !hideTroll || isGranted(_.UserModView)
val showProfile = ctx.kid.no && u.kid.no && !hideTroll || isGranted(_.AccountInfo)
div(id := "us_profile")(
if info.ratingChart.isDefined && (!u.lame || ctx.is(u) || isGranted(_.UserModView)) then
if info.ratingChart.isDefined && (!u.lame || ctx.is(u) || isGranted(_.AccountInfo)) then
views.user.perfStat.ratingHistoryContainer
else (ctx.is(u) && u.count.game < 10).option(ui.newPlayer(u)),
div(cls := "profile-side")(
@@ -162,7 +162,7 @@ object header:
.map(strong(cls := List("name" -> true, "muted" -> hideTroll))(_)),
info.publicFideId.map: id =>
p(a(href := routes.Fide.show(id, u.username.value))("FIDE player #" + id)),
(showLinks && showProfile || isGranted(_.UserModView))
(showLinks && showProfile || isGranted(_.AccountInfo))
.so(profile.nonEmptyBio)
.map: bio =>
p(cls := List("bio" -> true, "muted" -> hideTroll))(richText(bio, nl2br = true)),
+1 -1
View File
@@ -34,7 +34,7 @@ object page:
)
.js(pageModule(info))
.js(esModules())
.js(isGranted(_.UserModView).option(esmInit("mod.autolink")))
.js(isGranted(_.AccountInfo).option(esmInit("mod.autolink")))
.css("user.show")
.css(isGranted(_.UserModView).option("mod.user"))
.flag(_.noRobots, !indexable(u)):
+2 -2
View File
@@ -12,8 +12,8 @@ final class AppealUi(helpers: Helpers):
Page(title)
.css("bits.form3")
.css("bits.appeal")
.css(Granter.opt(_.UserModView).option("mod.user"))
.js(esmInitBit("appeal") ++ Granter.opt(_.UserModView).so(Esm("mod.user")))
.css(Granter.opt(_.Appeals).option("mod.user"))
.js(esmInitBit("appeal") ++ Granter.opt(_.Appeals).so(Esm("mod.user")))
def renderMark(suspect: User)(using ctx: Context) =
val query = Granter.opt(_.Appeals).so(ctx.req.queryString.toMap)
+130 -130
View File
@@ -48,154 +48,154 @@ final class ModUserUi(helpers: Helpers, modUi: ModUi, mailerEventsUrl: Url):
)(using Context, Me): Frag =
mzSection("actions")(
div(cls := "btn-rack")(
Granter.opt(_.ModMessage).option {
postForm(action := routes.Mod.spontaneousInquiry(u.username), title := "Start an inquiry")(
Granter(_.ModMessage).option:
postForm(action := routes.Mod.spontaneousInquiry(u.username), title := "Start an inquiry"):
submitButton(cls := "btn-rack__btn inquiry", title := "Hotkey: i")(i)
)
},
Granter.opt(_.UserEvaluate).option {
,
Granter(_.UserEvaluate).option:
postForm(
action := routes.Mod.refreshUserAssess(u.username),
title := "Collect data and ask irwin and Kaladin",
cls := "xhr"
):
submitButton(cls := "btn-rack__btn")("Evaluate")
},
Granter.opt(_.GamesModView).option {
,
Granter(_.GamesModView).option:
a(
cls := "btn-rack__btn",
href := routes.GameMod.index(u.username),
title := "View games"
)("Games")
},
Granter.opt(_.Shadowban).option {
,
Granter(_.Shadowban).option:
a(
cls := "btn-rack__btn",
href := routes.Mod.communicationPublic(u.id),
title := "View communications"
)("Comms")
}
),
div(cls := "btn-rack")(
postForm(
action := routes.Mod.alt(u.username, !u.marks.alt),
title := "Preemptively close unauthorized alt.",
cls := "xhr"
):
submitButton(
cls := List(
"btn-rack__btn" -> true,
"active" -> u.marks.alt
),
ModUserTableUi.canCloseAlt.not.option(disabled)
)("Alt")
,
postForm(
action := routes.Mod.engine(u.username, !u.marks.engine),
title := "This user is clearly cheating.",
cls := "xhr"
):
submitButton(
cls := List("btn-rack__btn" -> true, "active" -> u.marks.engine),
Granter.opt(_.MarkEngine).not.option(disabled)
)("Engine")
,
postForm(
action := routes.Mod.booster(u.username, !u.marks.boost),
title := "Marks the user as a booster or sandbagger.",
cls := "xhr"
):
submitButton(
cls := List("btn-rack__btn" -> true, "active" -> u.marks.boost),
Granter.opt(_.MarkBooster).not.option(disabled)
)("Booster")
,
frag(
Granter(_.AccountInfo).option:
div(cls := "btn-rack")(
postForm(
action := routes.Mod.troll(u.username, !u.marks.troll),
title := "Enable/disable communication features for this user.",
action := routes.Mod.alt(u.username, !u.marks.alt),
title := "Preemptively close unauthorized alt.",
cls := "xhr"
)(
):
submitButton(
cls := List("btn-rack__btn" -> true, "active" -> u.marks.troll),
Granter.opt(_.Shadowban).not.option(disabled)
)("Shadowban")
),
u.marks.troll.option:
frag(
postForm(
action := routes.Mod.deletePmsAndChats(u.username),
title := "Delete all PMs and public chat messages",
cls := "xhr"
):
submitButton(
cls := "btn-rack__btn yes-no-confirm",
Granter.opt(_.Shadowban).not.option(disabled)
cls := List(
"btn-rack__btn" -> true,
"active" -> u.marks.alt
),
ModUserTableUi.canCloseAlt.not.option(disabled)
)("Alt")
,
postForm(
action := routes.Mod.engine(u.username, !u.marks.engine),
title := "This user is clearly cheating.",
cls := "xhr"
):
submitButton(
cls := List("btn-rack__btn" -> true, "active" -> u.marks.engine),
Granter(_.MarkEngine).not.option(disabled)
)("Engine")
,
postForm(
action := routes.Mod.booster(u.username, !u.marks.boost),
title := "Marks the user as a booster or sandbagger.",
cls := "xhr"
):
submitButton(
cls := List("btn-rack__btn" -> true, "active" -> u.marks.boost),
Granter(_.MarkBooster).not.option(disabled)
)("Booster")
,
frag(
postForm(
action := routes.Mod.troll(u.username, !u.marks.troll),
title := "Enable/disable communication features for this user.",
cls := "xhr"
)(
submitButton(
cls := List("btn-rack__btn" -> true, "active" -> u.marks.troll),
Granter(_.Shadowban).not.option(disabled)
)("Shadowban")
),
u.marks.troll.option:
frag(
postForm(
action := routes.Mod.deletePmsAndChats(u.username),
title := "Delete all PMs and public chat messages",
cls := "xhr"
):
"Clear PMs & chats"
,
postForm(
action := routes.Mod.isolate(u.username, !u.marks.isolate),
title := "Isolate user by preventing all PMs, follows and challenges",
cls := "xhr"
)(
submitButton(
cls := List("btn-rack__btn yes-no-confirm" -> true, "active" -> u.marks.isolate),
Granter.opt(_.Shadowban).not.option(disabled)
)("Isolate")
submitButton(
cls := "btn-rack__btn yes-no-confirm",
Granter(_.Shadowban).not.option(disabled)
):
"Clear PMs & chats"
,
postForm(
action := routes.Mod.isolate(u.username, !u.marks.isolate),
title := "Isolate user by preventing all PMs, follows and challenges",
cls := "xhr"
)(
submitButton(
cls := List("btn-rack__btn yes-no-confirm" -> true, "active" -> u.marks.isolate),
Granter(_.Shadowban).not.option(disabled)
)("Isolate")
)
)
)
),
postForm(
action := routes.Mod.kid(u.username, !u.kid.value),
title := "Activate kid mode if not already the case",
cls := "xhr"
):
submitButton(
cls := List("btn-rack__btn yes-no-confirm" -> true, "active" -> u.kid.yes),
Granter.opt(_.SetKidMode).not.option(disabled)
)("Kid")
,
postForm(
action := routes.Mod.rankban(u.username, !u.marks.rankban),
title := "Include/exclude this user from the rankings.",
cls := "xhr"
):
submitButton(
cls := List("btn-rack__btn" -> true, "active" -> u.marks.rankban),
Granter.opt(_.RemoveRanking).not.option(disabled)
)("Rankban")
,
postForm(
action := routes.Mod.arenaBan(u.username, !u.marks.arenaBan),
title := "Enable/disable this user from joining all arenas.",
cls := "xhr"
):
submitButton(
cls := List("btn-rack__btn" -> true, "active" -> u.marks.arenaBan),
Granter.opt(_.ArenaBan).not.option(disabled)
)("Arena ban")
,
postForm(
action := routes.Mod.prizeban(u.username, !u.marks.prizeban),
title := "Enable/disable this user from joining prized tournaments.",
cls := "xhr"
):
submitButton(
cls := List("btn-rack__btn" -> true, "active" -> u.marks.prizeban),
Granter.opt(_.PrizeBan).not.option(disabled)
)("Prizeban")
,
postForm(
action := routes.Mod.reportban(u.username, !u.marks.reportban),
title := "Enable/disable the report feature for this user.",
cls := "xhr"
):
submitButton(
cls := List("btn-rack__btn" -> true, "active" -> u.marks.reportban),
Granter.opt(_.ReportBan).not.option(disabled)
)("Reportban")
),
),
postForm(
action := routes.Mod.kid(u.username, !u.kid.value),
title := "Activate kid mode if not already the case",
cls := "xhr"
):
submitButton(
cls := List("btn-rack__btn yes-no-confirm" -> true, "active" -> u.kid.yes),
Granter(_.SetKidMode).not.option(disabled)
)("Kid")
,
postForm(
action := routes.Mod.rankban(u.username, !u.marks.rankban),
title := "Include/exclude this user from the rankings.",
cls := "xhr"
):
submitButton(
cls := List("btn-rack__btn" -> true, "active" -> u.marks.rankban),
Granter(_.RemoveRanking).not.option(disabled)
)("Rankban")
,
postForm(
action := routes.Mod.arenaBan(u.username, !u.marks.arenaBan),
title := "Enable/disable this user from joining all arenas.",
cls := "xhr"
):
submitButton(
cls := List("btn-rack__btn" -> true, "active" -> u.marks.arenaBan),
Granter(_.ArenaBan).not.option(disabled)
)("Arena ban")
,
postForm(
action := routes.Mod.prizeban(u.username, !u.marks.prizeban),
title := "Enable/disable this user from joining prized tournaments.",
cls := "xhr"
):
submitButton(
cls := List("btn-rack__btn" -> true, "active" -> u.marks.prizeban),
Granter(_.PrizeBan).not.option(disabled)
)("Prizeban")
,
postForm(
action := routes.Mod.reportban(u.username, !u.marks.reportban),
title := "Enable/disable the report feature for this user.",
cls := "xhr"
):
submitButton(
cls := List("btn-rack__btn" -> true, "active" -> u.marks.reportban),
Granter(_.ReportBan).not.option(disabled)
)("Reportban")
)
,
Granter
.opt(_.CloseAccount)
.option(
@@ -219,11 +219,11 @@ final class ModUserUi(helpers: Helpers, modUi: ModUi, mailerEventsUrl: Url):
cls := "xhr"
)(submitButton(cls := "btn-rack__btn active")("Closed"))
,
Granter.opt(_.GdprErase).option(gdprEraseForm(u))
Granter(_.GdprErase).option(gdprEraseForm(u))
)
)
),
(u.totpSecret.isDefined).option:
(u.totpSecret.isDefined && Granter(_.AccountInfo)).option:
div(cls := "btn-rack")(
postForm(
action := routes.Mod.disableTwoFactor(u.username),
@@ -232,7 +232,7 @@ final class ModUserUi(helpers: Helpers, modUi: ModUi, mailerEventsUrl: Url):
):
submitButton(
cls := "btn-rack__btn yes-no-confirm",
Granter.opt(_.DisableTwoFactor).not.option(disabled)
Granter(_.DisableTwoFactor).not.option(disabled)
)(
"Disable 2FA"
)
@@ -249,7 +249,7 @@ final class ModUserUi(helpers: Helpers, modUi: ModUi, mailerEventsUrl: Url):
)
)
},
Granter.opt(_.SetTitle).option {
Granter(_.SetTitle).option {
postForm(cls := "fide-title", action := routes.Mod.setTitle(u.username))(
form3.select(
lila.user.UserForm.title.fill(u.title)("title"),
@@ -293,7 +293,7 @@ final class ModUserUi(helpers: Helpers, modUi: ModUi, mailerEventsUrl: Url):
submitButton(cls := "btn-rack__btn yes-no-confirm")("Blank password")
)
,
Granter.opt(_.FreePatron).option {
Granter(_.FreePatron).option {
postForm(
action := routes.Mod.freePatron(u.username),
title := "Give free Patron wings for a month",
+1 -1
View File
@@ -40,7 +40,7 @@ final class PerfStatApi(
.withPerfs(name.id)
.flatMap:
_.filter: u =>
(u.enabled.yes && (!u.lame || me.exists(_.is(u.user)))) || me.soUse(Granter(_.UserModView))
(u.enabled.yes && (!u.lame || me.exists(_.is(u.user)))) || me.soUse(Granter(_.AccountInfo))
.filter: u =>
!u.isBot || (perfKey != PerfKey.ultraBullet)
.traverse: u =>
+1 -1
View File
@@ -126,7 +126,7 @@ final class TeamApi(
.teamIdsList(of.id)
.map(_.take(Team.maxJoin(of).value))
.flatMap: allIds =>
if Granter(_.UserModView) then fuccess(allIds)
if Granter(_.AccountInfo) then fuccess(allIds)
else
allIds.nonEmpty.so:
teamRepo.filterHideMembers(allIds).flatMap { hiddenIds =>
+2 -2
View File
@@ -32,8 +32,8 @@ final class TitleModUi(helpers: Helpers)(ui: TitleUi)(using NetDomain):
Granter.opt(_.TitleRequest).option(a(href := ui.thumbnail.raw(id))(ui.thumbnail(id.some, 500)))
Page(s"${user.username}'s title verification")
.css("bits.titleRequest")
.css(Granter.opt(_.UserModView).option("mod.user"))
.js(esmInitBit("titleRequest") ++ Granter.opt(_.UserModView).so(Esm("mod.user"))):
.css(Granter.opt(_.AccountInfo).option("mod.user"))
.js(esmInitBit("titleRequest") ++ Granter.opt(_.AccountInfo).so(Esm("mod.user"))):
main(cls := "box box-pad page title-mod")(
div(cls := "box__top")(
h1("Title verification by ", userLink(user), " ", showStatus(req.status)),
+2 -2
View File
@@ -40,7 +40,7 @@ final class UserShow(helpers: Helpers, bits: UserBits):
ping.map(bits.signalBars)
),
realName.map(div(cls := "upt__info__realname")(_)),
if u.lame && ctx.isnt(u) && !Granter.opt(_.UserModView)
if u.lame && ctx.isnt(u) && !Granter.opt(_.AccountInfo)
then div(cls := "upt__info__warning")(trans.site.thisAccountViolatedTos())
else
ctx.pref.showRatings.option:
@@ -80,7 +80,7 @@ final class UserShow(helpers: Helpers, bits: UserBits):
div(cls := "upt__details")(
span(trans.site.nbGames.plural(u.count.game, u.count.game.localize)),
span(trans.site.joinedX(momentFromNow(u.createdAt))),
(Granter.opt(_.UserModView) && (u.lameOrTroll || u.enabled.no || u.marks.rankban))
(Granter.opt(_.AccountInfo) && (u.lameOrTroll || u.enabled.no || u.marks.rankban))
.option(span(cls := "upt__details__marks")(userMarks))
),
playing
+1 -1
View File
@@ -72,7 +72,7 @@ final class UserShowSide(helpers: Helpers):
)
div(cls := "side sub-ratings")(
(!u.lame || ctx.is(u) || Granter.opt(_.UserModView)).option(
(!u.lame || ctx.is(u) || Granter.opt(_.AccountInfo)).option(
frag(
showNonEmptyPerf(u.perfs.ultraBullet, PerfKey.ultraBullet),
showPerf(u.perfs.bullet, PerfKey.bullet),