use std::collections::HashSet; use authifier::models::{totp::Totp, Account, ValidatedTicket}; 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, Request, State}; use validator::Validate; /// # Edit Server /// /// Edit a server by its id. #[openapi(tag = "Server Information")] #[patch("/", data = "")] pub async fn edit( db: &State, account: Account, user: User, target: Reference<'_>, data: Json, validated_ticket: Option, ) -> 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.owner.is_none() && data.remove.is_empty() { 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_empty() { permissions.throw_if_lacking_channel_permission(ChannelPermission::ManageServer)?; } // Check we are the server owner or privileged if changing sensitive fields if data.owner.is_some() { if user.id != server.owner && !user.privileged { return Err(create_error!(NotOwner)); } if validated_ticket.is_none() { return Err(create_error!(InvalidCredentials)); } } // 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, owner, 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, owner: owner.clone(), ..Default::default() }; // 1. Remove fields from object if remove.contains(&v0::FieldsServer::Banner) { if let Some(banner) = &server.banner { db.mark_attachment_as_deleted(&banner.id).await?; } } if remove.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(); } // 5. Transfer ownership if let Some(owner) = owner { let owner_reference = Reference::from_unchecked(&owner); // Check if member exists owner_reference.as_member(db, &server.id).await?; let owner_user = owner_reference.as_user(db).await?; if owner_user.bot.is_some() { return Err(create_error!(InvalidOperation)); } server.owner = owner; partial.owner = Some(server.owner.clone()); } server .update(db, partial, remove.into_iter().map(Into::into).collect()) .await?; Ok(Json(server.into())) }