From 537f07b4a985c2dbeea78102435b84753e4fe4fc Mon Sep 17 00:00:00 2001 From: Paul Makles <paulmakles@gmail.com> Date: Sat, 11 Apr 2020 16:44:46 +0100 Subject: [PATCH] Add guild bans / unbans. --- src/database/guild.rs | 18 +++- src/database/permissions.rs | 40 +++---- src/guards/guild.rs | 24 ++++- src/routes/channel.rs | 7 +- src/routes/guild.rs | 201 ++++++++++++++++++++++++++++++------ src/routes/mod.rs | 4 +- src/routes/user.rs | 4 +- 7 files changed, 237 insertions(+), 61 deletions(-) diff --git a/src/database/guild.rs b/src/database/guild.rs index b2ff3b5..9368007 100644 --- a/src/database/guild.rs +++ b/src/database/guild.rs @@ -1,10 +1,17 @@ use bson::doc; use serde::{Deserialize, Serialize}; +#[derive(Serialize, Deserialize, Debug)] +pub struct MemberRef { + pub guild: String, + pub user: String, +} + #[derive(Serialize, Deserialize, Debug)] pub struct Member { - pub id: String, - pub nickname: String, + #[serde(rename = "_id")] + pub id: MemberRef, + pub nickname: Option<String>, } #[derive(Serialize, Deserialize, Debug)] @@ -14,6 +21,12 @@ pub struct Invite { pub channel: String, } +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Ban { + pub id: String, + pub reason: String, +} + #[derive(Serialize, Deserialize, Debug)] pub struct Guild { #[serde(rename = "_id")] @@ -25,6 +38,7 @@ pub struct Guild { pub channels: Vec<String>, pub invites: Vec<Invite>, + pub bans: Vec<Ban>, pub default_permissions: u32, } diff --git a/src/database/permissions.rs b/src/database/permissions.rs index 02e6397..3b34c02 100644 --- a/src/database/permissions.rs +++ b/src/database/permissions.rs @@ -1,11 +1,10 @@ use super::mutual::has_mutual_connection; -use crate::database::get_collection; +use crate::database::guild::Member; use crate::database::user::UserRelationship; use crate::guards::auth::UserRef; use crate::guards::channel::ChannelRef; -use crate::guards::guild::GuildRef; +use crate::guards::guild::{get_member, GuildRef}; -use bson::doc; use num_enum::TryFromPrimitive; #[derive(Debug, PartialEq, Eq, TryFromPrimitive)] @@ -91,6 +90,7 @@ pub struct PermissionCalculator { pub user: UserRef, pub channel: Option<ChannelRef>, pub guild: Option<GuildRef>, + pub member: Option<Member>, } impl PermissionCalculator { @@ -99,6 +99,7 @@ impl PermissionCalculator { user, channel: None, guild: None, + member: None, } } @@ -116,7 +117,7 @@ impl PermissionCalculator { } } - pub fn calculate(self) -> u32 { + pub fn fetch_data(mut self) -> PermissionCalculator { let guild = if let Some(value) = self.guild { Some(value) } else if let Some(channel) = &self.channel { @@ -135,22 +136,23 @@ impl PermissionCalculator { None }; - let mut permissions: u32 = 0; - if let Some(guild) = guild { - if let Ok(result) = get_collection("members").find_one( - doc! { - "_id.user": &self.user.id, - "_id.guild": &guild.id, - }, - None, - ) { - if result.is_some() { - if guild.owner == self.user.id { - return u32::MAX; - } + if let Some(guild) = &guild { + self.member = get_member(guild, &self.user.id); + } - permissions = guild.default_permissions as u32; + self.guild = guild; + self + } + + pub fn calculate(&self) -> u32 { + let mut permissions: u32 = 0; + if let Some(guild) = &self.guild { + if let Some(_member) = &self.member { + if guild.owner == self.user.id { + return u32::MAX; } + + permissions = guild.default_permissions as u32; } } @@ -206,7 +208,7 @@ impl PermissionCalculator { permissions } - pub fn as_permission(self) -> MemberPermissions<[u32; 1]> { + pub fn as_permission(&self) -> MemberPermissions<[u32; 1]> { MemberPermissions([self.calculate()]) } } diff --git a/src/guards/guild.rs b/src/guards/guild.rs index 1ca1f09..b7da669 100644 --- a/src/guards/guild.rs +++ b/src/guards/guild.rs @@ -1,10 +1,11 @@ -use bson::{doc, from_bson, Document}; +use bson::{doc, from_bson, Bson, Document}; use mongodb::options::FindOneOptions; use rocket::http::RawStr; use rocket::request::FromParam; use serde::{Deserialize, Serialize}; use crate::database; +use crate::database::guild::{Ban, Member}; #[derive(Serialize, Deserialize, Debug, Clone)] pub struct GuildRef { @@ -15,6 +16,8 @@ pub struct GuildRef { pub owner: String, pub channels: Vec<String>, + pub bans: Vec<Ban>, + pub default_permissions: i32, } @@ -28,6 +31,7 @@ impl GuildRef { "description": 1, "owner": 1, "channels": 1, + "bans": 1, "default_permissions": 1 }) .build(), @@ -73,3 +77,21 @@ impl<'r> FromParam<'r> for GuildRef { } } } + +pub fn get_member(guild: &GuildRef, member: &String) -> Option<Member> { + if let Ok(result) = database::get_collection("members").find_one( + doc! { + "_id.guild": &guild.id, + "_id.user": &member, + }, + None, + ) { + if let Some(doc) = result { + Some(from_bson(Bson::Document(doc)).expect("Failed to unwrap member.")) + } else { + None + } + } else { + None + } +} diff --git a/src/routes/channel.rs b/src/routes/channel.rs index 2906399..8dbf498 100644 --- a/src/routes/channel.rs +++ b/src/routes/channel.rs @@ -29,13 +29,14 @@ macro_rules! with_permissions { ($user: expr, $target: expr) => {{ let permissions = PermissionCalculator::new($user.clone()) .channel($target.clone()) - .as_permission(); + .fetch_data(); - if !permissions.get_access() { + let value = permissions.as_permission(); + if !value.get_access() { return None; } - permissions + value }}; } diff --git a/src/routes/guild.rs b/src/routes/guild.rs index 142df3b..7a11d51 100644 --- a/src/routes/guild.rs +++ b/src/routes/guild.rs @@ -1,11 +1,12 @@ use super::channel::ChannelType; use super::Response; -use crate::database::{self, channel::Channel, PermissionCalculator, Permission}; +use crate::database::{self, channel::Channel, Permission, PermissionCalculator}; use crate::guards::auth::UserRef; -use crate::guards::guild::GuildRef; +use crate::guards::guild::{get_member, GuildRef}; use bson::{doc, from_bson, Bson}; use mongodb::options::FindOptions; +use rocket::request::Form; use rocket_contrib::json::Json; use serde::{Deserialize, Serialize}; use ulid::Ulid; @@ -14,13 +15,14 @@ macro_rules! with_permissions { ($user: expr, $target: expr) => {{ let permissions = PermissionCalculator::new($user.clone()) .guild($target.clone()) - .as_permission(); + .fetch_data(); - if !permissions.get_access() { + let value = permissions.as_permission(); + if !value.get_access() { return None; } - permissions + (value, permissions.member.unwrap()) }}; } @@ -214,6 +216,7 @@ pub fn create_guild(user: UserRef, info: Json<CreateGuild>) -> Response { &channel_id ], "invites": [], + "bans": [], "default_permissions": 51, }, None, @@ -262,26 +265,14 @@ pub fn fetch_members(user: UserRef, target: GuildRef) -> Option<Response> { pub fn fetch_member(user: UserRef, target: GuildRef, other: String) -> Option<Response> { with_permissions!(user, target); - if let Ok(result) = database::get_collection("members").find_one( - doc! { - "_id.guild": &target.id, - "_id.user": &other, - }, - None, - ) { - if let Some(doc) = result { - Some(Response::Success(json!({ - "id": doc.get_document("_id").unwrap().get_str("user").unwrap(), - "nickname": doc.get_str("nickname").ok(), - }))) - } else { - Some(Response::NotFound( - json!({ "error": "User not part of guild." }), - )) - } + if let Some(member) = get_member(&target, &other) { + Some(Response::Success(json!({ + "id": member.id.user, + "nickname": member.nickname, + }))) } else { Some(Response::InternalServerError( - json!({ "error": "Failed to fetch member." }), + json!({ "error": "Failed to fetch member or user does not exist." }), )) } } @@ -289,23 +280,34 @@ pub fn fetch_member(user: UserRef, target: GuildRef, other: String) -> Option<Re /// kick a guild member #[delete("/<target>/members/<other>")] pub fn kick_member(user: UserRef, target: GuildRef, other: String) -> Option<Response> { - let permissions = with_permissions!(user, target); + let (permissions, _) = with_permissions!(user, target); + + if user.id == other { + return Some(Response::BadRequest( + json!({ "error": "Cannot kick yourself." }), + )); + } if !permissions.get_kick_members() { return Some(Response::LackingPermission(Permission::KickMembers)); } - if user.id == other { - return Some(Response::BadRequest(json!({ "error": "Cannot kick yourself." }))) + if get_member(&target, &other).is_none() { + return Some(Response::BadRequest( + json!({ "error": "User not part of guild." }), + )); } - if database::get_collection("members").delete_one( - doc! { - "_id.guild": &target.id, - "_id.user": &other, - }, - None, - ).is_ok() { + if database::get_collection("members") + .delete_one( + doc! { + "_id.guild": &target.id, + "_id.user": &other, + }, + None, + ) + .is_ok() + { Some(Response::Result(super::Status::Ok)) } else { Some(Response::InternalServerError( @@ -313,3 +315,136 @@ pub fn kick_member(user: UserRef, target: GuildRef, other: String) -> Option<Res )) } } + +#[derive(Serialize, Deserialize, FromForm)] +pub struct BanOptions { + reason: Option<String>, +} + +/// ban a guild member +#[put("/<target>/members/<other>/ban?<options..>")] +pub fn ban_member( + user: UserRef, + target: GuildRef, + other: String, + options: Form<BanOptions>, +) -> Option<Response> { + let (permissions, _) = with_permissions!(user, target); + let reason: String = options + .reason + .clone() + .unwrap_or("No reason specified.".to_string()) + .chars() + .take(64) + .collect(); + + if user.id == other { + return Some(Response::BadRequest( + json!({ "error": "Cannot ban yourself." }), + )); + } + + if !permissions.get_ban_members() { + return Some(Response::LackingPermission(Permission::BanMembers)); + } + + if get_member(&target, &other).is_none() { + return Some(Response::BadRequest( + json!({ "error": "User not part of guild." }), + )); + } + + if database::get_collection("guilds") + .update_one( + doc! { "_id": &target.id }, + doc! { + "$push": { + "bans": { + "id": &other, + "reason": reason, + } + } + }, + None, + ) + .is_err() + { + return Some(Response::BadRequest( + json!({ "error": "Failed to add ban to guild." }), + )); + } + + if database::get_collection("members") + .delete_one( + doc! { + "_id.guild": &target.id, + "_id.user": &other, + }, + None, + ) + .is_ok() + { + Some(Response::Result(super::Status::Ok)) + } else { + Some(Response::InternalServerError( + json!({ "error": "Failed to kick member after adding to ban list." }), + )) + } +} + +/// unban a guild member +#[delete("/<target>/members/<other>/ban")] +pub fn unban_member(user: UserRef, target: GuildRef, other: String) -> Option<Response> { + let (permissions, _) = with_permissions!(user, target); + + if user.id == other { + return Some(Response::BadRequest( + json!({ "error": "Cannot unban yourself (not checking if you're banned)." }), + )); + } + + if !permissions.get_ban_members() { + return Some(Response::LackingPermission(Permission::BanMembers)); + } + + if target + .fetch_data_given( + doc! { + "bans": { + "$elemMatch": { + "id": &other + } + } + }, + doc! { } + ) + .is_none() + { + return Some(Response::BadRequest(json!({ "error": "User not banned." }))); + } + + if database::get_collection("guilds") + .update_one( + doc! { + "_id": &target.id + }, + doc! { + "$pull": { + "bans": { + "$elemMatch": { + "id": &other + } + } + } + }, + None, + ) + .is_ok() + { + Some(Response::Result(super::Status::Ok)) + } else { + Some(Response::BadRequest( + json!({ "error": "Failed to remove ban." }), + )) + } +} diff --git a/src/routes/mod.rs b/src/routes/mod.rs index fe95c2d..830cce9 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -109,7 +109,9 @@ pub fn mount(rocket: Rocket) -> Rocket { guild::create_guild, guild::fetch_members, guild::fetch_member, - guild::kick_member + guild::kick_member, + guild::ban_member, + guild::unban_member ], ) } diff --git a/src/routes/user.rs b/src/routes/user.rs index 6c4543a..f2dee0f 100644 --- a/src/routes/user.rs +++ b/src/routes/user.rs @@ -42,14 +42,14 @@ pub fn user(user: UserRef, target: UserRef) -> Response { } #[derive(Serialize, Deserialize)] -pub struct Query { +pub struct LookupQuery { username: String, } /// lookup a user on Revolt /// currently only supports exact username searches #[post("/lookup", data = "<query>")] -pub fn lookup(user: UserRef, query: Json<Query>) -> Response { +pub fn lookup(user: UserRef, query: Json<LookupQuery>) -> Response { let relationships = user.fetch_relationships(); let col = database::get_collection("users"); -- GitLab