diff --git a/src/routes/servers/server_create.rs b/src/routes/servers/server_create.rs index 071d1cbb..97e12abd 100644 --- a/src/routes/servers/server_create.rs +++ b/src/routes/servers/server_create.rs @@ -1,13 +1,16 @@ -use std::collections::HashMap; - -use revolt_quark::{EmptyResponse, Result}; +use revolt_quark::{ + models::{Server, User}, + Db, Error, Result, DEFAULT_PERMISSION_CHANNEL_SERVER, DEFAULT_SERVER_PERMISSION, +}; use mongodb::bson::doc; -use rocket::serde::json::{Json, Value}; +use rocket::serde::json::Json; use serde::{Deserialize, Serialize}; use ulid::Ulid; use validator::Validate; +use crate::util::variables::MAX_SERVER_COUNT; + #[derive(Validate, Serialize, Deserialize)] pub struct Data { #[validate(length(min = 1, max = 32))] @@ -19,6 +22,35 @@ pub struct Data { } #[post("/create", data = "")] -pub async fn req(/*_idempotency: IdempotencyKey, user: User,*/ info: Json,) -> Result { - todo!() +pub async fn req(db: &Db, user: User, info: Json) -> Result> { + let info = info.into_inner(); + info.validate() + .map_err(|error| Error::FailedValidation { error })?; + + if !user.can_acquire_server(db).await? { + return Err(Error::TooManyServers { + max: *MAX_SERVER_COUNT, + }); + } + + let Data { + name, + description, + nsfw, + } = info; + + let server = Server { + id: Ulid::new().to_string(), + name, + description, + nsfw: nsfw.unwrap_or(false), + default_permissions: ( + *DEFAULT_SERVER_PERMISSION as i32, + *DEFAULT_PERMISSION_CHANNEL_SERVER as i32, + ), + ..Default::default() + }; + + db.insert_server(&server).await?; + Ok(Json(server)) } diff --git a/src/routes/servers/server_delete.rs b/src/routes/servers/server_delete.rs index 17185732..6899a1a8 100644 --- a/src/routes/servers/server_delete.rs +++ b/src/routes/servers/server_delete.rs @@ -1,8 +1,24 @@ -use revolt_quark::{EmptyResponse, Result}; +use revolt_quark::{models::User, perms, Db, EmptyResponse, Error, Ref, Result}; use mongodb::bson::doc; #[delete("/")] -pub async fn req(/*user: UserRef, target: Ref*/ target: String) -> Result { - todo!() +pub async fn req(db: &Db, user: User, target: Ref) -> Result { + let server = target.as_server(db).await?; + if !perms(&user) + .server(&server) + .calc_server(db) + .await + .get_view() + { + return Err(Error::NotFound); + } + + if server.owner == user.id { + db.delete_server(&server.id).await?; + } else { + db.delete_member(&server.id, &user.id).await?; + } + + Ok(EmptyResponse) } diff --git a/src/routes/servers/server_edit.rs b/src/routes/servers/server_edit.rs index a81bacac..97c8fb3e 100644 --- a/src/routes/servers/server_edit.rs +++ b/src/routes/servers/server_edit.rs @@ -1,6 +1,9 @@ use revolt_quark::{ - models::server::{Category, FieldsServer, SystemMessageChannels}, - EmptyResponse, Result, + models::{ + server::{Category, FieldsServer, PartialServer, SystemMessageChannels}, + File, Server, User, + }, + perms, Db, Error, Ref, Result, ServerPermission, }; use rocket::serde::json::Json; @@ -13,10 +16,14 @@ pub struct Data { name: Option, #[validate(length(min = 0, max = 1024))] description: Option, + icon: Option, banner: Option, + + #[validate] categories: Option>, system_messages: Option, + nsfw: Option, analytics: Option, @@ -25,9 +32,135 @@ pub struct Data { } #[patch("/", data = "")] -pub async fn req( - /*user: UserRef, target: Ref,*/ target: String, - data: Json, -) -> Result { - todo!() +pub async fn req(db: &Db, user: User, target: Ref, data: Json) -> Result> { + let data = data.into_inner(); + data.validate() + .map_err(|error| Error::FailedValidation { error })?; + + let mut server = target.as_server(db).await?; + let permissions = perms(&user).server(&server).calc_server(db).await; + if !permissions.get_view() { + return Err(Error::NotFound); + } + + // 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.analytics.is_none() + && data.remove.is_none() + { + return Ok(Json(server)); + } else if (data.name.is_some() + || data.description.is_some() + || data.icon.is_some() + || data.banner.is_some() + || data.system_messages.is_some() + || data.nsfw.is_some() + || data.analytics.is_some() + || data.remove.is_some()) + && !permissions.get_manage_server() + { + return Err(Error::MissingPermission { + permission: ServerPermission::ManageServer as i32, + }); + } else if (data.categories.is_some()) && !permissions.get_manage_channels() { + return Err(Error::MissingPermission { + permission: ServerPermission::ManageChannels as i32, + }); + } + + let Data { + name, + description, + icon, + banner, + categories, + system_messages, + nsfw, + analytics, + remove, + } = data; + + let mut partial = PartialServer { + name, + description, + categories, + system_messages, + nsfw, + analytics, + ..Default::default() + }; + + // 1. Remove fields from object + if let Some(fields) = &remove { + if fields.contains(&FieldsServer::Banner) { + if let Some(banner) = &server.banner { + db.mark_attachment_as_deleted(&banner.id).await?; + } + } + + if fields.contains(&FieldsServer::Icon) { + if let Some(icon) = &server.icon { + db.mark_attachment_as_deleted(&icon.id).await?; + } + } + + for field in fields { + server.remove(field); + } + } + + // 2. Apply new icon + if let Some(icon) = icon { + partial.icon = Some(File::use_server_icon(db, &icon, &server.id).await?); + server.icon = partial.icon.clone(); + } + + // 3. Apply new banner + if let Some(banner) = banner { + partial.banner = Some(File::use_server_icon(db, &banner, &server.id).await?); + server.banner = partial.banner.clone(); + } + + // 4. Pass-through and validate changes + if let Some(name) = &partial.name { + server.name = name.clone(); + } + + if let Some(description) = &partial.description { + server.description.replace(description.clone()); + } + + if let Some(categories) = &partial.categories { + server.categories.replace(categories.clone()); + } + + if let Some(system_messages) = &partial.system_messages { + let channels = system_messages.clone().into_channel_ids(); + if !db + .check_channels_exist(&channels.into_iter().collect()) + .await? + { + return Err(Error::NotFound); + } + + server.system_messages.replace(system_messages.clone()); + } + + if let Some(analytics) = &partial.analytics { + server.analytics = *analytics; + } + + if let Some(nsfw) = &partial.nsfw { + server.nsfw = *nsfw; + } + + db.update_server(&server.id, &partial, remove.unwrap_or_else(Vec::new)) + .await?; + Ok(Json(server)) } diff --git a/src/routes/servers/server_fetch.rs b/src/routes/servers/server_fetch.rs index fe9f9b9e..1ef615ee 100644 --- a/src/routes/servers/server_fetch.rs +++ b/src/routes/servers/server_fetch.rs @@ -1,8 +1,20 @@ -use revolt_quark::{Error, Result}; - -use rocket::serde::json::Value; +use revolt_quark::{ + models::{Server, User}, + perms, Db, Error, Ref, Result, +}; +use rocket::serde::json::Json; #[get("/")] -pub async fn req(/*user: UserRef, target: Ref*/ target: String) -> Result { - todo!() +pub async fn req(db: &Db, user: User, target: Ref) -> Result> { + let server = target.as_server(db).await?; + if !perms(&user) + .server(&server) + .calc_server(db) + .await + .get_view() + { + return Err(Error::NotFound); + } + + Ok(Json(server)) } diff --git a/src/routes/sync/get_settings.rs b/src/routes/sync/get_settings.rs index 04e2a223..3f90c65f 100644 --- a/src/routes/sync/get_settings.rs +++ b/src/routes/sync/get_settings.rs @@ -1,4 +1,7 @@ -use revolt_quark::{Result, models::{UserSettings, User}, Db}; +use revolt_quark::{ + models::{User, UserSettings}, + Db, Result, +}; use mongodb::bson::doc; use rocket::serde::json::Json; @@ -11,5 +14,7 @@ pub struct Options { #[post("/settings/fetch", data = "")] pub async fn req(db: &Db, user: User, options: Json) -> Result> { - db.fetch_user_settings(&user.id, &options.into_inner().keys).await.map(Json) + db.fetch_user_settings(&user.id, &options.into_inner().keys) + .await + .map(Json) } diff --git a/src/routes/sync/get_unreads.rs b/src/routes/sync/get_unreads.rs index dcc24f19..e6349109 100644 --- a/src/routes/sync/get_unreads.rs +++ b/src/routes/sync/get_unreads.rs @@ -1,4 +1,7 @@ -use revolt_quark::{Result, models::{ChannelUnread, User}, Db}; +use revolt_quark::{ + models::{ChannelUnread, User}, + Db, Result, +}; use mongodb::bson::doc; use rocket::serde::json::Json; diff --git a/src/routes/sync/set_settings.rs b/src/routes/sync/set_settings.rs index 75a30dfb..f4d632c2 100644 --- a/src/routes/sync/set_settings.rs +++ b/src/routes/sync/set_settings.rs @@ -1,5 +1,5 @@ use revolt_quark::models::User; -use revolt_quark::{EmptyResponse, Result, Db}; +use revolt_quark::{Db, EmptyResponse, Result}; use chrono::prelude::*; use rocket::serde::json::Json; @@ -29,11 +29,10 @@ pub async fn req(db: &Db, user: User, data: Json, options: Options) -> Res let mut settings = HashMap::new(); for (key, data) in data { - settings.insert(key, ( - timestamp, - data - )); + settings.insert(key, (timestamp, data)); } - db.set_user_settings(&user.id, &settings).await.map(|_| EmptyResponse) + db.set_user_settings(&user.id, &settings) + .await + .map(|_| EmptyResponse) } diff --git a/src/routes/users/edit_user.rs b/src/routes/users/edit_user.rs index 29542d3a..e4c89519 100644 --- a/src/routes/users/edit_user.rs +++ b/src/routes/users/edit_user.rs @@ -47,6 +47,7 @@ pub async fn req(db: &State, mut user: User, data: Json) -> Resu return Ok(Json(user)); } + // 1. Remove fields from object if let Some(fields) = &data.remove { if fields.contains(&FieldsUser::Avatar) { if let Some(avatar) = &user.avatar { @@ -69,11 +70,13 @@ pub async fn req(db: &State, mut user: User, data: Json) -> Resu let mut partial: PartialUser = Default::default(); + // 2. Apply new avatar if let Some(avatar) = data.avatar { partial.avatar = Some(File::use_avatar(db, &avatar, &user.id).await?); user.avatar = partial.avatar.clone(); } + // 3. Apply new status if let Some(status) = data.status { let mut new_status = user.status.take().unwrap_or_default(); if let Some(text) = status.text { @@ -88,6 +91,7 @@ pub async fn req(db: &State, mut user: User, data: Json) -> Resu user.status = partial.status.clone(); } + // 4. Apply new profile if let Some(profile) = data.profile { let mut new_profile = user.profile.take().unwrap_or_default(); if let Some(content) = profile.content { @@ -104,5 +108,6 @@ pub async fn req(db: &State, mut user: User, data: Json) -> Resu db.update_user(&user.id, &partial, data.remove.unwrap_or_default()) .await?; + Ok(Json(user.foreign())) }