From a8d5fcec9e3a69646793225d83571c83e7e0e778 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Thu, 10 Feb 2022 15:54:41 +0000 Subject: [PATCH] feat(bots): implement bot routes --- src/routes/bots/create.rs | 35 ++++++++++++-- src/routes/bots/delete.rs | 15 ++++-- src/routes/bots/edit.rs | 74 +++++++++++++++++++++++++++--- src/routes/bots/fetch.rs | 21 +++++++-- src/routes/bots/fetch_owned.rs | 16 +++++-- src/routes/bots/fetch_public.rs | 18 ++++++-- src/routes/bots/invite.rs | 53 +++++++++++++-------- src/routes/channels/channel_ack.rs | 20 ++++---- src/routes/channels/voice_join.rs | 2 +- src/routes/servers/member_edit.rs | 3 +- src/routes/servers/roles_edit.rs | 3 +- src/routes/servers/server_edit.rs | 4 +- 12 files changed, 206 insertions(+), 58 deletions(-) diff --git a/src/routes/bots/create.rs b/src/routes/bots/create.rs index e5314287..445bc672 100644 --- a/src/routes/bots/create.rs +++ b/src/routes/bots/create.rs @@ -1,9 +1,14 @@ -use crate::util::regex::RE_USERNAME; +use crate::util::{regex::RE_USERNAME, variables::MAX_BOT_COUNT}; -use revolt_quark::Result; +use nanoid::nanoid; +use revolt_quark::{ + models::{Bot, User}, + Db, Error, Result, +}; -use rocket::serde::json::{Json, Value}; +use rocket::serde::json::Json; use serde::{Deserialize, Serialize}; +use ulid::Ulid; use validator::Validate; #[derive(Validate, Serialize, Deserialize)] @@ -13,6 +18,26 @@ pub struct Data { } #[post("/create", data = "")] -pub async fn create_bot(/* user: User ,*/ info: Json) -> Result { - todo!() +pub async fn create_bot(db: &Db, user: User, info: Json) -> Result> { + if user.bot.is_some() { + return Err(Error::IsBot); + } + + let info = info.into_inner(); + info.validate() + .map_err(|error| Error::FailedValidation { error })?; + + if db.get_number_of_bots_by_user(&user.id).await? >= *MAX_BOT_COUNT { + return Err(Error::ReachedMaximumBots); + } + + let bot = Bot { + id: Ulid::new().to_string(), + owner: user.id, + token: nanoid!(64), + ..Default::default() + }; + + db.insert_bot(&bot).await?; + Ok(Json(bot)) } diff --git a/src/routes/bots/delete.rs b/src/routes/bots/delete.rs index a7e719ee..0cc22c06 100644 --- a/src/routes/bots/delete.rs +++ b/src/routes/bots/delete.rs @@ -1,7 +1,16 @@ use rauth::util::EmptyResponse; -use revolt_quark::Result; +use revolt_quark::{models::User, Db, Error, Ref, Result}; #[delete("/")] -pub async fn delete_bot(/*user: UserRef, target: Ref*/ target: String,) -> Result { - todo!() +pub async fn delete_bot(db: &Db, user: User, target: Ref) -> Result { + if user.bot.is_some() { + return Err(Error::IsBot); + } + + let bot = target.as_bot(db).await?; + if bot.owner != user.id { + return Err(Error::NotFound); + } + + db.delete_bot(&bot.id).await.map(|_| EmptyResponse) } diff --git a/src/routes/bots/edit.rs b/src/routes/bots/edit.rs index 1acacadb..4e78b118 100644 --- a/src/routes/bots/edit.rs +++ b/src/routes/bots/edit.rs @@ -1,6 +1,12 @@ use crate::util::regex::RE_USERNAME; -use revolt_quark::{models::bot::FieldsBot, EmptyResponse, Result}; +use revolt_quark::{ + models::{ + bot::{FieldsBot, PartialBot}, + Bot, User, + }, + Db, Error, Ref, Result, +}; use mongodb::bson::doc; use rocket::serde::json::Json; @@ -15,13 +21,67 @@ pub struct Data { public: Option, analytics: Option, interactions_url: Option, - remove: Option, + #[validate(length(min = 1))] + remove: Option>, } #[patch("/", data = "")] -pub async fn edit_bot( - /*user: UserRef, target: Ref,*/ target: String, - data: Json, -) -> Result { - todo!() +pub async fn edit_bot(db: &Db, user: User, target: Ref, data: Json) -> Result> { + if user.bot.is_some() { + return Err(Error::IsBot); + } + + let data = data.into_inner(); + data.validate() + .map_err(|error| Error::FailedValidation { error })?; + + let mut bot = target.as_bot(db).await?; + if bot.owner != user.id { + return Err(Error::NotFound); + } + + if let Some(name) = data.name { + if db.is_username_taken(&name).await? { + return Err(Error::UsernameTaken); + } + + let mut user = db.fetch_user(&bot.id).await?; + user.update_username(db, name).await?; + } + + if data.public.is_none() + || data.analytics.is_none() + || data.interactions_url.is_none() + || data.remove.is_none() + { + return Ok(Json(bot)); + } + + let Data { + public, + analytics, + interactions_url, + remove, + .. + } = data; + let mut partial = PartialBot { + public, + analytics, + interactions_url, + ..Default::default() + }; + + if let Some(remove) = &remove { + for field in remove { + bot.remove(field); + } + + if remove.iter().any(|x| x == &FieldsBot::Token) { + partial.token = Some(bot.token.clone()); + } + } + + db.update_bot(&bot.id, &partial, remove.unwrap_or_else(Vec::new)) + .await?; + Ok(Json(bot)) } diff --git a/src/routes/bots/fetch.rs b/src/routes/bots/fetch.rs index eb0ab2df..c9920e5c 100644 --- a/src/routes/bots/fetch.rs +++ b/src/routes/bots/fetch.rs @@ -1,8 +1,19 @@ -use revolt_quark::Result; - -use serde_json::Value; +use revolt_quark::{ + models::{Bot, User}, + Db, Error, Ref, Result, +}; +use rocket::serde::json::Json; #[get("/")] -pub async fn fetch_bot(/*user: UserRef, target: Ref*/ target: String) -> Result { - todo!() +pub async fn fetch_bot(db: &Db, user: User, target: Ref) -> Result> { + if user.bot.is_some() { + return Err(Error::IsBot); + } + + let bot = target.as_bot(db).await?; + if bot.owner != user.id { + return Err(Error::NotFound); + } + + Ok(Json(bot)) } diff --git a/src/routes/bots/fetch_owned.rs b/src/routes/bots/fetch_owned.rs index 1a4272a6..e0e0d6bb 100644 --- a/src/routes/bots/fetch_owned.rs +++ b/src/routes/bots/fetch_owned.rs @@ -1,8 +1,14 @@ -use revolt_quark::Result; - -use serde_json::Value; +use revolt_quark::{ + models::{Bot, User}, + Db, Error, Result, +}; +use rocket::serde::json::Json; #[get("/@me")] -pub async fn fetch_owned_bots(/*user: UserRef*/) -> Result { - todo!() +pub async fn fetch_owned_bots(db: &Db, user: User) -> Result>> { + if user.bot.is_some() { + return Err(Error::IsBot); + } + + db.fetch_bots_by_user(&user.id).await.map(Json) } diff --git a/src/routes/bots/fetch_public.rs b/src/routes/bots/fetch_public.rs index 9a07ebe3..930b4625 100644 --- a/src/routes/bots/fetch_public.rs +++ b/src/routes/bots/fetch_public.rs @@ -1,8 +1,20 @@ -use revolt_quark::Result; +use revolt_quark::{Db, Error, Ref, Result}; use serde_json::Value; #[get("//invite")] -pub async fn fetch_public_bot(/*user: UserRef, target: Ref*/ target: String) -> Result { - todo!() +pub async fn fetch_public_bot(db: &Db, target: Ref) -> Result { + let bot = target.as_bot(db).await?; + if !bot.public { + return Err(Error::NotFound); + } + + let user = db.fetch_user(&bot.id).await?; + + Ok(json!({ + "_id": bot.id, + "username": user.username, + "avatar": user.avatar, + "description": user.profile.map(|p| p.content) + })) } diff --git a/src/routes/bots/invite.rs b/src/routes/bots/invite.rs index 608bcc8e..9b8c59ce 100644 --- a/src/routes/bots/invite.rs +++ b/src/routes/bots/invite.rs @@ -1,29 +1,46 @@ -use revolt_quark::{EmptyResponse, Result}; +use revolt_quark::{models::{User, Channel}, EmptyResponse, Error, Ref, Result, Db, perms, ServerPermission, ChannelPermission}; use rocket::serde::json::Json; use serde::Deserialize; -#[derive(Deserialize)] -pub struct ServerId { - server: String, -} - -#[derive(Deserialize)] -pub struct GroupId { - group: String, -} - #[derive(Deserialize)] #[serde(untagged)] pub enum Destination { - Server(ServerId), - Group(GroupId), + Server { server: String }, + Group { group: String }, } #[post("//invite", data = "")] -pub async fn invite_bot( - /*user: UserRef, target: Ref,*/ target: String, - dest: Json, -) -> Result { - todo!() +pub async fn invite_bot(db: &Db, user: User, target: Ref, dest: Json) -> Result { + if user.bot.is_some() { + return Err(Error::IsBot); + } + + let bot = target.as_bot(db).await?; + if !bot.public && bot.owner != user.id { + return Err(Error::BotIsPrivate) + } + + match dest.into_inner() { + Destination::Server { server } => { + let server = db.fetch_server(&server).await?; + if !perms(&user).server(&server).calc_server(db).await.get_manage_server() { + return Err(Error::MissingPermission { permission: ServerPermission::ManageServer as i32 }) + } + + db.insert_member(&server.id, &bot.id).await.map(|_| EmptyResponse) + }, + Destination::Group{ group } => { + let channel = db.fetch_channel(&group).await?; + if !perms(&user).channel(&channel).calc_channel(db).await.get_invite_others() { + return Err(Error::MissingPermission { permission: ChannelPermission::InviteOthers as i32 }) + } + + if let Channel::Group { id, .. } = channel { + db.add_user_to_group(&id, &bot.id).await.map(|_| EmptyResponse) + } else { + Err(Error::InvalidOperation) + } + } + } } diff --git a/src/routes/channels/channel_ack.rs b/src/routes/channels/channel_ack.rs index 81fc99f2..34a131d7 100644 --- a/src/routes/channels/channel_ack.rs +++ b/src/routes/channels/channel_ack.rs @@ -1,14 +1,18 @@ -use revolt_quark::{EmptyResponse, Error, Result, Ref, models::User, Db, perms}; +use revolt_quark::{models::User, perms, Db, EmptyResponse, Error, Ref, Result}; #[put("//ack/")] -pub async fn req( - db: &Db, - user: User, target: Ref, message: Ref -) -> Result { +pub async fn req(db: &Db, user: User, target: Ref, message: Ref) -> Result { let channel = target.as_channel(db).await?; - if !perms(&user).channel(&channel).calc_channel(db).await.get_view() { - return Err(Error::NotFound) + if !perms(&user) + .channel(&channel) + .calc_channel(db) + .await + .get_view() + { + return Err(Error::NotFound); } - db.acknowledge_message(channel.id(), &user.id, &message.id).await.map(|_| EmptyResponse) + db.acknowledge_message(channel.id(), &user.id, &message.id) + .await + .map(|_| EmptyResponse) } diff --git a/src/routes/channels/voice_join.rs b/src/routes/channels/voice_join.rs index 14cc7c12..3b278052 100644 --- a/src/routes/channels/voice_join.rs +++ b/src/routes/channels/voice_join.rs @@ -10,5 +10,5 @@ struct CreateUserResponse { #[post("/<_target>/join_call")] pub async fn req(_user: User, _target: Ref) -> Result { - todo!() + unimplemented!() } diff --git a/src/routes/servers/member_edit.rs b/src/routes/servers/member_edit.rs index 9e0914fd..d7e51f62 100644 --- a/src/routes/servers/member_edit.rs +++ b/src/routes/servers/member_edit.rs @@ -10,7 +10,8 @@ pub struct Data { nickname: Option, avatar: Option, roles: Option>, - remove: Option, + #[validate(length(min = 1))] + remove: Option>, } #[patch("//members/", data = "")] diff --git a/src/routes/servers/roles_edit.rs b/src/routes/servers/roles_edit.rs index 6a0201c7..f890e459 100644 --- a/src/routes/servers/roles_edit.rs +++ b/src/routes/servers/roles_edit.rs @@ -13,7 +13,8 @@ pub struct Data { colour: Option, hoist: Option, rank: Option, - // remove: Option, + // #[validate(length(min = 1))] + // remove: Option>, } #[patch("//roles/", data = "")] diff --git a/src/routes/servers/server_edit.rs b/src/routes/servers/server_edit.rs index bf168010..a81bacac 100644 --- a/src/routes/servers/server_edit.rs +++ b/src/routes/servers/server_edit.rs @@ -17,9 +17,11 @@ pub struct Data { banner: Option, categories: Option>, system_messages: Option, - remove: Option, nsfw: Option, analytics: Option, + + #[validate(length(min = 1))] + remove: Option>, } #[patch("/", data = "")]