feat: track join time

This commit is contained in:
Zomatree
2025-09-20 05:26:58 +01:00
parent 938480ccec
commit 5b50532d15
19 changed files with 230 additions and 107 deletions

View File

@@ -12,7 +12,6 @@ use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
use revolt_presence::filter_online; use revolt_presence::filter_online;
use revolt_result::Result; use revolt_result::Result;
use super::state::{Cache, State}; use super::state::{Cache, State};
/// Cache Manager /// Cache Manager
@@ -217,7 +216,14 @@ impl State {
// fetch voice states for all the channels we can see // fetch voice states for all the channels we can see
let mut voice_states = Vec::new(); let mut voice_states = Vec::new();
for channel in &channels { for channel in channels.iter().filter(|c| {
matches!(
c,
Channel::DirectMessage { .. }
| Channel::Group { .. }
| Channel::TextChannel { voice: Some(_), .. }
)
}) {
if let Ok(Some(voice_state)) = get_channel_voice_state(channel).await { if let Ok(Some(voice_state)) = get_channel_voice_state(channel).await {
voice_states.push(voice_state) voice_states.push(voice_state)
} }

View File

@@ -153,6 +153,7 @@ auto_derived!(
Description, Description,
Icon, Icon,
DefaultPermissions, DefaultPermissions,
Voice,
} }
); );
@@ -424,9 +425,7 @@ impl Channel {
/// Clone this channel's server id /// Clone this channel's server id
pub fn server(&self) -> Option<&str> { pub fn server(&self) -> Option<&str> {
match self { match self {
Channel::TextChannel { server, .. } => { Channel::TextChannel { server, .. } => Some(server),
Some(server)
}
_ => None, _ => None,
} }
} }
@@ -535,6 +534,12 @@ impl Channel {
} }
_ => {} _ => {}
}, },
FieldsChannel::Voice => match self {
Self::TextChannel { voice, .. } => {
voice.take();
}
_ => {}
},
} }
} }
@@ -767,6 +772,7 @@ impl IntoDocumentPath for FieldsChannel {
FieldsChannel::Description => "description", FieldsChannel::Description => "description",
FieldsChannel::Icon => "icon", FieldsChannel::Icon => "icon",
FieldsChannel::DefaultPermissions => "default_permissions", FieldsChannel::DefaultPermissions => "default_permissions",
FieldsChannel::Voice => "voice",
}) })
} }
} }

View File

@@ -312,6 +312,7 @@ impl From<FieldsChannel> for crate::FieldsChannel {
FieldsChannel::Description => crate::FieldsChannel::Description, FieldsChannel::Description => crate::FieldsChannel::Description,
FieldsChannel::Icon => crate::FieldsChannel::Icon, FieldsChannel::Icon => crate::FieldsChannel::Icon,
FieldsChannel::DefaultPermissions => crate::FieldsChannel::DefaultPermissions, FieldsChannel::DefaultPermissions => crate::FieldsChannel::DefaultPermissions,
FieldsChannel::Voice => crate::FieldsChannel::Voice,
} }
} }
} }
@@ -322,6 +323,7 @@ impl From<crate::FieldsChannel> for FieldsChannel {
crate::FieldsChannel::Description => FieldsChannel::Description, crate::FieldsChannel::Description => FieldsChannel::Description,
crate::FieldsChannel::Icon => FieldsChannel::Icon, crate::FieldsChannel::Icon => FieldsChannel::Icon,
crate::FieldsChannel::DefaultPermissions => FieldsChannel::DefaultPermissions, crate::FieldsChannel::DefaultPermissions => FieldsChannel::DefaultPermissions,
crate::FieldsChannel::Voice => FieldsChannel::Voice,
} }
} }
} }

View File

@@ -4,6 +4,7 @@ use crate::{
util::{permissions::DatabasePermissionQuery, reference::Reference}, util::{permissions::DatabasePermissionQuery, reference::Reference},
Database, Server, Database, Server,
}; };
use iso8601_timestamp::{Duration, Timestamp};
use livekit_protocol::ParticipantPermission; use livekit_protocol::ParticipantPermission;
use redis_kiss::{get_connection as _get_connection, redis::Pipeline, AsyncCommands, Conn}; use redis_kiss::{get_connection as _get_connection, redis::Pipeline, AsyncCommands, Conn};
use revolt_config::FeaturesLimits; use revolt_config::FeaturesLimits;
@@ -148,10 +149,12 @@ pub async fn create_voice_state(
channel_id: &str, channel_id: &str,
server_id: Option<&str>, server_id: Option<&str>,
user_id: &str, user_id: &str,
joined_at: Timestamp,
) -> Result<UserVoiceState> { ) -> Result<UserVoiceState> {
let unique_key = format!("{}:{}", &user_id, server_id.unwrap_or(channel_id)); let unique_key = format!("{}:{}", &user_id, server_id.unwrap_or(channel_id));
let voice_state = UserVoiceState { let voice_state = UserVoiceState {
joined_at,
id: user_id.to_string(), id: user_id.to_string(),
is_receiving: true, is_receiving: true,
is_publishing: false, is_publishing: false,
@@ -163,6 +166,12 @@ pub async fn create_voice_state(
.sadd(format!("vc_members:{channel_id}"), user_id) .sadd(format!("vc_members:{channel_id}"), user_id)
.sadd(format!("vc:{user_id}"), channel_id) .sadd(format!("vc:{user_id}"), channel_id)
.set(&unique_key, channel_id) .set(&unique_key, channel_id)
.set(
format!("joined_at:{unique_key}"),
joined_at
.duration_since(Timestamp::UNIX_EPOCH)
.whole_milliseconds() as i64,
)
.set( .set(
format!("is_publishing:{unique_key}"), format!("is_publishing:{unique_key}"),
voice_state.is_publishing, voice_state.is_publishing,
@@ -194,6 +203,7 @@ pub async fn delete_voice_state(
.srem(format!("vc_members:{channel_id}"), user_id) .srem(format!("vc_members:{channel_id}"), user_id)
.srem(format!("vc:{user_id}"), channel_id) .srem(format!("vc:{user_id}"), channel_id)
.del(&[ .del(&[
format!("joined_at:{unique_key}"),
format!("is_publishing:{unique_key}"), format!("is_publishing:{unique_key}"),
format!("is_receiving:{unique_key}"), format!("is_receiving:{unique_key}"),
format!("screensharing:{unique_key}"), format!("screensharing:{unique_key}"),
@@ -205,6 +215,35 @@ pub async fn delete_voice_state(
.to_internal_error() .to_internal_error()
} }
pub async fn delete_channel_voice_state(
channel_id: &str,
server_id: Option<&str>,
user_ids: &[String],
) -> Result<()> {
let parent_id = server_id.unwrap_or(channel_id);
let mut pipeline = Pipeline::new();
pipeline.del(format!("vc_members:{channel_id}"));
for user_id in user_ids {
let unique_key = format!("{user_id}:{parent_id}");
pipeline.srem(format!("vc:{user_id}"), channel_id).del(&[
format!("joined_at:{unique_key}"),
format!("is_publishing:{unique_key}"),
format!("is_receiving:{unique_key}"),
format!("screensharing:{unique_key}"),
format!("camera:{unique_key}"),
unique_key.clone(),
]);
}
pipeline
.query_async(&mut get_connection().await?.into_inner())
.await
.to_internal_error()
}
pub async fn update_voice_state_tracks( pub async fn update_voice_state_tracks(
channel_id: &str, channel_id: &str,
server_id: Option<&str>, server_id: Option<&str>,
@@ -285,9 +324,10 @@ pub async fn get_voice_state(
) -> Result<Option<UserVoiceState>> { ) -> Result<Option<UserVoiceState>> {
let unique_key = format!("{}:{}", user_id, server_id.unwrap_or(channel_id)); let unique_key = format!("{}:{}", user_id, server_id.unwrap_or(channel_id));
let (is_publishing, is_receiving, screensharing, camera) = get_connection() let (joined_at, is_publishing, is_receiving, screensharing, camera) = get_connection()
.await? .await?
.mget(&[ .mget(&[
format!("joined_at:{unique_key}"),
format!("is_publishing:{unique_key}"), format!("is_publishing:{unique_key}"),
format!("is_receiving:{unique_key}"), format!("is_receiving:{unique_key}"),
format!("screensharing:{unique_key}"), format!("screensharing:{unique_key}"),
@@ -296,16 +336,29 @@ pub async fn get_voice_state(
.await .await
.to_internal_error()?; .to_internal_error()?;
match (is_publishing, is_receiving, screensharing, camera) { match (
(Some(is_publishing), Some(is_receiving), Some(screensharing), Some(camera)) => { joined_at,
Ok(Some(v0::UserVoiceState { is_publishing,
is_receiving,
screensharing,
camera,
) {
(
Some(joined_at),
Some(is_publishing),
Some(is_receiving),
Some(screensharing),
Some(camera),
) => Ok(Some(v0::UserVoiceState {
joined_at: Timestamp::UNIX_EPOCH
.checked_add(Duration::milliseconds(joined_at))
.unwrap(),
id: user_id.to_string(), id: user_id.to_string(),
is_receiving, is_receiving,
is_publishing, is_publishing,
screensharing, screensharing,
camera, camera,
})) })),
}
_ => Ok(None), _ => Ok(None),
} }
} }

View File

@@ -1,4 +1,7 @@
use crate::{models::{Channel, User}, Database}; use crate::{
models::{Channel, User},
Database,
};
use livekit_api::{ use livekit_api::{
access_token::{AccessToken, VideoGrants}, access_token::{AccessToken, VideoGrants},
services::room::{CreateRoomOptions, RoomClient as InnerRoomClient, UpdateParticipantOptions}, services::room::{CreateRoomOptions, RoomClient as InnerRoomClient, UpdateParticipantOptions},
@@ -76,7 +79,9 @@ impl VoiceClient {
AccessToken::with_api_key(&room.node.key, &room.node.secret) AccessToken::with_api_key(&room.node.key, &room.node.secret)
.with_name(&format!("{}#{}", user.username, user.discriminator)) .with_name(&format!("{}#{}", user.username, user.discriminator))
.with_identity(&user.id) .with_identity(&user.id)
.with_metadata(&serde_json::to_string(&user.clone().into(db, None).await).to_internal_error()?) .with_metadata(
&serde_json::to_string(&user.clone().into(db, None).await).to_internal_error()?,
)
.with_ttl(Duration::from_secs(10)) .with_ttl(Duration::from_secs(10))
.with_grants(VideoGrants { .with_grants(VideoGrants {
room_join: true, room_join: true,
@@ -139,4 +144,13 @@ impl VoiceClient {
.await .await
.to_internal_error() .to_internal_error()
} }
pub async fn delete_room(&self, node: &str, channel_id: &str) -> Result<()> {
let room = self.get_node(node)?;
room.client
.delete_room(channel_id)
.await
.to_internal_error()
}
} }

View File

@@ -157,6 +157,7 @@ auto_derived!(
Description, Description,
Icon, Icon,
DefaultPermissions, DefaultPermissions,
Voice,
} }
/// New webhook information /// New webhook information

View File

@@ -1,3 +1,4 @@
use iso8601_timestamp::Timestamp;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use regex::Regex; use regex::Regex;
@@ -283,6 +284,7 @@ auto_derived_partial!(
/// Voice State information for a user /// Voice State information for a user
pub struct UserVoiceState { pub struct UserVoiceState {
pub id: String, pub id: String,
pub joined_at: Timestamp,
pub is_receiving: bool, pub is_receiving: bool,
pub is_publishing: bool, pub is_publishing: bool,
pub screensharing: bool, pub screensharing: bool,

View File

@@ -3,7 +3,7 @@ use livekit_api::{access_token::TokenVerifier, webhooks::WebhookReceiver};
use livekit_protocol::TrackType; use livekit_protocol::TrackType;
use revolt_database::{ use revolt_database::{
events::client::EventV1, events::client::EventV1,
iso8601_timestamp::Timestamp, iso8601_timestamp::{Duration, Timestamp},
util::reference::Reference, util::reference::Reference,
voice::{ voice::{
create_voice_state, delete_voice_state, get_call_notification_recipients, create_voice_state, delete_voice_state, get_call_notification_recipients,
@@ -64,7 +64,12 @@ pub async fn ingress(
let channel = Reference::from_unchecked(channel_id).as_channel(db).await?; let channel = Reference::from_unchecked(channel_id).as_channel(db).await?;
let voice_state = create_voice_state(channel_id, channel.server(), user_id).await?; let joined_at = Timestamp::UNIX_EPOCH
.checked_add(Duration::seconds(event.created_at))
.unwrap();
let voice_state =
create_voice_state(channel_id, channel.server(), user_id, joined_at).await?;
// Only publish one event when a user is moved from one channel to another. // Only publish one event when a user is moved from one channel to another.
if let Some(moved_from) = get_user_moved_to_voice(channel_id, user_id).await? { if let Some(moved_from) = get_user_moved_to_voice(channel_id, user_id).await? {
@@ -89,15 +94,8 @@ pub async fn ingress(
if event.room.as_ref().unwrap().num_participants == 1 { if event.room.as_ref().unwrap().num_participants == 1 {
let user = Reference::from_unchecked(user_id).as_user(db).await?; let user = Reference::from_unchecked(user_id).as_user(db).await?;
let started_at = Timestamp::now_utc(); let message_id =
let message_id = Ulid::from_datetime( Ulid::from_datetime(DateTime::from_timestamp_secs(event.created_at).unwrap())
DateTime::from_timestamp_millis(
started_at
.duration_since(Timestamp::UNIX_EPOCH)
.whole_milliseconds() as i64,
)
.unwrap(),
)
.to_string(); .to_string();
let mut call_started_message = SystemMessage::CallStarted { let mut call_started_message = SystemMessage::CallStarted {
@@ -127,7 +125,7 @@ pub async fn ingress(
.await?; .await?;
let recipients = get_call_notification_recipients(&channel_id, &user_id).await?; let recipients = get_call_notification_recipients(&channel_id, &user_id).await?;
let now = started_at.format_short().to_string(); let now = joined_at.format_short().to_string();
if let Err(e) = amqp if let Err(e) = amqp
.dm_call_updated(&user.id, channel.id(), Some(&now), false, recipients) .dm_call_updated(&user.id, channel.id(), Some(&now), false, recipients)
@@ -165,7 +163,7 @@ pub async fn ingress(
if members.is_none_or(|m| m.is_empty()) { if members.is_none_or(|m| m.is_empty()) {
// The channel is empty so send out an "end" message for ringing // The channel is empty so send out an "end" message for ringing
if let Err(e) = amqp if let Err(e) = amqp
.dm_call_updated(&user_id, &channel_id, None, true, None) .dm_call_updated(user_id, channel_id, None, true, None)
.await .await
{ {
revolt_config::capture_internal_error!(&e); revolt_config::capture_internal_error!(&e);

View File

@@ -1,4 +1,4 @@
use revolt_database::{util::reference::Reference, Database, User}; use revolt_database::{util::reference::Reference, voice::{delete_voice_state, get_channel_node, get_user_voice_channels, VoiceClient}, Database, User};
use revolt_result::{create_error, Result}; use revolt_result::{create_error, Result};
use rocket::State; use rocket::State;
use rocket_empty::EmptyResponse; use rocket_empty::EmptyResponse;
@@ -10,6 +10,7 @@ use rocket_empty::EmptyResponse;
#[delete("/<target>")] #[delete("/<target>")]
pub async fn delete_bot( pub async fn delete_bot(
db: &State<Database>, db: &State<Database>,
voice_client: &State<VoiceClient>,
user: User, user: User,
target: Reference<'_>, target: Reference<'_>,
) -> Result<EmptyResponse> { ) -> Result<EmptyResponse> {
@@ -18,7 +19,18 @@ pub async fn delete_bot(
return Err(create_error!(NotFound)); return Err(create_error!(NotFound));
} }
bot.delete(db).await.map(|_| EmptyResponse) bot.delete(db).await?;
for channel_id in get_user_voice_channels(&bot.id).await? {
let node = get_channel_node(&channel_id).await?.unwrap();
let channel = Reference::from_unchecked(&channel_id).as_channel(db).await?;
voice_client.remove_user(&node, &bot.id, &channel_id).await?;
delete_voice_state(&channel_id, channel.server(), &bot.id).await?;
}
Ok(EmptyResponse)
} }
#[cfg(test)] #[cfg(test)]

View File

@@ -1,8 +1,7 @@
use revolt_database::{ use revolt_database::{
util::{permissions::DatabasePermissionQuery, reference::Reference}, util::{permissions::DatabasePermissionQuery, reference::Reference},
voice::{ voice::{
delete_voice_state, get_channel_node, get_voice_channel_members, is_in_voice_channel, delete_channel_voice_state, delete_voice_state, get_channel_node, get_voice_channel_members, is_in_voice_channel, VoiceClient
VoiceClient,
}, },
Channel, Database, PartialChannel, User, AMQP, Channel, Database, PartialChannel, User, AMQP,
}; };
@@ -63,6 +62,7 @@ pub async fn delete(
voice_client voice_client
.remove_user(&node, &user.id, channel.id()) .remove_user(&node, &user.id, channel.id())
.await?; .await?;
delete_voice_state(channel.id(), None, &user.id).await?; delete_voice_state(channel.id(), None, &user.id).await?;
}; };
} }
@@ -73,10 +73,9 @@ pub async fn delete(
if let Some(users) = get_voice_channel_members(channel.id()).await? { if let Some(users) = get_voice_channel_members(channel.id()).await? {
let node = get_channel_node(channel.id()).await?.unwrap(); let node = get_channel_node(channel.id()).await?.unwrap();
for user in users { voice_client.delete_room(&node, channel.id()).await?;
voice_client.remove_user(&node, &user, channel.id()).await?;
delete_voice_state(channel.id(), channel.server(), &user).await?; delete_channel_voice_state(channel.id(), channel.server(), &users).await?;
}
}; };
} }
}; };

View File

@@ -1,6 +1,5 @@
use revolt_database::{ use revolt_database::{
util::{permissions::DatabasePermissionQuery, reference::Reference}, util::{permissions::DatabasePermissionQuery, reference::Reference}, voice::{delete_channel_voice_state, get_channel_node, get_voice_channel_members, VoiceClient}, Channel, Database, File, PartialChannel, SystemMessage, User, AMQP
Channel, Database, File, PartialChannel, SystemMessage, User, AMQP,
}; };
use revolt_models::v0; use revolt_models::v0;
use revolt_permissions::{calculate_channel_permissions, ChannelPermission}; use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
@@ -15,6 +14,7 @@ use validator::Validate;
#[patch("/<target>", data = "<data>")] #[patch("/<target>", data = "<data>")]
pub async fn edit( pub async fn edit(
db: &State<Database>, db: &State<Database>,
voice_client: &State<VoiceClient>,
amqp: &State<AMQP>, amqp: &State<AMQP>,
user: User, user: User,
target: Reference<'_>, target: Reference<'_>,
@@ -214,6 +214,9 @@ pub async fn edit(
v0::FieldsChannel::Icon => { v0::FieldsChannel::Icon => {
icon.take(); icon.take();
} }
v0::FieldsChannel::Voice => {
voice.take();
}
_ => {} _ => {}
} }
} }
@@ -257,5 +260,15 @@ pub async fn edit(
) )
.await?; .await?;
if channel.voice().is_none() {
if let Some(users) = get_voice_channel_members(channel.id()).await? {
let node = get_channel_node(channel.id()).await?.unwrap();
voice_client.delete_room(&node, channel.id()).await?;
delete_channel_voice_state(channel.id(), channel.server(), &users).await?;
};
}
Ok(Json(channel.into())) Ok(Json(channel.into()))
} }

View File

@@ -87,6 +87,7 @@ pub async fn call(
voice_client voice_client
.remove_user(&node, &user.id, &channel_id) .remove_user(&node, &user.id, &channel_id)
.await?; .await?;
delete_voice_state(&channel_id, channel.server(), &user.id).await?; delete_voice_state(&channel_id, channel.server(), &user.id).await?;
} }
} else { } else {

View File

@@ -60,7 +60,9 @@ pub async fn ban(
// If the member is in a voice channel while banned kick them from the voice channel // If the member is in a voice channel while banned kick them from the voice channel
if let Some(channel_id) = get_user_voice_channel_in_server(&user.id, &server.id).await? { if let Some(channel_id) = get_user_voice_channel_in_server(&user.id, &server.id).await? {
let node = get_channel_node(&channel_id).await?.unwrap(); let node = get_channel_node(&channel_id).await?.unwrap();
voice_client.remove_user(&node, &user.id, &channel_id).await?; voice_client.remove_user(&node, &user.id, &channel_id).await?;
delete_voice_state(&channel_id, Some(&server.id), &user.id).await? delete_voice_state(&channel_id, Some(&server.id), &user.id).await?
} }
} }

View File

@@ -42,15 +42,17 @@ pub async fn kick(
return Err(create_error!(NotElevated)); return Err(create_error!(NotElevated));
} }
member
.remove(db, &server, RemovalIntention::Kick, false)
.await?;
if let Some(channel_id) = get_user_voice_channel_in_server(&user.id, &server.id).await? { if let Some(channel_id) = get_user_voice_channel_in_server(&user.id, &server.id).await? {
let node = get_channel_node(&channel_id).await?.unwrap(); let node = get_channel_node(&channel_id).await?.unwrap();
voice_client.remove_user(&node, &user.id, &channel_id).await?; voice_client.remove_user(&node, &user.id, &channel_id).await?;
delete_voice_state(&channel_id, Some(&server.id), &user.id).await?;
}
member delete_voice_state(&channel_id, Some(&server.id), &user.id).await?;
.remove(db, &server, RemovalIntention::Kick, false) };
.await
.map(|_| EmptyResponse) Ok(EmptyResponse)
} }

View File

@@ -1,5 +1,7 @@
use revolt_database::{ use revolt_database::{
util::{permissions::DatabasePermissionQuery, reference::Reference}, voice::{sync_voice_permissions, VoiceClient}, Database, User util::{permissions::DatabasePermissionQuery, reference::Reference},
voice::{sync_voice_permissions, VoiceClient},
Database, User,
}; };
use revolt_models::v0; use revolt_models::v0;
use revolt_permissions::{calculate_server_permissions, ChannelPermission, Override}; use revolt_permissions::{calculate_server_permissions, ChannelPermission, Override};
@@ -22,8 +24,13 @@ pub async fn set_role_permission(
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))
{ let (current_value, rank) = server
.roles
.get(&role_id)
.map(|x| (x.permissions, x.rank))
.ok_or_else(|| create_error!(NotFound))?;
let mut query = DatabasePermissionQuery::new(db, &user).server(&server); let mut query = DatabasePermissionQuery::new(db, &user).server(&server);
let permissions = calculate_server_permissions(&mut query).await; let permissions = calculate_server_permissions(&mut query).await;
@@ -40,18 +47,15 @@ pub async fn set_role_permission(
.throw_permission_override(current_value, &data.permissions) .throw_permission_override(current_value, &data.permissions)
.await?; .await?;
server
.set_role_permission(db, &role_id, data.permissions.into())
.await?;
for channel_id in &server.channels { for channel_id in &server.channels {
let channel = Reference::from_unchecked(channel_id).as_channel(db).await?; let channel = Reference::from_unchecked(channel_id).as_channel(db).await?;
sync_voice_permissions(db, voice_client, &channel, Some(&server), Some(&role_id)).await?; sync_voice_permissions(db, voice_client, &channel, Some(&server), Some(&role_id)).await?;
}; };
server
.set_role_permission(db, &role_id, data.permissions.into())
.await?;
Ok(Json(server.into())) Ok(Json(server.into()))
} else {
Err(create_error!(NotFound))
}
} }

View File

@@ -39,12 +39,6 @@ pub async fn set_default_server_permissions(
) )
.await?; .await?;
for channel_id in &server.channels {
let channel = Reference::from_unchecked(channel_id).as_channel(db).await?;
sync_voice_permissions(db, voice_client, &channel, Some(&server), None).await?;
};
server server
.update( .update(
db, db,
@@ -56,5 +50,11 @@ pub async fn set_default_server_permissions(
) )
.await?; .await?;
for channel_id in &server.channels {
let channel = Reference::from_unchecked(channel_id).as_channel(db).await?;
sync_voice_permissions(db, voice_client, &channel, Some(&server), None).await?;
};
Ok(Json(server.into())) Ok(Json(server.into()))
} }

View File

@@ -1,7 +1,7 @@
use revolt_database::{ use revolt_database::{
util::{permissions::DatabasePermissionQuery, reference::Reference}, util::{permissions::DatabasePermissionQuery, reference::Reference},
voice::{sync_voice_permissions, VoiceClient}, voice::{sync_voice_permissions, VoiceClient},
Database, User Database, User,
}; };
use revolt_permissions::{calculate_server_permissions, ChannelPermission}; use revolt_permissions::{calculate_server_permissions, ChannelPermission};
use revolt_result::{create_error, Result}; use revolt_result::{create_error, Result};
@@ -18,7 +18,7 @@ pub async fn delete(
user: User, user: User,
target: Reference<'_>, target: Reference<'_>,
role_id: String, role_id: String,
voice_client: &State<VoiceClient> voice_client: &State<VoiceClient>,
) -> Result<EmptyResponse> { ) -> Result<EmptyResponse> {
let mut server = target.as_server(db).await?; let mut server = target.as_server(db).await?;
let mut query = DatabasePermissionQuery::new(db, &user).server(&server); let mut query = DatabasePermissionQuery::new(db, &user).server(&server);
@@ -28,21 +28,22 @@ pub async fn delete(
let member_rank = query.get_member_rank().unwrap_or(i64::MIN); let member_rank = query.get_member_rank().unwrap_or(i64::MIN);
if let Some(role) = server.roles.remove(&role_id) { let role = server
.roles
.remove(&role_id)
.ok_or_else(|| create_error!(NotFound))?;
if role.rank <= member_rank { if role.rank <= member_rank {
return Err(create_error!(NotElevated)); return Err(create_error!(NotElevated));
} }
role.delete(db, &server.id, &role_id).await?;
for channel_id in &server.channels { for channel_id in &server.channels {
let channel = Reference::from_unchecked(channel_id).as_channel(db).await?; let channel = Reference::from_unchecked(channel_id).as_channel(db).await?;
sync_voice_permissions(db, voice_client, &channel, Some(&server), Some(&role_id)).await?; sync_voice_permissions(db, voice_client, &channel, Some(&server), Some(&role_id)).await?;
};
role.delete(db, &server.id, &role_id)
.await
.map(|_| EmptyResponse)
} else {
Err(create_error!(NotFound))
} }
Ok(EmptyResponse)
} }

View File

@@ -1,6 +1,5 @@
use revolt_database::{ use revolt_database::{
util::{permissions::DatabasePermissionQuery, reference::Reference}, util::{permissions::DatabasePermissionQuery, reference::Reference}, voice::{sync_voice_permissions, VoiceClient}, Database, User
Database, User,
}; };
use revolt_models::v0; use revolt_models::v0;
use revolt_permissions::{calculate_server_permissions, ChannelPermission}; use revolt_permissions::{calculate_server_permissions, ChannelPermission};
@@ -14,6 +13,7 @@ use rocket::{serde::json::Json, State};
#[patch("/<target>/roles/ranks", data = "<data>")] #[patch("/<target>/roles/ranks", data = "<data>")]
pub async fn edit_role_ranks( pub async fn edit_role_ranks(
db: &State<Database>, db: &State<Database>,
voice_client: &State<VoiceClient>,
user: User, user: User,
target: Reference<'_>, target: Reference<'_>,
data: Json<v0::DataEditRoleRanks>, data: Json<v0::DataEditRoleRanks>,
@@ -70,6 +70,12 @@ pub async fn edit_role_ranks(
server.set_role_ordering(db, new_order).await?; server.set_role_ordering(db, new_order).await?;
for channel_id in &server.channels {
let channel = Reference::from_unchecked(channel_id).as_channel(db).await?;
sync_voice_permissions(db, voice_client, &channel, Some(&server), None).await?;
};
Ok(Json(server.into())) Ok(Json(server.into()))
} }

View File

@@ -1,6 +1,6 @@
use revolt_database::{ use revolt_database::{
util::reference::Reference, util::reference::Reference,
voice::{delete_voice_state, get_channel_node, get_user_voice_channel_in_server, get_voice_channel_members, VoiceClient}, voice::{delete_channel_voice_state, delete_voice_state, get_channel_node, get_user_voice_channel_in_server, get_voice_channel_members, VoiceClient},
Database, RemovalIntention, User, Database, RemovalIntention, User,
}; };
use revolt_models::v0; use revolt_models::v0;
@@ -29,11 +29,10 @@ pub async fn delete(
if let Some(users) = get_voice_channel_members(channel_id).await? { if let Some(users) = get_voice_channel_members(channel_id).await? {
let node = get_channel_node(channel_id).await?.unwrap(); let node = get_channel_node(channel_id).await?.unwrap();
for user in users { voice_client.delete_room(&node, channel_id).await?;
voice_client.remove_user(&node, &user, channel_id).await?;
delete_voice_state(channel_id, Some(&server.id), &user).await?; delete_channel_voice_state(channel_id, Some(&server.id), &users).await?;
} };
}
} }
server.delete(db).await server.delete(db).await
@@ -41,7 +40,9 @@ pub async fn delete(
if let Some(channel_id) = get_user_voice_channel_in_server(&user.id, &server.id).await? { if let Some(channel_id) = get_user_voice_channel_in_server(&user.id, &server.id).await? {
if server.channels.iter().any(|c| c == &channel_id) { if server.channels.iter().any(|c| c == &channel_id) {
let node = get_channel_node(&channel_id).await?.unwrap(); let node = get_channel_node(&channel_id).await?.unwrap();
voice_client.remove_user(&node, &user.id, &channel_id).await?; voice_client.remove_user(&node, &user.id, &channel_id).await?;
delete_voice_state(&channel_id, Some(&server.id), &user.id).await?; delete_voice_state(&channel_id, Some(&server.id), &user.id).await?;
} }
}; };