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 rand::seq::SliceRandom;
use revolt_config::config;
use revolt_models::v0;
use revolt_presence::filter_online;
use revolt_result::{create_error, Error, ErrorType, Result};
use ulid::Ulid;
@@ -264,7 +266,27 @@ impl User {
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(
db: &Database,
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 {
fn from(value: crate::RemovalIntention) -> Self {
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
P: Into<Option<&'a crate::User>>,
{
@@ -725,10 +737,7 @@ impl crate::User {
})
.unwrap_or_default();
// TODO: refactor all of this to be less code? (as in with the method above)
// 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;
let can_see_profile = relationship != RelationshipStatus::BlockedOther;
(relationship, can_see_profile)
}
} else {
@@ -768,7 +777,7 @@ impl crate::User {
privileged: self.privileged,
bot: self.bot.map(|bot| bot.into()),
relationship,
online: can_see_profile && revolt_presence::is_online(&self.id).await,
online: can_see_profile && is_online,
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 once_cell::sync::Lazy;
use regex::Regex;
#[cfg(feature = "validator")]
use validator::Validate;
#[cfg(feature = "rocket")]
use rocket::FromForm;
/// Regex for valid role colours
///
/// Allows the use of named colours, rgb(a), variables and all gradients.
@@ -77,4 +85,46 @@ auto_derived!(
Kick,
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 revolt_permissions::OverrideField;
use revolt_permissions::{Override, OverrideField};
use std::collections::HashMap;
#[cfg(feature = "validator")]
@@ -160,6 +160,12 @@ auto_derived!(
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
pub struct CreateServerLegacyResponse {
/// Server object

View File

@@ -28,13 +28,7 @@ pub async fn fetch_members(
if let Channel::Group { recipients, .. } = channel {
Ok(Json(
join_all(
db.fetch_users(&recipients)
.await?
.into_iter()
.map(|other_user| other_user.into_known(&user)),
)
.await,
User::fetch_many_ids_as_mutuals(db, &user, &recipients).await?,
))
} else {
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_models::v0::{self, InviteJoinResponse};
use revolt_result::{create_error, Result};
@@ -37,13 +36,7 @@ pub async fn join(
channel.add_user_to_group(db, &user, creator).await?;
if let Channel::Group { recipients, .. } = &channel {
Ok(Json(InviteJoinResponse::Group {
users: join_all(
db.fetch_users(&recipients)
.await?
.into_iter()
.map(|other_user| other_user.into_known(&user)),
)
.await,
users: User::fetch_many_ids_as_mutuals(db, &user, &recipients).await?,
channel: channel.into(),
}))
} else {

View File

@@ -1,69 +1,55 @@
use std::collections::HashSet;
use revolt_quark::{
models::{
server_member::{FieldsMember, PartialMember},
File, Member, User,
},
perms, Db, Error, Permission, Ref, Result, Timestamp,
use revolt_database::{
util::{permissions::DatabasePermissionQuery, reference::Reference},
Database, File, PartialMember, User,
};
use revolt_models::v0;
use rocket::serde::json::Json;
use serde::{Deserialize, Serialize};
use revolt_permissions::{calculate_server_permissions, ChannelPermission};
use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
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 a member by their id.
#[openapi(tag = "Server Members")]
#[patch("/<server>/members/<target>", data = "<data>")]
pub async fn req(
db: &Db,
pub async fn edit(
db: &State<Database>,
user: User,
server: Ref,
target: Ref,
data: Json<DataMemberEdit>,
) -> Result<Json<Member>> {
server: Reference,
target: Reference,
data: Json<v0::DataMemberEdit>,
) -> Result<Json<v0::Member>> {
let data = data.into_inner();
data.validate()
.map_err(|error| Error::FailedValidation { error })?;
data.validate().map_err(|error| {
create_error!(FailedValidation {
error: error.to_string()
})
})?;
// Fetch server, target member and current permissions
let mut server = server.as_server(db).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
let mut required = vec![];
if data.nickname.is_some()
|| data
.remove
.as_ref()
.map(|x| x.contains(&FieldsMember::Nickname))
.map(|x| x.contains(&v0::FieldsMember::Nickname))
.unwrap_or_default()
{
if user.id == member.id.user {
required.push(Permission::ChangeNickname);
permissions.throw_if_lacking_channel_permission(ChannelPermission::ChangeNickname)?;
} else {
required.push(Permission::ManageNicknames);
permissions.throw_if_lacking_channel_permission(ChannelPermission::ManageNicknames)?;
}
}
@@ -71,13 +57,13 @@ pub async fn req(
|| data
.remove
.as_ref()
.map(|x| x.contains(&FieldsMember::Avatar))
.map(|x| x.contains(&v0::FieldsMember::Avatar))
.unwrap_or_default()
{
if user.id == member.id.user {
required.push(Permission::ChangeAvatar);
permissions.throw_if_lacking_channel_permission(ChannelPermission::ChangeAvatar)?;
} else {
return Err(Error::InvalidOperation);
return Err(create_error!(InvalidOperation));
}
}
@@ -85,34 +71,30 @@ pub async fn req(
|| data
.remove
.as_ref()
.map(|x| x.contains(&FieldsMember::Roles))
.map(|x| x.contains(&v0::FieldsMember::Roles))
.unwrap_or_default()
{
required.push(Permission::AssignRoles);
permissions.throw_if_lacking_channel_permission(ChannelPermission::AssignRoles)?;
}
if data.timeout.is_some()
|| data
.remove
.as_ref()
.map(|x| x.contains(&FieldsMember::Timeout))
.map(|x| x.contains(&v0::FieldsMember::Timeout))
.unwrap_or_default()
{
required.push(Permission::TimeoutMembers);
}
for permission in required {
permissions.throw_permission(db, permission).await?;
permissions.throw_if_lacking_channel_permission(ChannelPermission::TimeoutMembers)?;
}
// 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
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
@@ -125,16 +107,16 @@ pub async fn req(
for role_id in added_roles {
if let Some(role) = server.roles.remove(*role_id) {
if role.rank <= our_ranking {
return Err(Error::NotElevated);
return Err(create_error!(NotElevated));
}
} else {
return Err(Error::InvalidRole);
return Err(create_error!(InvalidRole));
}
}
}
// Apply edits to the member object
let DataMemberEdit {
let v0::DataMemberEdit {
nickname,
avatar,
roles,
@@ -151,7 +133,7 @@ pub async fn req(
// 1. Remove fields from object
if let Some(fields) = &remove {
if fields.contains(&FieldsMember::Avatar) {
if fields.contains(&v0::FieldsMember::Avatar) {
if let Some(avatar) = &member.avatar {
db.mark_attachment_as_deleted(&avatar.id).await?;
}
@@ -164,8 +146,14 @@ pub async fn req(
}
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?;
Ok(Json(member))
Ok(Json(member.into()))
}

View File

@@ -1,9 +1,12 @@
use revolt_quark::{
models::{Member, User},
perms, Db, Error, Ref, Result,
use futures::future::join_all;
use revolt_database::{
util::{permissions::DatabasePermissionQuery, reference::Reference},
Database, Member, User,
};
use rocket::serde::json::Json;
use revolt_models::v0;
use revolt_permissions::PermissionQuery;
use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
use serde::{Deserialize, Serialize};
/// # Query Parameters
@@ -20,9 +23,9 @@ pub struct OptionsQueryMembers {
#[derive(Serialize, JsonSchema)]
pub struct MemberQueryResponse {
/// List of members
members: Vec<Member>,
members: Vec<v0::Member>,
/// List of users
users: Vec<User>,
users: Vec<v0::User>,
}
/// # Query members by name
@@ -31,17 +34,20 @@ pub struct MemberQueryResponse {
#[openapi(tag = "Server Members")]
#[get("/<target>/members_experimental_query?<options..>")]
pub async fn member_experimental_query(
db: &Db,
db: &State<Database>,
user: User,
target: Ref,
target: Reference,
options: OptionsQueryMembers,
) -> Result<Json<MemberQueryResponse>> {
if !options.experimental_api {
return Err(Error::InternalError);
return Err(create_error!(InternalError));
}
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?;
@@ -50,7 +56,7 @@ pub async fn member_experimental_query(
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
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())
});
// Take the first five and return them
let (members, users) = zipped_vec.into_iter().take(10).unzip();
Ok(Json(MemberQueryResponse { members, users }))
// Take the first ten and return them
let (members, users): (Vec<Member>, Vec<User>) = zipped_vec.into_iter().take(10).unzip();
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_quark::{models::User, perms, Db, Ref, Result};
use rocket::serde::json::Json;
use revolt_database::{
util::{permissions::DatabasePermissionQuery, reference::Reference},
Database, User,
};
use revolt_models::v0;
use revolt_permissions::PermissionQuery;
use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
/// # Fetch Member
///
/// Retrieve a member.
#[openapi(tag = "Server Members")]
#[get("/<target>/members/<member>?<roles>")]
pub async fn req(
db: &Db,
pub async fn fetch(
db: &State<Database>,
user: User,
target: Ref,
member: Ref,
target: Reference,
member: Reference,
roles: Option<bool>,
) -> Result<Json<MemberResponse>> {
) -> Result<Json<v0::MemberResponse>> {
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 {
Some(true) => member.as_member_with_roles(db, &server.id).await?.into(),
_ => member.as_member(db, &server.id).await?.into(),
};
Ok(Json(member_response))
let member = member.as_member(db, &server.id).await?;
if let Some(true) = roles {
Ok(Json(v0::MemberResponse::MemberWithRoles {
roles: server
.roles
.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::{
models::{Member, User},
perms, Db, Ref, Result,
use revolt_database::{
util::{permissions::DatabasePermissionQuery, reference::Reference},
Database, User,
};
use rocket::serde::json::Json;
use serde::{Deserialize, Serialize};
/// # 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>,
}
use revolt_models::v0;
use revolt_permissions::PermissionQuery;
use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
/// # Fetch Members
///
/// Fetch all server members.
#[openapi(tag = "Server Members")]
#[get("/<target>/members?<options..>")]
pub async fn req(
db: &Db,
pub async fn fetch_all(
db: &State<Database>,
user: User,
target: Ref,
options: OptionsFetchAllMembers,
) -> Result<Json<AllMemberResponse>> {
target: Reference,
options: v0::OptionsFetchAllMembers,
) -> Result<Json<v0::AllMemberResponse>> {
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 user_ids = vec![];
for member in &members {
user_ids.push(member.id.user.clone());
}
let user_ids: Vec<String> = members
.iter()
.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.
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.
if let Some(true) = options.exclude_offline {
let mut iter = users.iter();
members.retain(|_| iter.next().unwrap().online.unwrap_or(false));
users.retain(|user| user.online.unwrap_or(false));
members.retain(|_| iter.next().unwrap().online);
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::{
models::{server_member::RemovalIntention, User},
perms, Db, EmptyResponse, Error, Permission, Ref, Result,
use revolt_database::{
util::{permissions::DatabasePermissionQuery, reference::Reference},
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
///
/// Removes a member from the server.
#[openapi(tag = "Server Members")]
#[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?;
if member.id == user.id {
return Err(Error::CannotRemoveYourself);
return Err(create_error!(CannotRemoveYourself));
}
if member.id == server.owner {
return Err(Error::InvalidOperation);
return Err(create_error!(InvalidOperation));
}
let mut permissions = perms(&user).server(&server);
permissions
.throw_permission(db, Permission::KickMembers)
.await?;
let mut query = DatabasePermissionQuery::new(db, &user).server(&server);
calculate_server_permissions(&mut query)
.await
.throw_if_lacking_channel_permission(ChannelPermission::KickMembers)?;
let member = member.as_member(db, &server.id).await?;
if member.get_ranking(permissions.server.get().unwrap())
<= permissions.get_member_rank().unwrap_or(i64::MIN)
if member.get_ranking(query.server_ref().as_ref().unwrap())
<= query.get_member_rank().unwrap_or(i64::MIN)
{
return Err(Error::NotElevated);
return Err(create_error!(NotElevated));
}
server
.remove_member(db, member, RemovalIntention::Kick, false)
member
.remove(db, &server, RemovalIntention::Kick, false)
.await
.map(|_| EmptyResponse)
}

View File

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

View File

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

View File

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