add: user ban_reason

This commit is contained in:
trisua 2025-07-16 20:18:39 -04:00
parent b25bda29b8
commit d1c3643574
11 changed files with 100 additions and 9 deletions

View file

@ -198,6 +198,7 @@ version = "1.0.0"
"mod_panel:label.associations" = "Associations"
"mod_panel:label.invited_by" = "Invited by"
"mod_panel:label.send_debug_payload" = "Send debug payload"
"mod_panel:label.ban_reason" = "Ban reason"
"mod_panel:action.send" = "Send"
"requests:label.requests" = "Requests"

View file

@ -87,7 +87,10 @@ macro_rules! get_user_from_token {
{
Ok(ua) => {
if ua.permissions.check_banned() {
Some(tetratto_core::model::auth::User::banned())
let mut banned_user = tetratto_core::model::auth::User::banned();
banned_user.ban_reason = ua.ban_reason;
Some(banned_user)
} else {
Some(ua)
}

View file

@ -102,7 +102,11 @@
("class" "flush")
("style" "font-weight: 600")
("target" "_top")
(text "{{ self::username(user=user) }}"))
(text "{% if user.permissions|has_banned -%}")
(del ("class" "fade") (text "{{ self::username(user=user) }}"))
(text "{% else %}")
(text "{{ self::username(user=user) }}")
(text "{%- endif %}"))
(text "{{ self::online_indicator(user=user) }} {% if user.is_verified -%}")
(span
("title" "Verified")

View file

@ -84,7 +84,7 @@
const ui = await ns(\"ui\");
const element = document.getElementById(\"mod_options\");
async function profile_request(do_confirm, path, body) {
globalThis.profile_request = async (do_confirm, path, body) => {
if (do_confirm) {
if (
!(await trigger(\"atto::confirm\", [
@ -273,6 +273,33 @@
("class" "card lowered flex flex-wrap gap-2")
(text "{{ components::user_plate(user=invite[0], show_menu=false) }}")))
(text "{%- endif %}")
(div
("class" "card-nest w-full")
(div
("class" "card small flex items-center justify-between gap-2")
(div
("class" "flex items-center gap-2")
(icon (text "scale"))
(span
(str (text "mod_panel:label.ban_reason")))))
(form
("class" "card flex flex-col gap-2")
("onsubmit" "event.preventDefault(); profile_request(false, 'ban_reason', { reason: event.target.reason.value || '' })")
(div
("class" "flex flex-col gap-1")
(label
("for" "title")
(str (text "mod_panel:label.ban_reason")))
(textarea
("type" "text")
("name" "reason")
("id" "reason")
("placeholder" "ban reason")
("minlength" "2")
(text "{{ profile.ban_reason|remove_script_tags|safe }}")))
(button
("class" "primary")
(str (text "general:action.save")))))
(div
("class" "card-nest w-full")
(div

View file

@ -70,8 +70,13 @@
(str (text "general:label.account_banned")))
(div
("class" "card")
(str (text "general:label.account_banned_body"))))))
("class" "card flex flex-col gap-2 no_p_margin")
(str (text "general:label.account_banned_body"))
(hr)
(span ("class" "fade") (text "The following reason was provided by a moderator:"))
(div
("class" "card lowered w-full")
(text "{{ user.ban_reason|markdown|safe }}"))))))
; if we aren't banned, just show the page body
(text "{% elif user and user.awaiting_purchase %}")

View file

@ -4,8 +4,9 @@ use crate::{
model::{ApiReturn, Error},
routes::api::v1::{
AppendAssociations, AwardAchievement, DeleteUser, DisableTotp, RefreshGrantToken,
UpdateSecondaryUserRole, UpdateUserAwaitingPurchase, UpdateUserInviteCode,
UpdateUserIsVerified, UpdateUserPassword, UpdateUserRole, UpdateUserUsername,
UpdateSecondaryUserRole, UpdateUserAwaitingPurchase, UpdateUserBanReason,
UpdateUserInviteCode, UpdateUserIsVerified, UpdateUserPassword, UpdateUserRole,
UpdateUserUsername,
},
State,
};
@ -424,6 +425,35 @@ pub async fn update_user_secondary_role_request(
}
}
/// Update the ban reason of the given user.
///
/// Does not support third-party grants.
pub async fn update_user_ban_reason_request(
jar: CookieJar,
Path(id): Path<usize>,
Extension(data): Extension<State>,
Json(req): Json<UpdateUserBanReason>,
) -> impl IntoResponse {
let data = &(data.read().await).0;
let user = match get_user_from_token!(jar, data) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
if !user.permissions.check(FinePermission::MANAGE_USERS) {
return Json(Error::NotAllowed.into());
}
match data.update_user_ban_reason(id, &req.reason).await {
Ok(_) => Json(ApiReturn {
ok: true,
message: "User updated".to_string(),
payload: (),
}),
Err(e) => Json(e.into()),
}
}
/// Update the current user's last seen value.
pub async fn seen_request(jar: CookieJar, Extension(data): Extension<State>) -> impl IntoResponse {
let data = &(data.read().await).0;

View file

@ -322,6 +322,10 @@ pub fn routes() -> Router {
"/auth/user/{id}/role/2",
post(auth::profile::update_user_secondary_role_request),
)
.route(
"/auth/user/{id}/ban_reason",
post(auth::profile::update_user_ban_reason_request),
)
.route(
"/auth/user/{id}",
delete(auth::profile::delete_user_request),
@ -840,6 +844,11 @@ pub struct UpdateSecondaryUserRole {
pub role: SecondaryPermission,
}
#[derive(Deserialize)]
pub struct UpdateUserBanReason {
pub reason: String,
}
#[derive(Deserialize)]
pub struct UpdateUserInviteCode {
pub invite_code: String,