feat: add /mod summon
This commit is contained in:
parent
864cfaec54
commit
45a7e44b3a
11 changed files with 598 additions and 5 deletions
|
|
@ -209,6 +209,42 @@ impl std::str::FromStr for RealmRole {
|
|||
}
|
||||
}
|
||||
|
||||
/// Moderation action type.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "ssr", derive(sqlx::Type))]
|
||||
#[cfg_attr(
|
||||
feature = "ssr",
|
||||
sqlx(type_name = "action_type", rename_all = "snake_case")
|
||||
)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ActionType {
|
||||
Warning,
|
||||
Mute,
|
||||
Kick,
|
||||
Ban,
|
||||
Unban,
|
||||
PropRemoval,
|
||||
MessageDeletion,
|
||||
Summon,
|
||||
SummonAll,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ActionType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ActionType::Warning => write!(f, "warning"),
|
||||
ActionType::Mute => write!(f, "mute"),
|
||||
ActionType::Kick => write!(f, "kick"),
|
||||
ActionType::Ban => write!(f, "ban"),
|
||||
ActionType::Unban => write!(f, "unban"),
|
||||
ActionType::PropRemoval => write!(f, "prop_removal"),
|
||||
ActionType::MessageDeletion => write!(f, "message_deletion"),
|
||||
ActionType::Summon => write!(f, "summon"),
|
||||
ActionType::SummonAll => write!(f, "summon_all"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Scene dimension mode.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
#[cfg_attr(feature = "ssr", derive(sqlx::Type))]
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ pub mod guests;
|
|||
pub mod inventory;
|
||||
pub mod loose_props;
|
||||
pub mod memberships;
|
||||
pub mod moderation;
|
||||
pub mod owner;
|
||||
pub mod props;
|
||||
pub mod realms;
|
||||
|
|
|
|||
|
|
@ -199,3 +199,33 @@ pub async fn is_member(pool: &PgPool, user_id: Uuid, realm_id: Uuid) -> Result<b
|
|||
|
||||
Ok(exists.0)
|
||||
}
|
||||
|
||||
/// Check if a user is a moderator (server-level or realm-level).
|
||||
///
|
||||
/// Returns true if the user has any of:
|
||||
/// - Server staff role: owner, admin, or moderator (from `server.staff`)
|
||||
/// - Realm role: owner or moderator (from `realm.memberships`)
|
||||
pub async fn is_moderator(pool: &PgPool, user_id: Uuid, realm_id: Uuid) -> Result<bool, AppError> {
|
||||
// Check server-level staff first (owner, admin, moderator can moderate any realm)
|
||||
if let Some(server_role) = get_user_staff_role(pool, user_id).await? {
|
||||
match server_role {
|
||||
ServerRole::Owner | ServerRole::Admin | ServerRole::Moderator => return Ok(true),
|
||||
}
|
||||
}
|
||||
|
||||
// Check realm-level role (owner or moderator for this specific realm)
|
||||
let realm_mod: (bool,) = sqlx::query_as(
|
||||
r#"
|
||||
SELECT EXISTS(
|
||||
SELECT 1 FROM realm.memberships
|
||||
WHERE user_id = $1 AND realm_id = $2 AND role IN ('owner', 'moderator')
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.bind(user_id)
|
||||
.bind(realm_id)
|
||||
.fetch_one(pool)
|
||||
.await?;
|
||||
|
||||
Ok(realm_mod.0)
|
||||
}
|
||||
|
|
|
|||
77
crates/chattyness-db/src/queries/moderation.rs
Normal file
77
crates/chattyness-db/src/queries/moderation.rs
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
//! Moderation-related database queries.
|
||||
|
||||
use sqlx::PgPool;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::models::ActionType;
|
||||
use chattyness_error::AppError;
|
||||
|
||||
/// Log a moderation action to the realm audit log.
|
||||
pub async fn log_moderation_action(
|
||||
pool: &PgPool,
|
||||
realm_id: Uuid,
|
||||
moderator_id: Uuid,
|
||||
action_type: ActionType,
|
||||
target_user_id: Option<Uuid>,
|
||||
reason: &str,
|
||||
metadata: serde_json::Value,
|
||||
) -> Result<(), AppError> {
|
||||
sqlx::query(
|
||||
r#"
|
||||
INSERT INTO realm.moderation_actions (
|
||||
realm_id,
|
||||
action_type,
|
||||
target_user_id,
|
||||
moderator_id,
|
||||
reason,
|
||||
metadata
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
"#,
|
||||
)
|
||||
.bind(realm_id)
|
||||
.bind(action_type)
|
||||
.bind(target_user_id)
|
||||
.bind(moderator_id)
|
||||
.bind(reason)
|
||||
.bind(metadata)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Log a moderation action using a connection (for RLS support).
|
||||
pub async fn log_moderation_action_conn(
|
||||
conn: &mut sqlx::PgConnection,
|
||||
realm_id: Uuid,
|
||||
moderator_id: Uuid,
|
||||
action_type: ActionType,
|
||||
target_user_id: Option<Uuid>,
|
||||
reason: &str,
|
||||
metadata: serde_json::Value,
|
||||
) -> Result<(), AppError> {
|
||||
sqlx::query(
|
||||
r#"
|
||||
INSERT INTO realm.moderation_actions (
|
||||
realm_id,
|
||||
action_type,
|
||||
target_user_id,
|
||||
moderator_id,
|
||||
reason,
|
||||
metadata
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
"#,
|
||||
)
|
||||
.bind(realm_id)
|
||||
.bind(action_type)
|
||||
.bind(target_user_id)
|
||||
.bind(moderator_id)
|
||||
.bind(reason)
|
||||
.bind(metadata)
|
||||
.execute(conn)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -90,6 +90,14 @@ pub enum ClientMessage {
|
|||
/// Scene ID to teleport to.
|
||||
scene_id: Uuid,
|
||||
},
|
||||
|
||||
/// Moderator command (only processed if sender is a moderator).
|
||||
ModCommand {
|
||||
/// Subcommand name ("summon", "avatar", "teleport", "ban", etc.).
|
||||
subcommand: String,
|
||||
/// Arguments for the subcommand.
|
||||
args: Vec<String>,
|
||||
},
|
||||
}
|
||||
|
||||
/// Server-to-client WebSocket messages.
|
||||
|
|
@ -234,4 +242,22 @@ pub enum ServerMessage {
|
|||
/// Scene slug for URL.
|
||||
scene_slug: String,
|
||||
},
|
||||
|
||||
/// User has been summoned by a moderator - triggers teleport.
|
||||
Summoned {
|
||||
/// Scene ID to teleport to.
|
||||
scene_id: Uuid,
|
||||
/// Scene slug for URL.
|
||||
scene_slug: String,
|
||||
/// Display name of the moderator who summoned.
|
||||
summoned_by: String,
|
||||
},
|
||||
|
||||
/// Result of a moderator command.
|
||||
ModCommandResult {
|
||||
/// Whether the command succeeded.
|
||||
success: bool,
|
||||
/// Human-readable result message.
|
||||
message: String,
|
||||
},
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue