feat: Working server/comment routes

This commit is contained in:
IAmTomahawkx
2025-07-22 03:27:05 -07:00
parent ff36d71ad5
commit bf4530563a
70 changed files with 1841 additions and 411 deletions

View File

@@ -0,0 +1,44 @@
use revolt_database::{AdminAuthorization, AdminComment, Database};
use revolt_models::v0::{self};
use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
use crate::routes::admin::util::{
create_audit_action, flatten_authorized_user, user_has_permission,
};
/// Create a comment on an object or case. Requires Comments permission.
#[openapi(tag = "Admin")]
#[post("/comments", data = "<body>")]
pub async fn admin_comment_create(
db: &State<Database>,
auth: AdminAuthorization,
body: Json<v0::AdminCommentCreate>,
) -> Result<Json<v0::AdminComment>> {
let user = flatten_authorized_user(&auth);
if !user_has_permission(user, v0::AdminUserPermissionFlags::Comments) {
return Err(create_error!(MissingPermission {
permission: "Comments".to_string()
}));
}
let comment = AdminComment::new(
&body.object_id,
&user.id,
&body.content,
body.case_id.as_deref(),
);
db.admin_comment_insert(comment.clone()).await?;
create_audit_action(
db,
&user.id,
v0::AdminAuditItemActions::CommentCreate,
None,
Some(&body.object_id),
None,
)
.await?;
Ok(Json(comment.into()))
}

View File

@@ -0,0 +1,56 @@
use iso8601_timestamp::Timestamp;
use revolt_database::{
util::reference::Reference, AdminAuthorization, Database, PartialAdminComment,
};
use revolt_models::v0::{self};
use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
use rocket_empty::EmptyResponse;
use crate::routes::admin::util::{
create_audit_action, flatten_authorized_user, user_has_permission,
};
/// Create a comment on an object or case. Requires Comments permission.
#[openapi(tag = "Admin")]
#[patch("/comments/<id>", data = "<body>")]
pub async fn admin_comment_edit(
db: &State<Database>,
auth: AdminAuthorization,
id: Reference,
body: Json<v0::AdminCommentEdit>,
) -> Result<EmptyResponse> {
let user = flatten_authorized_user(&auth);
if !user_has_permission(user, v0::AdminUserPermissionFlags::Comments) {
return Err(create_error!(MissingPermission {
permission: "Comments".to_string()
}));
}
let existing = db.admin_comment_fetch(&id.id).await?;
if existing.user_id != user.id {
return Err(create_error!(MissingPermission {
permission: "CannotEditOthersComment".to_string()
}));
}
let partial = PartialAdminComment {
content: Some(body.content.clone()),
edited_at: Some(Timestamp::now_utc().format_short().to_string()),
..Default::default()
};
db.admin_comment_update(&id.id, &partial).await?;
let context = format!("comment id {:}, object id {:}", &id.id, &existing.object_id);
create_audit_action(
db,
&user.id,
v0::AdminAuditItemActions::CommentEdit,
None,
Some(&existing.object_id),
Some(&context),
)
.await?;
Ok(EmptyResponse)
}

View File

@@ -0,0 +1,43 @@
use revolt_database::{util::reference::Reference, AdminAuthorization, Database};
use revolt_models::v0::{self};
use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
use crate::routes::admin::util::{
create_audit_action, flatten_authorized_user, user_has_permission,
};
/// Fetch all comments related to a case. id should be the 7 character short code. Requires Comments permission.
#[openapi(tag = "Admin")]
#[get("/comments/case/<id>")]
pub async fn admin_comment_fetch_case(
db: &State<Database>,
auth: AdminAuthorization,
id: Reference,
) -> Result<Json<Vec<v0::AdminComment>>> {
let user = flatten_authorized_user(&auth);
if !user_has_permission(user, v0::AdminUserPermissionFlags::Comments) {
return Err(create_error!(MissingPermission {
permission: "Comments".to_string()
}));
}
let comments: Vec<v0::AdminComment> = db
.admin_comment_fetch_object_comments(&id.id)
.await?
.iter()
.map(|f| f.clone().into())
.collect();
create_audit_action(
db,
&user.id,
v0::AdminAuditItemActions::CommentFetchForObject,
Some(&id.id),
None,
None,
)
.await?;
Ok(Json(comments))
}

View File

@@ -0,0 +1,43 @@
use revolt_database::{util::reference::Reference, AdminAuthorization, Database};
use revolt_models::v0::{self};
use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
use crate::routes::admin::util::{
create_audit_action, flatten_authorized_user, user_has_permission,
};
/// Fetch comments on an object. Requires Comments permission.
#[openapi(tag = "Admin")]
#[get("/comments/object/<id>")]
pub async fn admin_comment_fetch_object(
db: &State<Database>,
auth: AdminAuthorization,
id: Reference,
) -> Result<Json<Vec<v0::AdminComment>>> {
let user = flatten_authorized_user(&auth);
if !user_has_permission(user, v0::AdminUserPermissionFlags::Comments) {
return Err(create_error!(MissingPermission {
permission: "Comments".to_string()
}));
}
let comments: Vec<v0::AdminComment> = db
.admin_comment_fetch_object_comments(&id.id)
.await?
.iter()
.map(|f| f.clone().into())
.collect();
create_audit_action(
db,
&user.id,
v0::AdminAuditItemActions::CommentFetchForObject,
None,
Some(&id.id),
None,
)
.await?;
Ok(Json(comments))
}

View File

@@ -0,0 +1,4 @@
pub mod comment_create;
pub mod comment_edit;
pub mod comment_fetch_case;
pub mod comment_fetch_object;

View File

@@ -18,7 +18,9 @@ pub async fn admin_create_token(
&auth.on_behalf_of,
v0::AdminUserPermissionFlags::CreateTokens,
) {
return Err(create_error!(NotFound));
return Err(create_error!(MissingPermission {
permission: "CreateTokens".to_string()
}));
}
if body.expiry > Timestamp::now_utc() + Duration::days(30) {

View File

@@ -15,7 +15,9 @@ pub async fn admin_create_user(
) -> Result<Json<v0::AdminUser>> {
let user = flatten_authorized_user(&auth);
if !user_has_permission(user, v0::AdminUserPermissionFlags::ManageAdminUsers) {
return Err(create_error!(NotFound));
return Err(create_error!(MissingPermission {
permission: "ManageAdminUsers".to_string()
}));
}
// TODO: technically there's a privilege escalation here since anyone with the manageAdminUsers permission can assign whatever permissions they want.

View File

@@ -17,7 +17,9 @@ pub async fn admin_edit_user(
) -> Result<EmptyResponse> {
let user = flatten_authorized_user(&auth);
if !user_has_permission(user, v0::AdminUserPermissionFlags::ManageAdminUsers) {
return Err(create_error!(NotFound));
return Err(create_error!(MissingPermission {
permission: "ManageAdminUsers".to_string()
}));
}
// TODO: technically there's a privilege escalation here since anyone with the manageAdminUsers permission can assign whatever permissions they want.

View File

@@ -19,7 +19,9 @@ pub async fn admin_revoke_token(
&auth.on_behalf_of,
v0::AdminUserPermissionFlags::CreateTokens,
) {
return Err(create_error!(NotFound));
return Err(create_error!(MissingPermission {
permission: "CreateTokens".to_string()
}));
}
let token = token.as_admin_token(db).await?;

View File

@@ -3,24 +3,38 @@ use rocket::Route;
mod accounts;
mod cases;
mod comments;
mod meta;
mod reports;
mod roles;
mod search;
mod servers;
mod users;
mod object_edit_note;
mod object_get_note;
mod util;
pub fn routes() -> (Vec<Route>, OpenApi) {
openapi_get_routes_spec![
object_edit_note::admin_object_edit_note,
object_get_note::admin_object_get_note,
meta::create_user::admin_create_user,
meta::edit_user::admin_edit_user,
meta::fetch_users::admin_fetch_users,
meta::create_token::admin_create_token,
meta::revoke_token::admin_revoke_token
meta::revoke_token::admin_revoke_token,
comments::comment_create::admin_comment_create,
comments::comment_edit::admin_comment_edit,
comments::comment_fetch_case::admin_comment_fetch_case,
comments::comment_fetch_object::admin_comment_fetch_object,
servers::fetch::server_get::admin_server_get,
servers::fetch::server_get_participants::admin_server_get_participants,
servers::fetch::server_get_all_members::admin_server_get_members,
servers::actions::server_add_members::admin_server_add_member,
servers::actions::server_ban_members::admin_server_ban_member,
servers::actions::server_unban_members::admin_server_unban_member,
servers::actions::server_change_owner::admin_server_change_owner,
servers::actions::server_create_invite::admin_server_create_invite,
servers::actions::server_delete_invites::admin_server_delete_invites,
servers::actions::server_delete::admin_server_delete,
servers::actions::server_edit::admin_server_edit,
servers::actions::server_remove_members::admin_server_remove_members
]
}

View File

@@ -1,28 +0,0 @@
use revolt_database::{util::reference::Reference, AdminAuthorization, AdminObjectNote, Database};
use revolt_models::v0::{self};
use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
use rocket_empty::EmptyResponse;
use crate::routes::admin::util::{flatten_authorized_user, user_has_permission};
/// Edit an object note. Requires ObjectNotes permission.
#[openapi(tag = "Admin")]
#[post("/notes/<object>", data = "<body>")]
pub async fn admin_object_edit_note(
db: &State<Database>,
auth: AdminAuthorization,
object: Reference,
body: Json<v0::AdminObjectNoteEdit>,
) -> Result<EmptyResponse> {
let user = flatten_authorized_user(&auth);
if !user_has_permission(user, v0::AdminUserPermissionFlags::ObjectNotes) {
return Err(create_error!(NotFound));
}
// Unfortunately due to how our system works, we can't limit users to editing objects they have permissions for (eg users, servers, etc.).
// Hence it being tied to its own permission
db.admin_note_update(AdminObjectNote::new(&object.id, &user.id, &body.content))
.await?;
Ok(EmptyResponse)
}

View File

@@ -1,24 +0,0 @@
use revolt_database::{util::reference::Reference, AdminAuthorization, Database};
use revolt_models::v0::{self};
use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
use crate::routes::admin::util::{flatten_authorized_user, user_has_permission};
/// Edit an object note. Requires ObjectNotes permission.
#[openapi(tag = "Admin")]
#[get("/notes/<object>")]
pub async fn admin_object_get_note(
db: &State<Database>,
auth: AdminAuthorization,
object: Reference,
) -> Result<Json<v0::AdminObjectNote>> {
let user = flatten_authorized_user(&auth);
if !user_has_permission(user, v0::AdminUserPermissionFlags::ObjectNotes) {
return Err(create_error!(NotFound));
}
// Unfortunately due to how our system works, we can't limit users to editing objects they have permissions for (eg users, servers, etc.).
// Hence it being tied to its own permission
Ok(Json(db.admin_note_fetch(&object.id).await?.into()))
}

View File

@@ -0,0 +1,9 @@
pub mod server_add_members;
pub mod server_ban_members;
pub mod server_change_owner;
pub mod server_create_invite;
pub mod server_delete;
pub mod server_delete_invites;
pub mod server_edit;
pub mod server_remove_members;
pub mod server_unban_members;

View File

@@ -0,0 +1,48 @@
use revolt_database::{util::reference::Reference, AdminAuthorization, Database, Member};
use revolt_models::v0::{self};
use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
use crate::routes::admin::util::{
create_audit_action, flatten_authorized_user, user_has_permission,
};
/// # Add server member
///
/// Add a user to a server, optionally disabling notifications
#[openapi(tag = "Admin")]
#[post("/servers/<id>/members?<case>&<user_id>&<suppress_alerts>")]
pub async fn admin_server_add_member(
db: &State<Database>,
auth: AdminAuthorization,
id: Reference,
case: Option<&str>,
user_id: Reference,
suppress_alerts: bool,
) -> Result<Json<v0::MemberWithUserResponse>> {
let user = flatten_authorized_user(&auth);
if !user_has_permission(user, v0::AdminUserPermissionFlags::ManageServers) {
return Err(create_error!(MissingPermission {
permission: "ManageServers".to_string()
}));
}
let server = id.as_server(db).await?;
let user = user_id.as_user(db).await?;
let (member, _) = Member::create(db, &server, &user, None, !suppress_alerts).await?;
create_audit_action(
db,
&user.id,
v0::AdminAuditItemActions::ServerAddMember,
case,
Some(&id.id),
None,
)
.await?;
Ok(Json(v0::MemberWithUserResponse {
user: user.into_self(false).await,
member: member.into(),
}))
}

View File

@@ -0,0 +1,83 @@
use revolt_database::{util::reference::Reference, AdminAuthorization, Database, ServerBan};
use revolt_models::v0::{self};
use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
use validator::Validate;
use crate::routes::admin::util::{
create_audit_action, flatten_authorized_user, user_has_permission,
};
/// # Ban a server member
///
/// Ban a member who is not the owner
#[openapi(tag = "Admin")]
#[post(
"/servers/<id>/ban?<case>&<user_id>&<suppress_alerts>",
data = "<body>"
)]
pub async fn admin_server_ban_member(
db: &State<Database>,
auth: AdminAuthorization,
id: Reference,
case: Option<&str>,
user_id: Reference,
suppress_alerts: bool,
body: Json<v0::DataBanCreate>,
) -> Result<Json<v0::ServerBan>> {
let user = flatten_authorized_user(&auth);
if !user_has_permission(user, v0::AdminUserPermissionFlags::ManageServers) {
return Err(create_error!(MissingPermission {
permission: "ManageServers".to_string()
}));
}
let body = body.into_inner();
body.validate().map_err(|error| {
create_error!(FailedValidation {
error: error.to_string()
})
})?;
let server = id.as_server(db).await?;
let target = user_id.as_user(db).await?;
if target.id == server.owner {
return Err(create_error!(InvalidOperation));
}
let resp = if let Ok(member) = user_id.as_member(db, &id.id).await {
member
.remove(
db,
&server,
revolt_database::RemovalIntention::Ban,
suppress_alerts,
)
.await?;
Ok(ServerBan::create(db, &server, &target.id, body.reason.clone()).await?)
} else {
Err(create_error!(NotAMember))
}?;
let context = format!(
"user id: {:}, server id: {:}, reason: {:}",
&user_id.id,
&id.id,
body.reason
.clone()
.unwrap_or_else(|| "None Provided".to_string())
);
create_audit_action(
db,
&user.id,
v0::AdminAuditItemActions::ServerBanMember,
case,
Some(&id.id),
Some(&context),
)
.await?;
Ok(Json(resp.into()))
}

View File

@@ -0,0 +1,55 @@
use revolt_database::{util::reference::Reference, AdminAuthorization, Database, PartialServer};
use revolt_models::v0::{self};
use revolt_result::{create_error, Result};
use rocket::State;
use rocket_empty::EmptyResponse;
use crate::routes::admin::util::{
create_audit_action, flatten_authorized_user, user_has_permission,
};
/// # Change Server Owner
///
/// Change the ownership of a server.
#[openapi(tag = "Admin")]
#[post("/servers/<id>/owner?<case>&<user_id>")]
pub async fn admin_server_change_owner(
db: &State<Database>,
auth: AdminAuthorization,
id: Reference,
case: Option<&str>,
user_id: Reference,
) -> Result<EmptyResponse> {
let user = flatten_authorized_user(&auth);
if !user_has_permission(user, v0::AdminUserPermissionFlags::ManageServers) {
return Err(create_error!(MissingPermission {
permission: "ManageServers".to_string()
}));
}
let server = id.as_server(db).await?;
let target = user_id.as_user(db).await?;
if target.id == server.owner {
return Err(create_error!(InvalidOperation));
}
let partial = PartialServer {
owner: Some(target.id),
..Default::default()
};
db.update_server(&server.id, &partial, vec![]).await?;
let context = format!("user id: {:}, server id: {:}", &user_id.id, &id.id);
create_audit_action(
db,
&user.id,
v0::AdminAuditItemActions::ServerChangeOwner,
case,
Some(&id.id),
Some(&context),
)
.await?;
Ok(EmptyResponse)
}

View File

@@ -0,0 +1,66 @@
use revolt_database::{util::reference::Reference, AdminAuthorization, Database, Invite};
use revolt_models::v0::{self};
use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
use crate::routes::admin::util::{
create_audit_action, flatten_authorized_user, user_has_permission,
};
/// # Create Server Invite
///
/// Create an invite to a server. Optionally include a slug to create a vanity URL.
#[openapi(tag = "Admin")]
#[post("/servers/<id>/invites?<case>&<slug>&<channel_id>")]
pub async fn admin_server_create_invite(
db: &State<Database>,
auth: AdminAuthorization,
id: Reference,
channel_id: Reference,
case: Option<&str>,
slug: Option<&str>,
) -> Result<Json<v0::Invite>> {
let user = flatten_authorized_user(&auth);
if !user_has_permission(user, v0::AdminUserPermissionFlags::ManageServers) {
return Err(create_error!(MissingPermission {
permission: "ManageServers".to_string()
}));
}
let server = id.as_server(db).await?;
let channel = channel_id.as_channel(db).await?;
let creator = db.fetch_user(&server.owner).await?;
let invite: Invite;
if let Some(slug) = slug {
invite = Invite::Server {
code: slug.to_string(),
creator: creator.id.clone(),
server: server.id.clone(),
channel: channel.id().to_string(),
};
#[allow(clippy::disallowed_methods)]
db.insert_invite(&invite).await?;
} else {
invite = Invite::create_channel_invite(db, &creator, &channel).await?;
}
let context = format!(
"server id: {:}, invite slug: {:}, channel: {:}",
&id.id,
invite.code(),
channel.id()
);
create_audit_action(
db,
&user.id,
v0::AdminAuditItemActions::ServerCreateInvite,
case,
Some(&id.id),
Some(&context),
)
.await?;
Ok(Json(invite.into()))
}

View File

@@ -0,0 +1,41 @@
use revolt_database::{util::reference::Reference, AdminAuthorization, Database};
use revolt_models::v0::{self};
use revolt_result::{create_error, Result};
use rocket::State;
use rocket_empty::EmptyResponse;
use crate::routes::admin::util::{
create_audit_action, flatten_authorized_user, user_has_permission,
};
/// Get a list of admin users. Any active user may use this endpoint.
/// Typically the client should cache this data.
#[openapi(tag = "Admin")]
#[delete("/servers/<id>?<case>")]
pub async fn admin_server_delete(
db: &State<Database>,
auth: AdminAuthorization,
id: Reference,
case: Option<&str>,
) -> Result<EmptyResponse> {
let user = flatten_authorized_user(&auth);
if !user_has_permission(user, v0::AdminUserPermissionFlags::ManageServers) {
return Err(create_error!(MissingPermission {
permission: "ManageServers".to_string()
}));
}
db.delete_server(&id.id).await?;
create_audit_action(
db,
&user.id,
v0::AdminAuditItemActions::ServerDelete,
case,
Some(&id.id),
None,
)
.await?;
Ok(EmptyResponse)
}

View File

@@ -0,0 +1,62 @@
use revolt_database::{util::reference::Reference, AdminAuthorization, Database};
use revolt_models::v0::{self};
use revolt_result::{create_error, Result};
use rocket::State;
use rocket_empty::EmptyResponse;
use crate::routes::admin::util::{
create_audit_action, flatten_authorized_user, user_has_permission,
};
/// Delete Server Invite
///
/// Delete a server invite, or all of a server's invites.
#[openapi(tag = "Admin")]
#[delete("/servers/<id>/invites?<case>&<slug>")]
pub async fn admin_server_delete_invites(
db: &State<Database>,
auth: AdminAuthorization,
id: Reference,
case: Option<&str>,
slug: Option<Reference>,
) -> Result<EmptyResponse> {
let user = flatten_authorized_user(&auth);
if !user_has_permission(user, v0::AdminUserPermissionFlags::ManageServers) {
return Err(create_error!(MissingPermission {
permission: "ManageServers".to_string()
}));
}
if let Some(slug) = slug {
let invite = slug.as_invite(db).await?;
db.delete_invite(&slug.id).await?;
let context = format!("server id: {:}, invite slug: {:}", &id.id, invite.code());
create_audit_action(
db,
&user.id,
v0::AdminAuditItemActions::ServerDeleteInvite,
case,
Some(&id.id),
Some(&context),
)
.await?;
} else {
let invites = db.fetch_invites_for_server(&id.id).await?;
for invite in invites {
db.delete_invite(invite.code()).await?;
}
create_audit_action(
db,
&user.id,
v0::AdminAuditItemActions::ServerDeleteAllInvites,
case,
Some(&id.id),
None,
)
.await?;
}
Ok(EmptyResponse)
}

View File

@@ -0,0 +1,52 @@
use revolt_database::{util::reference::Reference, AdminAuthorization, Database};
use revolt_models::v0::{self};
use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
use validator::Validate;
use crate::routes::admin::util::{
create_audit_action, flatten_authorized_user, user_has_permission,
};
/// # Edit server
///
/// Edit any attributes of a server.
#[openapi(tag = "Admin")]
#[patch("/servers/<id>?<case>", data = "<body>")]
pub async fn admin_server_edit(
db: &State<Database>,
auth: AdminAuthorization,
id: Reference,
case: Option<&str>,
body: Json<v0::DataEditServer>,
) -> Result<Json<v0::Server>> {
let user = flatten_authorized_user(&auth);
if !user_has_permission(user, v0::AdminUserPermissionFlags::ManageServers) {
return Err(create_error!(MissingPermission {
permission: "ManageServers".to_string()
}));
}
let body = body.into_inner();
body.validate().map_err(|error| {
create_error!(FailedValidation {
error: error.to_string()
})
})?;
let mut server = id.as_server(db).await?;
let user = db.fetch_user(&server.owner).await?;
crate::routes::servers::server_edit::edit_data(body, db, &mut server, &user).await?;
create_audit_action(
db,
&user.id,
v0::AdminAuditItemActions::ServerEdit,
case,
Some(&id.id),
None,
)
.await?;
Ok(Json(server.into()))
}

View File

@@ -0,0 +1,61 @@
use revolt_database::{util::reference::Reference, AdminAuthorization, Database};
use revolt_models::v0::{self};
use revolt_result::{create_error, Result};
use rocket::State;
use rocket_empty::EmptyResponse;
use crate::routes::admin::util::{
create_audit_action, flatten_authorized_user, user_has_permission,
};
/// # Ban a server member
///
/// Ban a member who is not the owner
#[openapi(tag = "Admin")]
#[post("/servers/<id>/remove?<case>&<user_id>&<suppress_alerts>")]
pub async fn admin_server_remove_members(
db: &State<Database>,
auth: AdminAuthorization,
id: Reference,
case: Option<&str>,
user_id: Vec<Reference>,
suppress_alerts: bool,
) -> Result<EmptyResponse> {
let admin_user = flatten_authorized_user(&auth);
if !user_has_permission(admin_user, v0::AdminUserPermissionFlags::ManageServers) {
return Err(create_error!(MissingPermission {
permission: "ManageServers".to_string()
}));
}
let server = id.as_server(db).await?;
for uid in user_id {
let target = uid.as_member(db, &id.id).await?;
if target.id.user == server.owner {
return Err(create_error!(InvalidOperation));
}
target
.remove(
db,
&server,
revolt_database::RemovalIntention::Kick,
suppress_alerts,
)
.await?;
let context = format!("user id: {:}, server id: {:}", &uid.id, &id.id);
create_audit_action(
db,
&admin_user.id,
v0::AdminAuditItemActions::ServerRemoveMember,
case,
Some(&id.id),
Some(&context),
)
.await?;
}
Ok(EmptyResponse)
}

View File

@@ -0,0 +1,60 @@
use revolt_database::{util::reference::Reference, AdminAuthorization, Database};
use revolt_models::v0::{self};
use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
use rocket_empty::EmptyResponse;
use crate::routes::admin::util::{
create_audit_action, flatten_authorized_user, user_has_permission,
};
/// Get a list of admin users. Any active user may use this endpoint.
/// Typically the client should cache this data.
#[openapi(tag = "Admin")]
#[post(
"/servers/<id>/unban?<case>&<user_id>&<suppress_alerts>",
data = "<body>"
)]
pub async fn admin_server_unban_member(
db: &State<Database>,
auth: AdminAuthorization,
id: Reference,
case: Option<&str>,
user_id: Reference,
suppress_alerts: bool,
body: Json<v0::DataBanCreate>,
) -> Result<EmptyResponse> {
let user = flatten_authorized_user(&auth);
if !user_has_permission(user, v0::AdminUserPermissionFlags::ManageServers) {
return Err(create_error!(MissingPermission {
permission: "ManageServers".to_string()
}));
}
let ban = db
.fetch_ban(&id.id, &user_id.id)
.await
.map_err(|_| create_error!(NotFound))?;
db.delete_ban(&ban.id).await?;
let context = format!(
"user id: {:}, server id: {:}, reason: {:}",
&user_id.id,
&id.id,
body.reason
.clone()
.unwrap_or_else(|| "None Provided".to_string())
);
create_audit_action(
db,
&user.id,
v0::AdminAuditItemActions::ServerUnbanMember,
case,
Some(&id.id),
Some(&context),
)
.await?;
Ok(EmptyResponse)
}

View File

@@ -0,0 +1,3 @@
pub mod server_get;
pub mod server_get_all_members;
pub mod server_get_participants;

View File

@@ -0,0 +1,51 @@
use revolt_database::{util::reference::Reference, AdminAuthorization, Database};
use revolt_models::v0::{self};
use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
use crate::routes::admin::util::{
create_audit_action, flatten_authorized_user, user_has_permission,
};
/// Get a list of admin users. Any active user may use this endpoint.
/// Typically the client should cache this data.
#[openapi(tag = "Admin")]
#[get("/servers/<id>?<case>")]
pub async fn admin_server_get(
db: &State<Database>,
auth: AdminAuthorization,
id: Reference,
case: Option<&str>,
) -> Result<Json<v0::AdminServerResponse>> {
let user = flatten_authorized_user(&auth);
if !user_has_permission(user, v0::AdminUserPermissionFlags::ManageServers) {
return Err(create_error!(MissingPermission {
permission: "ManageServers".to_string()
}));
}
let server = id.as_server(db).await?;
let owner = db.fetch_user(&server.owner).await?.into_self(false).await;
let comments: Vec<v0::AdminComment> = db
.admin_comment_fetch_object_comments(&server.id)
.await?
.iter()
.map(|f| f.clone().into())
.collect();
create_audit_action(
db,
&user.id,
v0::AdminAuditItemActions::ServerFetch,
case,
Some(&id.id),
None,
)
.await?;
Ok(Json(v0::AdminServerResponse {
server: server.into(),
owner,
comments,
}))
}

View File

@@ -0,0 +1,67 @@
use futures::future::join_all;
use iso8601_timestamp::Timestamp;
use revolt_database::{util::reference::Reference, AdminAuthorization, Database};
use revolt_models::v0::{self};
use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
use crate::routes::admin::util::{
create_audit_action, flatten_authorized_user, user_has_permission,
};
/// Get a list of admin users. Any active user may use this endpoint.
/// Typically the client should cache this data.
#[openapi(tag = "Admin")]
#[get("/servers/<id>/members?<case>&<after>")]
pub async fn admin_server_get_members(
db: &State<Database>,
auth: AdminAuthorization,
id: Reference,
case: Option<&str>,
after: Option<usize>,
) -> Result<Json<v0::AdminMemberWithUserAndOffsetResponse>> {
let user = flatten_authorized_user(&auth);
if !user_has_permission(user, v0::AdminUserPermissionFlags::ManageServers) {
return Err(create_error!(MissingPermission {
permission: "ManageServers".to_string()
}));
}
let server = id.as_server(db).await?;
let members = db.fetch_server_members(&server.id, 100, after).await?;
create_audit_action(
db,
&user.id,
v0::AdminAuditItemActions::ServerFetchMembers,
case,
Some(&id.id),
None,
)
.await?;
let members_and_users = join_all(members.iter().map(|(u, m)| async move {
let user: v0::User = u.clone().into_self(false).await;
let member: v0::Member = m.clone().into();
v0::MemberWithUserResponse { user, member }
}))
.await;
let mut resp = v0::AdminMemberWithUserAndOffsetResponse {
after: None,
users: vec![],
};
if let Some(last) = members_and_users.last() {
resp.after = Some(
last.member
.joined_at
.duration_since(Timestamp::UNIX_EPOCH)
.whole_milliseconds() as usize,
);
}
resp.users = members_and_users;
Ok(Json(resp))
}

View File

@@ -0,0 +1,51 @@
use futures::future::join_all;
use revolt_database::{util::reference::Reference, AdminAuthorization, Database};
use revolt_models::v0::{self};
use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
use crate::routes::admin::util::{
create_audit_action, flatten_authorized_user, user_has_permission,
};
/// Get a list of admin users. Any active user may use this endpoint.
/// Typically the client should cache this data.
#[openapi(tag = "Admin")]
#[get("/servers/<id>/participants?<case>")]
pub async fn admin_server_get_participants(
db: &State<Database>,
auth: AdminAuthorization,
id: Reference,
case: Option<&str>,
) -> Result<Json<Vec<(v0::User, Option<v0::Member>)>>> {
let user = flatten_authorized_user(&auth);
if !user_has_permission(user, v0::AdminUserPermissionFlags::ManageServers) {
return Err(create_error!(MissingPermission {
permission: "ManageServers".to_string()
}));
}
let server = id.as_server(db).await?;
let participants = db.fetch_server_participants(&server.id).await?;
create_audit_action(
db,
&user.id,
v0::AdminAuditItemActions::ServerFetchParticipants,
case,
Some(&id.id),
None,
)
.await?;
println!("{:?}", &participants);
let resp = join_all(participants.iter().map(|(u, m)| async move {
let user: v0::User = u.clone().into_self(false).await;
let member: Option<v0::Member> = m.clone().map(|mu| mu.into());
(user, member)
}))
.await;
Ok(Json(resp))
}

View File

@@ -1,2 +1,2 @@
mod actions;
mod fetch;
pub mod actions;
pub mod fetch;

View File

@@ -1,5 +1,9 @@
use revolt_database::{AdminAuthorization, AdminUser, AdminUserPermissionFlagsValue};
use revolt_models::v0::AdminUserPermissionFlags;
use revolt_database::{
AdminAuditItem, AdminAuthorization, AdminComment, AdminUser, AdminUserPermissionFlagsValue,
Database,
};
use revolt_models::v0::{AdminAuditItemActions, AdminUserPermissionFlags};
use revolt_result::Result;
pub fn user_has_permission(user: &AdminUser, permission: AdminUserPermissionFlags) -> bool {
let flags = AdminUserPermissionFlagsValue(user.permissions);
@@ -7,9 +11,40 @@ pub fn user_has_permission(user: &AdminUser, permission: AdminUserPermissionFlag
flags.has(permission)
}
pub fn flatten_authorized_user<'a>(auth: &'a AdminAuthorization) -> &'a AdminUser {
pub fn flatten_authorized_user(auth: &AdminAuthorization) -> &AdminUser {
match auth {
AdminAuthorization::AdminUser(admin_user) => &admin_user,
AdminAuthorization::AdminUser(admin_user) => admin_user,
AdminAuthorization::AdminMachine(admin_machine_token) => &admin_machine_token.on_behalf_of,
}
}
/// Creates audit logs, and if applicable, adds a comment to the referenced case.
pub async fn create_audit_action(
db: &Database,
mod_id: &str,
action: AdminAuditItemActions,
short_case_id: Option<&str>,
target_id: Option<&str>,
context: Option<&str>,
) -> Result<()> {
let audit = AdminAuditItem::new(mod_id, action.clone(), short_case_id, target_id, context);
db.admin_audit_insert(audit).await?;
if action.makes_comment() {
if let Some(short_case_id) = short_case_id {
if let Some(target_id) = target_id {
let case = db.admin_case_fetch_from_shorthand(short_case_id).await?;
db.admin_comment_insert(AdminComment::new_system_message(
&case.id,
mod_id,
short_case_id,
action,
context,
target_id,
))
.await?;
}
}
}
Ok(())
}

View File

@@ -43,7 +43,7 @@ pub async fn invite_bot(
.await
.throw_if_lacking_channel_permission(ChannelPermission::ManageServer)?;
Member::create(db, &server, &bot_user, None)
Member::create(db, &server, &bot_user, None, true)
.await
.map(|_| EmptyResponse)
}

View File

@@ -24,7 +24,7 @@ pub async fn join(
match &invite {
Invite::Server { server, .. } => {
let server = db.fetch_server(server).await?;
let (_, channels) = Member::create(db, &server, &user, None).await?;
let (_, channels) = Member::create(db, &server, &user, None, true).await?;
Ok(Json(InviteJoinResponse::Server {
channels: channels.into_iter().map(|c| c.into()).collect(),

View File

@@ -21,7 +21,7 @@ mod roles_fetch;
mod server_ack;
mod server_create;
mod server_delete;
mod server_edit;
pub mod server_edit;
mod server_fetch;
pub fn routes() -> (Vec<Route>, OpenApi) {

View File

@@ -30,7 +30,7 @@ pub async fn create_server(
user.can_acquire_server(db).await?;
let (server, channels) = Server::create(db, data, &user, true).await?;
let (_, channels) = Member::create(db, &server, &user, Some(channels)).await?;
let (_, channels) = Member::create(db, &server, &user, Some(channels), true).await?;
Ok(Json(v0::CreateServerLegacyResponse {
server: server.into(),

View File

@@ -2,7 +2,7 @@ use std::collections::HashSet;
use revolt_database::{
util::{permissions::DatabasePermissionQuery, reference::Reference},
Database, File, PartialServer, User,
Database, File, PartialServer, Server, User,
};
use revolt_models::v0;
use revolt_permissions::{calculate_server_permissions, ChannelPermission};
@@ -69,6 +69,16 @@ pub async fn edit(
permissions.throw_if_lacking_channel_permission(ChannelPermission::ManageChannel)?;
}
edit_data(data, db, &mut server, &user).await?;
Ok(Json(server.into()))
}
pub async fn edit_data(
data: v0::DataEditServer,
db: &Database,
server: &mut Server,
user: &User,
) -> Result<()> {
let v0::DataEditServer {
name,
description,
@@ -158,5 +168,5 @@ pub async fn edit(
)
.await?;
Ok(Json(server.into()))
Ok(())
}

View File

@@ -167,7 +167,7 @@ impl TestHarness {
server: &Server,
channels: Vec<Channel>,
) -> (Channel, Member, Message) {
let (member, channels) = Member::create(&self.db, server, user, Some(channels))
let (member, channels) = Member::create(&self.db, server, user, Some(channels), true)
.await
.expect("Failed to create member");
let channel = &channels[0];