feat: initial category permissions and rework of category crud routes
This commit is contained in:
45
crates/delta/src/routes/servers/category_create.rs
Normal file
45
crates/delta/src/routes/servers/category_create.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
use revolt_config::config;
|
||||
use revolt_database::{
|
||||
util::{permissions::DatabasePermissionQuery, reference::Reference}, Category, Database, Role, User
|
||||
};
|
||||
use revolt_models::v0;
|
||||
use revolt_permissions::{calculate_server_permissions, ChannelPermission};
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::{serde::json::Json, State};
|
||||
use validator::Validate;
|
||||
|
||||
/// # Create a category
|
||||
///
|
||||
/// Creates a new server category.
|
||||
#[openapi(tag = "Server Categories")]
|
||||
#[post("/<target>/categories", data = "<data>")]
|
||||
pub async fn create(
|
||||
db: &State<Database>,
|
||||
user: User,
|
||||
target: Reference,
|
||||
data: Json<v0::DataCreateCategory>,
|
||||
) -> Result<Json<v0::Category>> {
|
||||
let data = data.into_inner();
|
||||
data.validate().map_err(|error| {
|
||||
create_error!(FailedValidation {
|
||||
error: error.to_string()
|
||||
})
|
||||
})?;
|
||||
|
||||
let mut server = target.as_server(db).await?;
|
||||
let mut query = DatabasePermissionQuery::new(db, &user).server(&server);
|
||||
calculate_server_permissions(&mut query)
|
||||
.await
|
||||
.throw_if_lacking_channel_permission(ChannelPermission::ManageChannel)?;
|
||||
|
||||
let config = config().await;
|
||||
if server.categories.len() >= config.features.limits.global.server_categories {
|
||||
return Err(create_error!(TooManyCategories {
|
||||
max: config.features.limits.global.server_categories,
|
||||
}));
|
||||
};
|
||||
|
||||
let category = Category::create(db, &mut server, data).await?;
|
||||
|
||||
Ok(Json(category.into()))
|
||||
}
|
||||
38
crates/delta/src/routes/servers/category_delete.rs
Normal file
38
crates/delta/src/routes/servers/category_delete.rs
Normal file
@@ -0,0 +1,38 @@
|
||||
use revolt_config::config;
|
||||
use revolt_database::{
|
||||
util::{permissions::DatabasePermissionQuery, reference::Reference}, Category, Database, PartialCategory, Role, User
|
||||
};
|
||||
use revolt_models::v0::{self, DataEditCategory};
|
||||
use revolt_permissions::{calculate_server_permissions, ChannelPermission};
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::{serde::json::Json, State};
|
||||
use rocket_empty::EmptyResponse;
|
||||
use validator::Validate;
|
||||
|
||||
/// # Edits a category
|
||||
///
|
||||
/// Edits a server category.
|
||||
#[openapi(tag = "Server Categories")]
|
||||
#[delete("/<server>/categories/<category>")]
|
||||
pub async fn delete(
|
||||
db: &State<Database>,
|
||||
user: User,
|
||||
server: Reference,
|
||||
category: String
|
||||
) -> Result<EmptyResponse> {
|
||||
let mut server = server.as_server(db).await?;
|
||||
|
||||
let category = server.categories
|
||||
.get(&category)
|
||||
.ok_or(create_error!(UnknownCategory))?
|
||||
.clone();
|
||||
|
||||
let mut query = DatabasePermissionQuery::new(db, &user).server(&server);
|
||||
calculate_server_permissions(&mut query)
|
||||
.await
|
||||
.throw_if_lacking_channel_permission(ChannelPermission::ManageChannel)?;
|
||||
|
||||
category.delete(db, &mut server).await?;
|
||||
|
||||
Ok(EmptyResponse)
|
||||
}
|
||||
62
crates/delta/src/routes/servers/category_edit.rs
Normal file
62
crates/delta/src/routes/servers/category_edit.rs
Normal file
@@ -0,0 +1,62 @@
|
||||
use revolt_config::config;
|
||||
use revolt_database::{
|
||||
util::{permissions::DatabasePermissionQuery, reference::Reference}, Category, Database, PartialCategory, Role, User
|
||||
};
|
||||
use revolt_models::v0::{self, DataEditCategory};
|
||||
use revolt_permissions::{calculate_server_permissions, ChannelPermission};
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::{serde::json::Json, State};
|
||||
use validator::Validate;
|
||||
|
||||
/// # Edits a category
|
||||
///
|
||||
/// Edits a server category.
|
||||
#[openapi(tag = "Server Categories")]
|
||||
#[patch("/<server>/categories/<category>", data = "<data>")]
|
||||
pub async fn edit(
|
||||
db: &State<Database>,
|
||||
user: User,
|
||||
server: Reference,
|
||||
category: String,
|
||||
data: Json<v0::DataEditCategory>,
|
||||
) -> Result<Json<v0::Category>> {
|
||||
let data = data.into_inner();
|
||||
data.validate().map_err(|error| {
|
||||
create_error!(FailedValidation {
|
||||
error: error.to_string()
|
||||
})
|
||||
})?;
|
||||
|
||||
let mut server = server.as_server(db).await?;
|
||||
|
||||
let mut category = server.categories
|
||||
.get(&category)
|
||||
.ok_or(create_error!(UnknownCategory))?
|
||||
.clone();
|
||||
|
||||
let mut query = DatabasePermissionQuery::new(db, &user).server(&server);
|
||||
calculate_server_permissions(&mut query)
|
||||
.await
|
||||
.throw_if_lacking_channel_permission(ChannelPermission::ManageChannel)?;
|
||||
|
||||
let DataEditCategory {
|
||||
title,
|
||||
channels,
|
||||
remove
|
||||
} = data;
|
||||
|
||||
category.update(
|
||||
db,
|
||||
&mut server,
|
||||
PartialCategory {
|
||||
title,
|
||||
channels,
|
||||
..Default::default()
|
||||
},
|
||||
remove
|
||||
.map(|v| v.into_iter().map(Into::into).collect())
|
||||
.unwrap_or_default()
|
||||
).await?;
|
||||
|
||||
Ok(Json(category.into()))
|
||||
}
|
||||
50
crates/delta/src/routes/servers/category_permissions_set.rs
Normal file
50
crates/delta/src/routes/servers/category_permissions_set.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
use revolt_database::{
|
||||
util::{permissions::DatabasePermissionQuery, reference::Reference}, Database, User
|
||||
};
|
||||
use revolt_models::v0;
|
||||
use revolt_permissions::{calculate_category_permissions, ChannelPermission, Override};
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::{serde::json::Json, State};
|
||||
|
||||
/// # Set Role Permission
|
||||
///
|
||||
/// Sets permissions for the specified role in this channel.
|
||||
///
|
||||
/// Channel must be a `TextChannel` or `VoiceChannel`.
|
||||
#[openapi(tag = "Channel Permissions")]
|
||||
#[put("/<server>/categories/<category>/permissions/<role_id>", data = "<data>", rank = 2)]
|
||||
pub async fn set_role_permissions(
|
||||
db: &State<Database>,
|
||||
user: User,
|
||||
server: Reference,
|
||||
category: String,
|
||||
role_id: String,
|
||||
data: Json<v0::DataSetRolePermissions>,
|
||||
) -> Result<Json<v0::Category>> {
|
||||
let mut server = server.as_server(db).await?;
|
||||
let mut category = server.categories.get(&category).ok_or(create_error!(UnknownCategory))?.clone();
|
||||
|
||||
let mut query = DatabasePermissionQuery::new(db, &user).server(&server).category(&category);
|
||||
let permissions = calculate_category_permissions(&mut query).await;
|
||||
|
||||
permissions.throw_if_lacking_channel_permission(ChannelPermission::ManagePermissions)?;
|
||||
|
||||
if let Some(role) = server.roles.get(&role_id) {
|
||||
if role.rank <= query.get_member_rank().unwrap_or(i64::MIN) {
|
||||
return Err(create_error!(NotElevated));
|
||||
}
|
||||
|
||||
let current_value: Override = role.permissions.into();
|
||||
permissions
|
||||
.throw_permission_override(current_value, &data.permissions)
|
||||
.await?;
|
||||
|
||||
category
|
||||
.set_role_permission(db, &mut server, role_id, data.permissions.clone().into())
|
||||
.await?;
|
||||
|
||||
Ok(Json(category.into()))
|
||||
} else {
|
||||
Err(create_error!(NotFound))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
use revolt_database::{
|
||||
util::{permissions::DatabasePermissionQuery, reference::Reference}, Channel, Database, PartialCategory, PartialChannel, User
|
||||
};
|
||||
use revolt_models::v0::{self, DataDefaultChannelPermissions};
|
||||
use revolt_permissions::{calculate_category_permissions, calculate_channel_permissions, ChannelPermission};
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::{serde::json::Json, State};
|
||||
|
||||
/// # Set Default Permission
|
||||
///
|
||||
/// Sets permissions for the default role in this channel.
|
||||
///
|
||||
/// Channel must be a `Group`, `TextChannel` or `VoiceChannel`.
|
||||
#[openapi(tag = "Category Permissions")]
|
||||
#[put("/<server>/categories/<target>/permissions/default", data = "<data>", rank = 1)]
|
||||
pub async fn set_default_permissions(
|
||||
db: &State<Database>,
|
||||
user: User,
|
||||
server: Reference,
|
||||
target: String,
|
||||
data: Json<v0::DataDefaultCategoryPermissions>,
|
||||
) -> Result<Json<v0::Category>> {
|
||||
let data = data.into_inner();
|
||||
|
||||
let mut server = server.as_server(db).await?;
|
||||
let mut category = server.categories.get(&target).ok_or(create_error!(UnknownCategory))?.clone();
|
||||
|
||||
let mut query = DatabasePermissionQuery::new(db, &user).server(&server).category(&category);
|
||||
let permissions = calculate_category_permissions(&mut query).await;
|
||||
|
||||
permissions.throw_if_lacking_channel_permission(ChannelPermission::ManagePermissions)?;
|
||||
|
||||
permissions
|
||||
.throw_permission_override(category.default_permissions.map(|x| x.into()), &data.permissions)
|
||||
.await?;
|
||||
|
||||
category
|
||||
.update(
|
||||
db,
|
||||
&mut server,
|
||||
PartialCategory {
|
||||
default_permissions: Some(data.permissions.into()),
|
||||
..Default::default()
|
||||
},
|
||||
vec![],
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(Json(category.into()))
|
||||
}
|
||||
@@ -23,6 +23,11 @@ mod server_create;
|
||||
mod server_delete;
|
||||
mod server_edit;
|
||||
mod server_fetch;
|
||||
mod category_create;
|
||||
mod category_edit;
|
||||
mod category_delete;
|
||||
mod category_permissions_set_default;
|
||||
mod category_permissions_set;
|
||||
|
||||
pub fn routes() -> (Vec<Route>, OpenApi) {
|
||||
openapi_get_routes_spec![
|
||||
@@ -47,6 +52,11 @@ pub fn routes() -> (Vec<Route>, OpenApi) {
|
||||
roles_delete::delete,
|
||||
permissions_set::set_role_permission,
|
||||
permissions_set_default::set_default_permissions,
|
||||
emoji_list::list_emoji
|
||||
emoji_list::list_emoji,
|
||||
category_create::create,
|
||||
category_edit::edit,
|
||||
category_delete::delete,
|
||||
category_permissions_set_default::set_default_permissions,
|
||||
category_permissions_set::set_role_permission,
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::collections::HashSet;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use revolt_database::{
|
||||
util::{permissions::DatabasePermissionQuery, reference::Reference},
|
||||
@@ -38,7 +38,6 @@ pub async fn edit(
|
||||
&& data.icon.is_none()
|
||||
&& data.banner.is_none()
|
||||
&& data.system_messages.is_none()
|
||||
&& data.categories.is_none()
|
||||
// && data.nsfw.is_none()
|
||||
&& data.flags.is_none()
|
||||
&& data.analytics.is_none()
|
||||
@@ -63,18 +62,11 @@ pub async fn edit(
|
||||
{
|
||||
return Err(create_error!(NotPrivileged));
|
||||
}
|
||||
|
||||
// Changing categories requires manage channel
|
||||
if data.categories.is_some() {
|
||||
permissions.throw_if_lacking_channel_permission(ChannelPermission::ManageChannel)?;
|
||||
}
|
||||
|
||||
let v0::DataEditServer {
|
||||
name,
|
||||
description,
|
||||
icon,
|
||||
banner,
|
||||
categories,
|
||||
system_messages,
|
||||
flags,
|
||||
// nsfw,
|
||||
@@ -86,7 +78,6 @@ pub async fn edit(
|
||||
let mut partial = PartialServer {
|
||||
name,
|
||||
description,
|
||||
categories: categories.map(|v| v.into_iter().map(Into::into).collect()),
|
||||
system_messages: system_messages.map(Into::into),
|
||||
flags,
|
||||
// nsfw,
|
||||
@@ -119,23 +110,6 @@ pub async fn edit(
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(categories) = &mut partial.categories {
|
||||
let mut channel_ids = HashSet::new();
|
||||
for category in categories {
|
||||
for channel in &category.channels {
|
||||
if channel_ids.contains(channel) {
|
||||
return Err(create_error!(InvalidOperation));
|
||||
}
|
||||
|
||||
channel_ids.insert(channel.to_string());
|
||||
}
|
||||
|
||||
category
|
||||
.channels
|
||||
.retain(|item| server.channels.contains(item));
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Apply new icon
|
||||
if let Some(icon) = icon {
|
||||
partial.icon = Some(File::use_server_icon(db, &icon, &server.id, &user.id).await?);
|
||||
|
||||
Reference in New Issue
Block a user