forked from jmug/stoatchat
refactor(quark): port member_edit, member_experimental_query, member_fetch_all, member_remove, permissions_set_default, permissions_set
#283
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>>,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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()))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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())))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user