use std::collections::HashSet; use revolt_database::{ util::{permissions::DatabasePermissionQuery, reference::Reference}, Database, File, PartialServer, 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; /// # Edit Server /// /// Edit a server by its id. #[openapi(tag = "Server Information")] #[patch("/", data = "")] pub async fn edit( db: &State, user: User, target: Reference, data: Json, ) -> Result> { 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); let permissions = calculate_server_permissions(&mut query).await; // Check permissions if data.name.is_none() && data.description.is_none() && 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() && data.discoverable.is_none() && data.remove.is_none() { return Ok(Json(server.into())); } else if data.name.is_some() || data.description.is_some() || data.icon.is_some() || data.banner.is_some() || data.system_messages.is_some() || data.analytics.is_some() || data.remove.is_some() { permissions.throw_if_lacking_channel_permission(ChannelPermission::ManageServer)?; } // Check we are privileged if changing sensitive fields if (data.flags.is_some() /*|| data.nsfw.is_some()*/ || data.discoverable.is_some()) && !user.privileged { 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, discoverable, analytics, remove, } = data; 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, discoverable, analytics, ..Default::default() }; // 1. Remove fields from object if let Some(fields) = &remove { if fields.contains(&v0::FieldsServer::Banner) { if let Some(banner) = &server.banner { db.mark_attachment_as_deleted(&banner.id).await?; } } if fields.contains(&v0::FieldsServer::Icon) { if let Some(icon) = &server.icon { db.mark_attachment_as_deleted(&icon.id).await?; } } } // 2. Validate changes if let Some(system_messages) = &partial.system_messages { for id in system_messages.clone().into_channel_ids() { if !server.channels.contains(&id) { return Err(create_error!(NotFound)); } } } 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?); server.icon = partial.icon.clone(); } // 4. Apply new banner if let Some(banner) = banner { partial.banner = Some(File::use_server_banner(db, &banner, &server.id, &user.id).await?); server.banner = partial.banner.clone(); } server .update( db, partial, remove .map(|v| v.into_iter().map(Into::into).collect()) .unwrap_or_default(), ) .await?; Ok(Json(server.into())) }