feat: Rewrite acks (#741)
* feat: rewrite ack system Signed-off-by: IAmTomahawkx <iamtomahawkx@gmail.com> * feat: rewrite acks to crond + rabbit task * fix: review changes --------- Signed-off-by: IAmTomahawkx <iamtomahawkx@gmail.com>
This commit is contained in:
@@ -2,7 +2,10 @@ use std::collections::HashSet;
|
||||
|
||||
use crate::events::rabbit::*;
|
||||
use crate::User;
|
||||
use amqprs::channel::{BasicPublishArguments, ExchangeDeclareArguments};
|
||||
use amqprs::channel::{
|
||||
BasicPublishArguments, ExchangeDeclareArguments, ExchangeType, QueueBindArguments,
|
||||
QueueDeclareArguments,
|
||||
};
|
||||
use amqprs::connection::OpenConnectionArguments;
|
||||
use amqprs::{channel::Channel, connection::Connection, error::Error as AMQPError};
|
||||
use amqprs::{BasicProperties, FieldTable};
|
||||
@@ -55,6 +58,43 @@ impl AMQP {
|
||||
AMQP::new(connection, channel)
|
||||
}
|
||||
|
||||
pub async fn configure_channels(&self) -> revolt_result::Result<()> {
|
||||
let config = revolt_config::config().await;
|
||||
|
||||
self.channel
|
||||
.exchange_declare(
|
||||
ExchangeDeclareArguments::new(
|
||||
&config.rabbit.default_exchange,
|
||||
&ExchangeType::Topic.to_string(),
|
||||
)
|
||||
.durable(true)
|
||||
.finish(),
|
||||
)
|
||||
.await
|
||||
.expect("Failed to declare exchange");
|
||||
|
||||
// Configure acks channel & routing
|
||||
self.channel
|
||||
.queue_declare(
|
||||
QueueDeclareArguments::new(&config.rabbit.queues.acks)
|
||||
.durable(true)
|
||||
.no_wait(true)
|
||||
.finish(),
|
||||
)
|
||||
.await
|
||||
.expect("Failed to bind queue");
|
||||
|
||||
self.channel
|
||||
.queue_bind(QueueBindArguments::new(
|
||||
&config.rabbit.queues.acks,
|
||||
&config.rabbit.default_exchange,
|
||||
&config.rabbit.queues.acks,
|
||||
))
|
||||
.await
|
||||
.expect("Failed to bind channel");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn friend_request_accepted(
|
||||
&self,
|
||||
accepted_request_user: &User,
|
||||
@@ -232,7 +272,9 @@ impl AMQP {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn ack_message(
|
||||
/// # Sends an ack to pushd to update badges on iPhones.
|
||||
/// Not to be confused with the process_ack function, which handles sending all acks to crond for processing.
|
||||
pub async fn ack_notification_message(
|
||||
&self,
|
||||
user_id: String,
|
||||
channel_id: String,
|
||||
@@ -316,4 +358,41 @@ impl AMQP {
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// # Send an ack to crond for processing
|
||||
pub async fn process_ack(
|
||||
&self,
|
||||
user_id: &str,
|
||||
channel_id: Option<&str>,
|
||||
server_id: Option<&str>,
|
||||
) -> Result<(), AMQPError> {
|
||||
let config = revolt_config::config().await;
|
||||
|
||||
let payload = AckEventPayload {
|
||||
user_id: user_id.to_string(),
|
||||
channel_id: channel_id.map(|value| value.to_string()),
|
||||
server_id: server_id.map(|value| value.to_string()),
|
||||
};
|
||||
let payload = to_string(&payload).unwrap();
|
||||
|
||||
info!(
|
||||
"Sending ack processor event on exchange {}, channel {}: {}",
|
||||
config.rabbit.default_exchange, config.rabbit.queues.acks, payload
|
||||
);
|
||||
|
||||
self.channel
|
||||
.basic_publish(
|
||||
BasicProperties::default()
|
||||
.with_content_type("application/json")
|
||||
.with_persistence(true)
|
||||
//.with_headers(headers)
|
||||
.finish(),
|
||||
payload.into(),
|
||||
BasicPublishArguments::new(
|
||||
&config.rabbit.default_exchange,
|
||||
&config.rabbit.queues.acks,
|
||||
),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,3 +78,11 @@ pub struct AckPayload {
|
||||
pub channel_id: String,
|
||||
pub message_id: String,
|
||||
}
|
||||
|
||||
/// This is not the same as the AckPayload above, as the state for this event is stored in redis to allow for state updates while the event is queued.
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct AckEventPayload {
|
||||
pub user_id: String,
|
||||
pub channel_id: Option<String>,
|
||||
pub server_id: Option<String>,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#![allow(deprecated)]
|
||||
use std::{borrow::Cow, collections::HashMap};
|
||||
|
||||
use redis_kiss::get_connection;
|
||||
use revolt_config::config;
|
||||
use revolt_models::v0::{self, MessageAuthor};
|
||||
use revolt_permissions::OverrideField;
|
||||
@@ -212,7 +213,7 @@ impl Channel {
|
||||
role_permissions: HashMap::new(),
|
||||
nsfw: data.nsfw.unwrap_or(false),
|
||||
voice: data.voice.map(|voice| voice.into()),
|
||||
slowmode: None
|
||||
slowmode: None,
|
||||
},
|
||||
v0::LegacyServerChannelType::Voice => Channel::TextChannel {
|
||||
id: id.clone(),
|
||||
@@ -225,7 +226,7 @@ impl Channel {
|
||||
role_permissions: HashMap::new(),
|
||||
nsfw: data.nsfw.unwrap_or(false),
|
||||
voice: Some(data.voice.unwrap_or_default().into()),
|
||||
slowmode: None
|
||||
slowmode: None,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -643,7 +644,7 @@ impl Channel {
|
||||
}
|
||||
|
||||
/// Acknowledge a message
|
||||
pub async fn ack(&self, user: &str, message: &str) -> Result<()> {
|
||||
pub async fn ack(&self, user: &str, message: &str, amqp: &AMQP) -> Result<()> {
|
||||
EventV1::ChannelAck {
|
||||
id: self.id().to_string(),
|
||||
user: user.to_string(),
|
||||
@@ -652,17 +653,7 @@ impl Channel {
|
||||
.private(user.to_string())
|
||||
.await;
|
||||
|
||||
#[cfg(feature = "tasks")]
|
||||
crate::tasks::ack::queue_ack(
|
||||
self.id().to_string(),
|
||||
user.to_string(),
|
||||
crate::tasks::ack::AckEvent::AckMessage {
|
||||
id: message.to_string(),
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
crate::util::acker::ack_channel(user, self.id(), message, amqp).await
|
||||
}
|
||||
|
||||
/// Remove user from a group
|
||||
|
||||
@@ -105,7 +105,11 @@ pub async fn handle_ack_event(
|
||||
|
||||
if mentions_acked > 0 {
|
||||
if let Err(err) = amqp
|
||||
.ack_message(user.to_string(), channel.to_string(), id.to_owned())
|
||||
.ack_notification_message(
|
||||
user.to_string(),
|
||||
channel.to_string(),
|
||||
id.to_owned(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
revolt_config::capture_error(&err);
|
||||
@@ -192,9 +196,7 @@ pub async fn handle_ack_event(
|
||||
.expect("Failed to fetch channel from db");
|
||||
|
||||
if let TextChannel { server, .. } = channel {
|
||||
if let Err(err) =
|
||||
amqp.mass_mention_message_sent(server, mass_mentions).await
|
||||
{
|
||||
if let Err(err) = amqp.mass_mention_message_sent(server, mass_mentions).await {
|
||||
revolt_config::capture_error(&err);
|
||||
}
|
||||
} else {
|
||||
|
||||
77
crates/core/database/src/util/acker.rs
Normal file
77
crates/core/database/src/util/acker.rs
Normal file
@@ -0,0 +1,77 @@
|
||||
use redis_kiss::{get_connection, AsyncCommands};
|
||||
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
|
||||
use revolt_result::{Result, ToRevoltError};
|
||||
|
||||
use crate::{events::client::EventV1, Channel, Database, Server, User, AMQP};
|
||||
|
||||
pub async fn ack_channel(user: &str, channel: &str, message: &str, amqp: &AMQP) -> Result<()> {
|
||||
let mut redis = get_connection()
|
||||
.await
|
||||
.map_err(|_| create_error!(InternalError))?;
|
||||
|
||||
let old: Option<String> = redis
|
||||
.getset(format!("acker:{user}+{channel}"), message)
|
||||
.await
|
||||
.to_internal_error()?;
|
||||
|
||||
if old.is_none() || old.unwrap() == message {
|
||||
amqp.process_ack(user, Some(channel), None)
|
||||
.await
|
||||
.to_internal_error()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn ack_server(user: &User, server: &Server, db: &Database, amqp: &AMQP) -> Result<()> {
|
||||
let mut redis = get_connection()
|
||||
.await
|
||||
.map_err(|_| create_error!(InternalError))?;
|
||||
|
||||
let channels = db.fetch_channels(&server.channels).await?;
|
||||
let query = crate::util::permissions::DatabasePermissionQuery::new(db, user).server(server);
|
||||
|
||||
for channel in channels {
|
||||
let channel_id = channel.id();
|
||||
let mut q = query.clone().channel(&channel);
|
||||
|
||||
if calculate_channel_permissions(&mut q)
|
||||
.await
|
||||
.has_channel_permission(ChannelPermission::ViewChannel)
|
||||
{
|
||||
let channel_last_msg = match &channel {
|
||||
Channel::TextChannel {
|
||||
last_message_id, ..
|
||||
} => last_message_id,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
.clone();
|
||||
|
||||
if let Some(channel_last_msg) = channel_last_msg {
|
||||
let old: Option<String> = redis
|
||||
.getset(
|
||||
format!("acker:{}+{}", user.id, channel_id),
|
||||
&channel_last_msg,
|
||||
)
|
||||
.await
|
||||
.to_internal_error()?;
|
||||
|
||||
if old.is_none() || old.unwrap() == channel_last_msg {
|
||||
amqp.process_ack(&user.id, Some(channel_id), Some(&server.id))
|
||||
.await
|
||||
.to_internal_error()?;
|
||||
|
||||
EventV1::ChannelAck {
|
||||
id: channel_id.to_string(),
|
||||
user: user.id.clone(),
|
||||
message_id: channel_last_msg,
|
||||
}
|
||||
.private(user.id.clone())
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod acker;
|
||||
pub mod bridge;
|
||||
pub mod bulk_permissions;
|
||||
mod funcs;
|
||||
|
||||
Reference in New Issue
Block a user