refactor(quark): port member_edit, member_experimental_query, member_fetch_all, member_remove, permissions_set_default, permissions_set

#283
This commit is contained in:
Paul Makles
2024-04-07 21:35:45 +01:00
parent 03340a2b5a
commit 9de963fb56
14 changed files with 308 additions and 216 deletions

View File

@@ -5,6 +5,8 @@ use crate::{events::client::EventV1, Database, File, RatelimitEvent};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use rand::seq::SliceRandom; use rand::seq::SliceRandom;
use revolt_config::config; use revolt_config::config;
use revolt_models::v0;
use revolt_presence::filter_online;
use revolt_result::{create_error, Error, ErrorType, Result}; use revolt_result::{create_error, Error, ErrorType, Result};
use ulid::Ulid; use ulid::Ulid;
@@ -264,7 +266,27 @@ impl User {
Ok(username) Ok(username)
} }
// Find a free discriminator for a given username /// Helper function to fetch many users as a mutually connected user
/// (while optimising the online ID query)
pub async fn fetch_many_ids_as_mutuals(
db: &Database,
perspective: &User,
ids: &[String],
) -> Result<Vec<v0::User>> {
let online_ids = filter_online(ids).await;
Ok(db
.fetch_users(ids)
.await?
.into_iter()
.map(|user| {
let is_online = online_ids.contains(&user.id);
user.into_known(perspective, is_online)
})
.collect())
}
/// Find a free discriminator for a given username
pub async fn find_discriminator( pub async fn find_discriminator(
db: &Database, db: &Database,
username: &str, username: &str,

View File

@@ -500,6 +500,17 @@ impl From<crate::FieldsMember> for FieldsMember {
} }
} }
impl From<FieldsMember> for crate::FieldsMember {
fn from(value: FieldsMember) -> crate::FieldsMember {
match value {
FieldsMember::Avatar => crate::FieldsMember::Avatar,
FieldsMember::Nickname => crate::FieldsMember::Nickname,
FieldsMember::Roles => crate::FieldsMember::Roles,
FieldsMember::Timeout => crate::FieldsMember::Timeout,
}
}
}
impl From<crate::RemovalIntention> for RemovalIntention { impl From<crate::RemovalIntention> for RemovalIntention {
fn from(value: crate::RemovalIntention) -> Self { fn from(value: crate::RemovalIntention) -> Self {
match value { match value {
@@ -702,7 +713,8 @@ impl crate::User {
} }
} }
pub async fn into_known<'a, P>(self, perspective: P) -> User /// Convert user object into user model assuming mutual connection
pub fn into_known<'a, P>(self, perspective: P, is_online: bool) -> User
where where
P: Into<Option<&'a crate::User>>, P: Into<Option<&'a crate::User>>,
{ {
@@ -725,10 +737,7 @@ impl crate::User {
}) })
.unwrap_or_default(); .unwrap_or_default();
// TODO: refactor all of this to be less code? (as in with the method above) let can_see_profile = relationship != RelationshipStatus::BlockedOther;
// TODO: also not sure if this is the best solution, but we don't want to
// TODO: fetch mutual information again here
let can_see_profile = relationship == RelationshipStatus::Friend;
(relationship, can_see_profile) (relationship, can_see_profile)
} }
} else { } else {
@@ -768,7 +777,7 @@ impl crate::User {
privileged: self.privileged, privileged: self.privileged,
bot: self.bot.map(|bot| bot.into()), bot: self.bot.map(|bot| bot.into()),
relationship, relationship,
online: can_see_profile && revolt_presence::is_online(&self.id).await, online: can_see_profile && is_online,
id: self.id, id: self.id,
} }
} }

View File

@@ -1,9 +1,17 @@
use super::File; use std::collections::HashMap;
use super::{File, Role, User};
use iso8601_timestamp::Timestamp; use iso8601_timestamp::Timestamp;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use regex::Regex; use regex::Regex;
#[cfg(feature = "validator")]
use validator::Validate;
#[cfg(feature = "rocket")]
use rocket::FromForm;
/// Regex for valid role colours /// Regex for valid role colours
/// ///
/// Allows the use of named colours, rgb(a), variables and all gradients. /// Allows the use of named colours, rgb(a), variables and all gradients.
@@ -77,4 +85,46 @@ auto_derived!(
Kick, Kick,
Ban, Ban,
} }
/// Member response
#[serde(untagged)]
pub enum MemberResponse {
Member(Member),
MemberWithRoles {
member: Member,
roles: HashMap<String, Role>,
},
}
/// Options for fetching all members
#[cfg_attr(feature = "rocket", derive(FromForm))]
pub struct OptionsFetchAllMembers {
/// Whether to exclude offline users
pub exclude_offline: Option<bool>,
}
/// Response with all members
pub struct AllMemberResponse {
/// List of members
pub members: Vec<Member>,
/// List of users
pub users: Vec<User>,
}
/// New member information
#[cfg_attr(feature = "validator", derive(Validate))]
pub struct DataMemberEdit {
/// Member nickname
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 32)))]
pub nickname: Option<String>,
/// Attachment Id to set for avatar
pub avatar: Option<String>,
/// Array of role ids
pub roles: Option<Vec<String>>,
/// Timestamp this member is timed out until
pub timeout: Option<Timestamp>,
/// Fields to remove from channel object
#[cfg_attr(feature = "validator", validate(length(min = 1)))]
pub remove: Option<Vec<FieldsMember>>,
}
); );

View File

@@ -1,6 +1,6 @@
use super::{Channel, File}; use super::{Channel, File};
use revolt_permissions::OverrideField; use revolt_permissions::{Override, OverrideField};
use std::collections::HashMap; use std::collections::HashMap;
#[cfg(feature = "validator")] #[cfg(feature = "validator")]
@@ -160,6 +160,12 @@ auto_derived!(
pub nsfw: Option<bool>, pub nsfw: Option<bool>,
} }
/// New role permissions
pub struct DataSetServerRolePermission {
/// Allow / deny values for the role in this server.
pub permissions: Override,
}
/// Information returned when creating server /// Information returned when creating server
pub struct CreateServerLegacyResponse { pub struct CreateServerLegacyResponse {
/// Server object /// Server object

View File

@@ -28,13 +28,7 @@ pub async fn fetch_members(
if let Channel::Group { recipients, .. } = channel { if let Channel::Group { recipients, .. } = channel {
Ok(Json( Ok(Json(
join_all( User::fetch_many_ids_as_mutuals(db, &user, &recipients).await?,
db.fetch_users(&recipients)
.await?
.into_iter()
.map(|other_user| other_user.into_known(&user)),
)
.await,
)) ))
} else { } else {
Err(create_error!(InvalidOperation)) Err(create_error!(InvalidOperation))

View File

@@ -1,4 +1,3 @@
use futures::future::join_all;
use revolt_database::{util::reference::Reference, Channel, Database, Invite, Member, User}; use revolt_database::{util::reference::Reference, Channel, Database, Invite, Member, User};
use revolt_models::v0::{self, InviteJoinResponse}; use revolt_models::v0::{self, InviteJoinResponse};
use revolt_result::{create_error, Result}; use revolt_result::{create_error, Result};
@@ -37,13 +36,7 @@ pub async fn join(
channel.add_user_to_group(db, &user, creator).await?; channel.add_user_to_group(db, &user, creator).await?;
if let Channel::Group { recipients, .. } = &channel { if let Channel::Group { recipients, .. } = &channel {
Ok(Json(InviteJoinResponse::Group { Ok(Json(InviteJoinResponse::Group {
users: join_all( users: User::fetch_many_ids_as_mutuals(db, &user, &recipients).await?,
db.fetch_users(&recipients)
.await?
.into_iter()
.map(|other_user| other_user.into_known(&user)),
)
.await,
channel: channel.into(), channel: channel.into(),
})) }))
} else { } else {

View File

@@ -1,69 +1,55 @@
use std::collections::HashSet; use std::collections::HashSet;
use revolt_quark::{ use revolt_database::{
models::{ util::{permissions::DatabasePermissionQuery, reference::Reference},
server_member::{FieldsMember, PartialMember}, Database, File, PartialMember, User,
File, Member, User,
},
perms, Db, Error, Permission, Ref, Result, Timestamp,
}; };
use revolt_models::v0;
use rocket::serde::json::Json; use revolt_permissions::{calculate_server_permissions, ChannelPermission};
use serde::{Deserialize, Serialize}; use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
use validator::Validate; use validator::Validate;
/// # Member Data
#[derive(Validate, Serialize, Deserialize, JsonSchema)]
pub struct DataMemberEdit {
/// Member nickname
#[validate(length(min = 1, max = 32))]
nickname: Option<String>,
/// Attachment Id to set for avatar
avatar: Option<String>,
/// Array of role ids
roles: Option<Vec<String>>,
/// Timestamp this member is timed out until
timeout: Option<Timestamp>,
/// Fields to remove from channel object
#[validate(length(min = 1))]
remove: Option<Vec<FieldsMember>>,
}
/// # Edit Member /// # Edit Member
/// ///
/// Edit a member by their id. /// Edit a member by their id.
#[openapi(tag = "Server Members")] #[openapi(tag = "Server Members")]
#[patch("/<server>/members/<target>", data = "<data>")] #[patch("/<server>/members/<target>", data = "<data>")]
pub async fn req( pub async fn edit(
db: &Db, db: &State<Database>,
user: User, user: User,
server: Ref, server: Reference,
target: Ref, target: Reference,
data: Json<DataMemberEdit>, data: Json<v0::DataMemberEdit>,
) -> Result<Json<Member>> { ) -> Result<Json<v0::Member>> {
let data = data.into_inner(); let data = data.into_inner();
data.validate() data.validate().map_err(|error| {
.map_err(|error| Error::FailedValidation { error })?; create_error!(FailedValidation {
error: error.to_string()
})
})?;
// Fetch server, target member and current permissions // Fetch server, target member and current permissions
let mut server = server.as_server(db).await?; let mut server = server.as_server(db).await?;
let mut member = target.as_member(db, &server.id).await?; let mut member = target.as_member(db, &server.id).await?;
let mut permissions = perms(&user).server(&server); let mut query = DatabasePermissionQuery::new(db, &user)
.server(&server)
.member(&member);
let permissions = calculate_server_permissions(&mut query).await;
// Check permissions in server // Check permissions in server
let mut required = vec![];
if data.nickname.is_some() if data.nickname.is_some()
|| data || data
.remove .remove
.as_ref() .as_ref()
.map(|x| x.contains(&FieldsMember::Nickname)) .map(|x| x.contains(&v0::FieldsMember::Nickname))
.unwrap_or_default() .unwrap_or_default()
{ {
if user.id == member.id.user { if user.id == member.id.user {
required.push(Permission::ChangeNickname); permissions.throw_if_lacking_channel_permission(ChannelPermission::ChangeNickname)?;
} else { } else {
required.push(Permission::ManageNicknames); permissions.throw_if_lacking_channel_permission(ChannelPermission::ManageNicknames)?;
} }
} }
@@ -71,13 +57,13 @@ pub async fn req(
|| data || data
.remove .remove
.as_ref() .as_ref()
.map(|x| x.contains(&FieldsMember::Avatar)) .map(|x| x.contains(&v0::FieldsMember::Avatar))
.unwrap_or_default() .unwrap_or_default()
{ {
if user.id == member.id.user { if user.id == member.id.user {
required.push(Permission::ChangeAvatar); permissions.throw_if_lacking_channel_permission(ChannelPermission::ChangeAvatar)?;
} else { } else {
return Err(Error::InvalidOperation); return Err(create_error!(InvalidOperation));
} }
} }
@@ -85,34 +71,30 @@ pub async fn req(
|| data || data
.remove .remove
.as_ref() .as_ref()
.map(|x| x.contains(&FieldsMember::Roles)) .map(|x| x.contains(&v0::FieldsMember::Roles))
.unwrap_or_default() .unwrap_or_default()
{ {
required.push(Permission::AssignRoles); permissions.throw_if_lacking_channel_permission(ChannelPermission::AssignRoles)?;
} }
if data.timeout.is_some() if data.timeout.is_some()
|| data || data
.remove .remove
.as_ref() .as_ref()
.map(|x| x.contains(&FieldsMember::Timeout)) .map(|x| x.contains(&v0::FieldsMember::Timeout))
.unwrap_or_default() .unwrap_or_default()
{ {
required.push(Permission::TimeoutMembers); permissions.throw_if_lacking_channel_permission(ChannelPermission::TimeoutMembers)?;
}
for permission in required {
permissions.throw_permission(db, permission).await?;
} }
// Resolve our ranking // Resolve our ranking
let our_ranking = permissions.get_member_rank().unwrap_or(i64::MIN); let our_ranking = query.get_member_rank().unwrap_or(i64::MIN);
// Check that we have permissions to act against this member // Check that we have permissions to act against this member
if member.id.user != user.id if member.id.user != user.id
&& member.get_ranking(permissions.server.get().unwrap()) <= our_ranking && member.get_ranking(query.server_ref().as_ref().unwrap()) <= our_ranking
{ {
return Err(Error::NotElevated); return Err(create_error!(NotElevated));
} }
// Check permissions against roles in diff // Check permissions against roles in diff
@@ -125,16 +107,16 @@ pub async fn req(
for role_id in added_roles { for role_id in added_roles {
if let Some(role) = server.roles.remove(*role_id) { if let Some(role) = server.roles.remove(*role_id) {
if role.rank <= our_ranking { if role.rank <= our_ranking {
return Err(Error::NotElevated); return Err(create_error!(NotElevated));
} }
} else { } else {
return Err(Error::InvalidRole); return Err(create_error!(InvalidRole));
} }
} }
} }
// Apply edits to the member object // Apply edits to the member object
let DataMemberEdit { let v0::DataMemberEdit {
nickname, nickname,
avatar, avatar,
roles, roles,
@@ -151,7 +133,7 @@ pub async fn req(
// 1. Remove fields from object // 1. Remove fields from object
if let Some(fields) = &remove { if let Some(fields) = &remove {
if fields.contains(&FieldsMember::Avatar) { if fields.contains(&v0::FieldsMember::Avatar) {
if let Some(avatar) = &member.avatar { if let Some(avatar) = &member.avatar {
db.mark_attachment_as_deleted(&avatar.id).await?; db.mark_attachment_as_deleted(&avatar.id).await?;
} }
@@ -164,8 +146,14 @@ pub async fn req(
} }
member member
.update(db, partial, remove.unwrap_or_default()) .update(
db,
partial,
remove
.map(|v| v.into_iter().map(Into::into).collect())
.unwrap_or_default(),
)
.await?; .await?;
Ok(Json(member)) Ok(Json(member.into()))
} }

View File

@@ -1,9 +1,12 @@
use revolt_quark::{ use futures::future::join_all;
models::{Member, User}, use revolt_database::{
perms, Db, Error, Ref, Result, util::{permissions::DatabasePermissionQuery, reference::Reference},
Database, Member, User,
}; };
use revolt_models::v0;
use rocket::serde::json::Json; use revolt_permissions::PermissionQuery;
use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// # Query Parameters /// # Query Parameters
@@ -20,9 +23,9 @@ pub struct OptionsQueryMembers {
#[derive(Serialize, JsonSchema)] #[derive(Serialize, JsonSchema)]
pub struct MemberQueryResponse { pub struct MemberQueryResponse {
/// List of members /// List of members
members: Vec<Member>, members: Vec<v0::Member>,
/// List of users /// List of users
users: Vec<User>, users: Vec<v0::User>,
} }
/// # Query members by name /// # Query members by name
@@ -31,17 +34,20 @@ pub struct MemberQueryResponse {
#[openapi(tag = "Server Members")] #[openapi(tag = "Server Members")]
#[get("/<target>/members_experimental_query?<options..>")] #[get("/<target>/members_experimental_query?<options..>")]
pub async fn member_experimental_query( pub async fn member_experimental_query(
db: &Db, db: &State<Database>,
user: User, user: User,
target: Ref, target: Reference,
options: OptionsQueryMembers, options: OptionsQueryMembers,
) -> Result<Json<MemberQueryResponse>> { ) -> Result<Json<MemberQueryResponse>> {
if !options.experimental_api { if !options.experimental_api {
return Err(Error::InternalError); return Err(create_error!(InternalError));
} }
let server = target.as_server(db).await?; let server = target.as_server(db).await?;
perms(&user).server(&server).calc(db).await?; let mut query = DatabasePermissionQuery::new(db, &user).server(&server);
if !query.are_we_a_member().await {
return Err(create_error!(NotFound));
}
let mut members = db.fetch_all_members(&server.id).await?; let mut members = db.fetch_all_members(&server.id).await?;
@@ -50,7 +56,7 @@ pub async fn member_experimental_query(
user_ids.push(member.id.user.clone()); user_ids.push(member.id.user.clone());
} }
let mut users = User::fetch_foreign_users(db, &user_ids).await?; let mut users = db.fetch_users(&user_ids).await?;
// Ensure the lists match up exactly // Ensure the lists match up exactly
members.sort_by(|a, b| a.id.user.cmp(&b.id.user)); members.sort_by(|a, b| a.id.user.cmp(&b.id.user));
@@ -76,7 +82,15 @@ pub async fn member_experimental_query(
left.len().cmp(&right.len()) left.len().cmp(&right.len())
}); });
// Take the first five and return them // Take the first ten and return them
let (members, users) = zipped_vec.into_iter().take(10).unzip(); let (members, users): (Vec<Member>, Vec<User>) = zipped_vec.into_iter().take(10).unzip();
Ok(Json(MemberQueryResponse { members, users })) Ok(Json(MemberQueryResponse {
members: members.into_iter().map(Into::into).collect(),
users: join_all(
users
.into_iter()
.map(|other_user| other_user.into(db, &user)),
)
.await,
}))
} }

View File

@@ -1,25 +1,42 @@
use revolt_quark::models::server_member::MemberResponse; use revolt_database::{
use revolt_quark::{models::User, perms, Db, Ref, Result}; util::{permissions::DatabasePermissionQuery, reference::Reference},
use rocket::serde::json::Json; Database, User,
};
use revolt_models::v0;
use revolt_permissions::PermissionQuery;
use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
/// # Fetch Member /// # Fetch Member
/// ///
/// Retrieve a member. /// Retrieve a member.
#[openapi(tag = "Server Members")] #[openapi(tag = "Server Members")]
#[get("/<target>/members/<member>?<roles>")] #[get("/<target>/members/<member>?<roles>")]
pub async fn req( pub async fn fetch(
db: &Db, db: &State<Database>,
user: User, user: User,
target: Ref, target: Reference,
member: Ref, member: Reference,
roles: Option<bool>, roles: Option<bool>,
) -> Result<Json<MemberResponse>> { ) -> Result<Json<v0::MemberResponse>> {
let server = target.as_server(db).await?; let server = target.as_server(db).await?;
perms(&user).server(&server).calc(db).await?; let mut query = DatabasePermissionQuery::new(db, &user).server(&server);
if !query.are_we_a_member().await {
return Err(create_error!(NotFound));
}
let member_response: MemberResponse = match roles { let member = member.as_member(db, &server.id).await?;
Some(true) => member.as_member_with_roles(db, &server.id).await?.into(), if let Some(true) = roles {
_ => member.as_member(db, &server.id).await?.into(), Ok(Json(v0::MemberResponse::MemberWithRoles {
}; roles: server
.roles
Ok(Json(member_response)) .into_iter()
.filter(|(k, _)| member.roles.contains(k))
.map(|(k, v)| (k, v.into()))
.collect(),
member: member.into(),
}))
} else {
Ok(Json(v0::MemberResponse::Member(member.into())))
}
} }

View File

@@ -1,51 +1,37 @@
use revolt_quark::{ use revolt_database::{
models::{Member, User}, util::{permissions::DatabasePermissionQuery, reference::Reference},
perms, Db, Ref, Result, Database, User,
}; };
use revolt_models::v0;
use rocket::serde::json::Json; use revolt_permissions::PermissionQuery;
use serde::{Deserialize, Serialize}; use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
/// # Query Parameters
#[derive(Deserialize, JsonSchema, FromForm)]
pub struct OptionsFetchAllMembers {
/// Whether to exclude offline users
exclude_offline: Option<bool>,
}
/// # Member List
///
/// Both lists are sorted by ID.
#[derive(Serialize, JsonSchema)]
pub struct AllMemberResponse {
/// List of members
members: Vec<Member>,
/// List of users
users: Vec<User>,
}
/// # Fetch Members /// # Fetch Members
/// ///
/// Fetch all server members. /// Fetch all server members.
#[openapi(tag = "Server Members")] #[openapi(tag = "Server Members")]
#[get("/<target>/members?<options..>")] #[get("/<target>/members?<options..>")]
pub async fn req( pub async fn fetch_all(
db: &Db, db: &State<Database>,
user: User, user: User,
target: Ref, target: Reference,
options: OptionsFetchAllMembers, options: v0::OptionsFetchAllMembers,
) -> Result<Json<AllMemberResponse>> { ) -> Result<Json<v0::AllMemberResponse>> {
let server = target.as_server(db).await?; let server = target.as_server(db).await?;
perms(&user).server(&server).calc(db).await?; let mut query = DatabasePermissionQuery::new(db, &user).server(&server);
if !query.are_we_a_member().await {
return Err(create_error!(NotFound));
}
let mut members = db.fetch_all_members(&server.id).await?; let mut members = db.fetch_all_members(&server.id).await?;
let mut user_ids = vec![]; let user_ids: Vec<String> = members
for member in &members { .iter()
user_ids.push(member.id.user.clone()); .map(|member| member.id.user.clone())
} .collect();
let mut users = User::fetch_foreign_users(db, &user_ids).await?; let mut users = User::fetch_many_ids_as_mutuals(db, &user, &user_ids).await?;
// Ensure the lists match up exactly. // Ensure the lists match up exactly.
members.sort_by(|a, b| a.id.user.cmp(&b.id.user)); members.sort_by(|a, b| a.id.user.cmp(&b.id.user));
@@ -54,9 +40,12 @@ pub async fn req(
// Optionally, remove all offline user entries. // Optionally, remove all offline user entries.
if let Some(true) = options.exclude_offline { if let Some(true) = options.exclude_offline {
let mut iter = users.iter(); let mut iter = users.iter();
members.retain(|_| iter.next().unwrap().online.unwrap_or(false)); members.retain(|_| iter.next().unwrap().online);
users.retain(|user| user.online.unwrap_or(false)); users.retain(|user| user.online);
} }
Ok(Json(AllMemberResponse { members, users })) Ok(Json(v0::AllMemberResponse {
members: members.into_iter().map(Into::into).collect(),
users,
}))
} }

View File

@@ -1,40 +1,47 @@
use revolt_quark::{ use revolt_database::{
models::{server_member::RemovalIntention, User}, util::{permissions::DatabasePermissionQuery, reference::Reference},
perms, Db, EmptyResponse, Error, Permission, Ref, Result, Database, RemovalIntention, User,
}; };
use revolt_permissions::{calculate_server_permissions, ChannelPermission};
use revolt_result::{create_error, Result};
use rocket::State;
use rocket_empty::EmptyResponse;
/// # Kick Member /// # Kick Member
/// ///
/// Removes a member from the server. /// Removes a member from the server.
#[openapi(tag = "Server Members")] #[openapi(tag = "Server Members")]
#[delete("/<target>/members/<member>")] #[delete("/<target>/members/<member>")]
pub async fn req(db: &Db, user: User, target: Ref, member: Ref) -> Result<EmptyResponse> { pub async fn kick(
db: &State<Database>,
user: User,
target: Reference,
member: Reference,
) -> Result<EmptyResponse> {
let server = target.as_server(db).await?; let server = target.as_server(db).await?;
if member.id == user.id { if member.id == user.id {
return Err(Error::CannotRemoveYourself); return Err(create_error!(CannotRemoveYourself));
} }
if member.id == server.owner { if member.id == server.owner {
return Err(Error::InvalidOperation); return Err(create_error!(InvalidOperation));
} }
let mut permissions = perms(&user).server(&server); let mut query = DatabasePermissionQuery::new(db, &user).server(&server);
calculate_server_permissions(&mut query)
permissions .await
.throw_permission(db, Permission::KickMembers) .throw_if_lacking_channel_permission(ChannelPermission::KickMembers)?;
.await?;
let member = member.as_member(db, &server.id).await?; let member = member.as_member(db, &server.id).await?;
if member.get_ranking(query.server_ref().as_ref().unwrap())
if member.get_ranking(permissions.server.get().unwrap()) <= query.get_member_rank().unwrap_or(i64::MIN)
<= permissions.get_member_rank().unwrap_or(i64::MIN)
{ {
return Err(Error::NotElevated); return Err(create_error!(NotElevated));
} }
server member
.remove_member(db, member, RemovalIntention::Kick, false) .remove(db, &server, RemovalIntention::Kick, false)
.await .await
.map(|_| EmptyResponse) .map(|_| EmptyResponse)
} }

View File

@@ -32,10 +32,10 @@ pub fn routes() -> (Vec<Route>, OpenApi) {
server_edit::req, server_edit::req,
server_ack::req, server_ack::req,
channel_create::create_server_channel, channel_create::create_server_channel,
member_fetch_all::req, member_fetch_all::fetch_all,
member_remove::req, member_remove::kick,
member_fetch::req, member_fetch::fetch,
member_edit::req, member_edit::edit,
member_experimental_query::member_experimental_query, member_experimental_query::member_experimental_query,
ban_create::ban, ban_create::ban,
ban_remove::unban, ban_remove::unban,
@@ -45,8 +45,8 @@ pub fn routes() -> (Vec<Route>, OpenApi) {
roles_edit::req, roles_edit::req,
roles_fetch::req, roles_fetch::req,
roles_delete::req, roles_delete::req,
permissions_set::req, permissions_set::set_role_permission,
permissions_set_default::req, permissions_set_default::set_default_permissions,
emoji_list::list_emoji emoji_list::list_emoji
] ]
} }

View File

@@ -1,58 +1,51 @@
use rocket::serde::json::Json; use revolt_database::{
use serde::Deserialize; util::{permissions::DatabasePermissionQuery, reference::Reference},
Database, User,
use revolt_quark::{
models::{Server, User},
perms, Db, Error, Override, Permission, Ref, Result,
}; };
use revolt_models::v0;
/// # Permission Value use revolt_permissions::{calculate_server_permissions, ChannelPermission, Override};
#[derive(Deserialize, JsonSchema)] use revolt_result::{create_error, Result};
pub struct DataSetServerRolePermission { use rocket::{serde::json::Json, State};
/// Allow / deny values for the role in this server.
permissions: Override,
}
/// # Set Role Permission /// # Set Role Permission
/// ///
/// Sets permissions for the specified role in the server. /// Sets permissions for the specified role in the server.
#[openapi(tag = "Server Permissions")] #[openapi(tag = "Server Permissions")]
#[put("/<target>/permissions/<role_id>", data = "<data>", rank = 2)] #[put("/<target>/permissions/<role_id>", data = "<data>", rank = 2)]
pub async fn req( pub async fn set_role_permission(
db: &Db, db: &State<Database>,
user: User, user: User,
target: Ref, target: Reference,
role_id: String, role_id: String,
data: Json<DataSetServerRolePermission>, data: Json<v0::DataSetServerRolePermission>,
) -> Result<Json<Server>> { ) -> Result<Json<v0::Server>> {
let data = data.into_inner(); let data = data.into_inner();
let mut server = target.as_server(db).await?; let mut server = target.as_server(db).await?;
if let Some((current_value, rank)) = server.roles.get(&role_id).map(|x| (x.permissions, x.rank)) if let Some((current_value, rank)) = server.roles.get(&role_id).map(|x| (x.permissions, x.rank))
{ {
let mut permissions = perms(&user).server(&server); let mut query = DatabasePermissionQuery::new(db, &user).server(&server);
let permissions = calculate_server_permissions(&mut query).await;
permissions permissions.throw_if_lacking_channel_permission(ChannelPermission::ManagePermissions)?;
.throw_permission(db, Permission::ManagePermissions)
.await?;
// Prevent us from editing roles above us // Prevent us from editing roles above us
if rank <= permissions.get_member_rank().unwrap_or(i64::MIN) { if rank <= query.get_member_rank().unwrap_or(i64::MIN) {
return Err(Error::NotElevated); return Err(create_error!(NotElevated));
} }
// Ensure we have access to grant these permissions forwards // Ensure we have access to grant these permissions forwards
let current_value: Override = current_value.into(); let current_value: Override = current_value.into();
permissions permissions
.throw_permission_override(db, current_value, data.permissions) .throw_permission_override(current_value, &data.permissions)
.await?; .await?;
server server
.set_role_permission(db, &role_id, data.permissions.into()) .set_role_permission(db, &role_id, data.permissions.into())
.await?; .await?;
Ok(Json(server)) Ok(Json(server.into()))
} else { } else {
Err(Error::NotFound) Err(create_error!(NotFound))
} }
} }

View File

@@ -1,32 +1,42 @@
use revolt_permissions::DataPermissionsValue; use revolt_database::{
use revolt_quark::{ util::{permissions::DatabasePermissionQuery, reference::Reference},
models::{server::PartialServer, Server, User}, Database, PartialServer, Server, User,
perms, Db, Permission, Ref, Result,
}; };
use rocket::serde::json::Json; use revolt_models::v0;
use revolt_permissions::{
calculate_server_permissions, ChannelPermission, DataPermissionsValue, Override,
};
use revolt_result::Result;
use rocket::{serde::json::Json, State};
/// # Set Default Permission /// # Set Default Permission
/// ///
/// Sets permissions for the default role in this server. /// Sets permissions for the default role in this server.
#[openapi(tag = "Server Permissions")] #[openapi(tag = "Server Permissions")]
#[put("/<target>/permissions/default", data = "<data>", rank = 1)] #[put("/<target>/permissions/default", data = "<data>", rank = 1)]
pub async fn req( pub async fn set_default_permissions(
db: &Db, db: &State<Database>,
user: User, user: User,
target: Ref, target: Reference,
data: Json<DataPermissionsValue>, data: Json<DataPermissionsValue>,
) -> Result<Json<Server>> { ) -> Result<Json<v0::Server>> {
let data = data.into_inner(); let data = data.into_inner();
let mut server = target.as_server(db).await?; let mut server = target.as_server(db).await?;
let mut permissions = perms(&user).server(&server); let mut query = DatabasePermissionQuery::new(db, &user).server(&server);
let permissions = calculate_server_permissions(&mut query).await;
permissions permissions.throw_if_lacking_channel_permission(ChannelPermission::ManagePermissions)?;
.throw_permission(db, Permission::ManagePermissions)
.await?;
// Ensure we have permissions to grant these permissions forwards // Ensure we have permissions to grant these permissions forwards
permissions permissions
.throw_permission_value(db, data.permissions) .throw_permission_override(
None,
&Override {
allow: data.permissions,
deny: 0,
},
)
.await?; .await?;
server server
@@ -40,5 +50,5 @@ pub async fn req(
) )
.await?; .await?;
Ok(Json(server)) Ok(Json(server.into()))
} }