refactor(quark): port channel_ack, channel_delete, channel_ edit, group_remove_member, invite_create, members_fetch, message_bulk_delete, message_clear_reactions, message_delete

#283
This commit is contained in:
Paul Makles
2024-04-07 15:41:43 +01:00
parent 50c36dcefd
commit 54a4eff623
27 changed files with 500 additions and 216 deletions

View File

@@ -1,12 +1,12 @@
use revolt_result::{create_error, Result};
use crate::Database;
use crate::{Channel, Database, User};
/* static ALPHABET: [char; 54] = [
static ALPHABET: [char; 54] = [
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J',
'K', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'j', 'k', 'm', 'n', 'p', 'q', 'r', 's', 't', 'v', 'w', 'x', 'y', 'z',
]; */
];
auto_derived!(
/// Invite
@@ -56,9 +56,13 @@ impl Invite {
}
/// Create a new invite from given information
/*pub async fn create_channel_invite(db: &Database, creator_id: String, target: &Channel) -> Result<Invite> {
pub async fn create_channel_invite(
db: &Database,
creator: &User,
channel: &Channel,
) -> Result<Invite> {
let code = nanoid::nanoid!(8, &ALPHABET);
let invite = match &target {
let invite = match &channel {
Channel::Group { id, .. } => Ok(Invite::Group {
code,
creator: creator.id.clone(),
@@ -72,12 +76,12 @@ impl Invite {
channel: id.clone(),
})
}
_ => Err(Error::InvalidOperation),
_ => Err(create_error!(InvalidOperation)),
}?;
db.insert_invite(&invite).await?;
Ok(invite)
}*/
}
/// Resolve an invite by its ID or by a public server ID
pub async fn find(db: &Database, code: &str) -> Result<Invite> {

View File

@@ -7,8 +7,8 @@ use revolt_result::Result;
use serde::{Deserialize, Serialize};
use crate::{
events::client::EventV1, Database, File, IntoDocumentPath, PartialServer, Server,
SystemMessage, User,
events::client::EventV1, tasks::ack::AckEvent, Database, File, IntoDocumentPath, PartialServer,
Server, SystemMessage, User,
};
auto_derived!(
@@ -582,6 +582,28 @@ impl Channel {
}
}
/// Acknowledge a message
pub async fn ack(&self, user: &str, message: &str) -> Result<()> {
EventV1::ChannelAck {
id: self.id().to_string(),
user: user.to_string(),
message_id: message.to_string(),
}
.private(user.to_string())
.await;
crate::tasks::ack::queue(
self.id().to_string(),
user.to_string(),
AckEvent::AckMessage {
id: message.to_string(),
},
)
.await;
Ok(())
}
/// Remove user from a group
pub async fn remove_user_from_group(
&self,
@@ -627,8 +649,7 @@ impl Channel {
.await
.ok();
} else {
db.delete_channel(self).await?;
return Ok(());
return self.delete(db).await;
}
}

View File

@@ -441,6 +441,22 @@ impl Message {
Ok(())
}
/// Update message data
pub async fn update(&mut self, db: &Database, partial: PartialMessage) -> Result<()> {
self.apply_options(partial.clone());
db.update_message(&self.id, &partial).await?;
EventV1::MessageUpdate {
id: self.id.clone(),
channel: self.channel.clone(),
data: partial.into(),
}
.p(self.channel.clone())
.await;
Ok(())
}
/// Append content to message
pub async fn append(
db: &Database,
@@ -518,6 +534,48 @@ impl Message {
Err(create_error!(PayloadTooLarge))
}
}
/// Delete a message
pub async fn delete(self, db: &Database) -> Result<()> {
let file_ids: Vec<String> = self
.attachments
.map(|files| files.iter().map(|file| file.id.to_string()).collect())
.unwrap_or_default();
if !file_ids.is_empty() {
db.mark_attachments_as_deleted(&file_ids).await?;
}
db.delete_message(&self.id).await?;
EventV1::MessageDelete {
id: self.id,
channel: self.channel.clone(),
}
.p(self.channel)
.await;
Ok(())
}
/// Bulk delete messages
pub async fn bulk_delete(db: &Database, channel: &str, ids: Vec<String>) -> Result<()> {
let valid_ids = db
.fetch_messages_by_id(&ids)
.await?
.into_iter()
.filter(|msg| msg.channel == channel)
.map(|msg| msg.id)
.collect::<Vec<String>>();
db.delete_messages(channel, &valid_ids).await?;
EventV1::BulkMessageDelete {
channel: channel.to_string(),
ids: valid_ids,
}
.p(channel.to_string())
.await;
Ok(())
}
}
impl SystemMessage {

View File

@@ -16,6 +16,9 @@ pub trait AbstractMessages: Sync + Send {
/// Fetch multiple messages by given query
async fn fetch_messages(&self, query: MessageQuery) -> Result<Vec<Message>>;
/// Fetch multiple messages by given IDs
async fn fetch_messages_by_id(&self, ids: &[String]) -> Result<Vec<Message>>;
/// Update a given message with new information
async fn update_message(&self, id: &str, message: &PartialMessage) -> Result<()>;

View File

@@ -159,6 +159,21 @@ impl AbstractMessages for MongoDb {
}
}
/// Fetch multiple messages by given IDs
async fn fetch_messages_by_id(&self, ids: &[String]) -> Result<Vec<Message>> {
self.find_with_options(
COL,
doc! {
"ids": {
"$in": ids
}
},
None,
)
.await
.map_err(|_| create_database_error!("find", COL))
}
/// Update a given message with new information
async fn update_message(&self, id: &str, message: &PartialMessage) -> Result<()> {
query!(self, update_one_by_id, COL, id, message, vec![], None).map(|_| ())

View File

@@ -1,3 +1,4 @@
use futures::future::try_join_all;
use indexmap::IndexSet;
use revolt_result::Result;
@@ -176,6 +177,11 @@ impl AbstractMessages for ReferenceDb {
}*/
}
/// Fetch multiple messages by given IDs
async fn fetch_messages_by_id(&self, ids: &[String]) -> Result<Vec<Message>> {
try_join_all(ids.iter().map(|id| self.fetch_message(id))).await
}
/// Update a given message with new information
async fn update_message(&self, id: &str, message: &PartialMessage) -> Result<()> {
let mut messages = self.messages.lock().await;

View File

@@ -727,6 +727,13 @@ impl crate::User {
id: self.id,
}
}
pub fn as_author_for_system(&self) -> MessageAuthor {
MessageAuthor::System {
username: &self.username,
avatar: self.avatar.as_ref().map(|file| file.id.as_ref()),
}
}
}
impl From<crate::PartialUser> for PartialUser {

View File

@@ -2,6 +2,4 @@ pub mod bridge;
pub mod idempotency;
pub mod permissions;
pub mod reference;
#[cfg(test)]
pub mod test_fixtures;

View File

@@ -42,6 +42,16 @@ impl Reference {
db.fetch_message(&self.id).await
}
/// Fetch message from Ref and validate channel
pub async fn as_message_in_channel(&self, db: &Database, channel: &str) -> Result<Message> {
let msg = db.fetch_message(&self.id).await?;
if msg.channel != channel {
return Err(create_error!(NotFound));
}
Ok(msg)
}
/// Fetch server from Ref
pub async fn as_server(&self, db: &Database) -> Result<Server> {
db.fetch_server(&self.id).await

View File

@@ -92,7 +92,7 @@ macro_rules! fixture {
let fixtures = $crate::util::test_fixtures::load_fixture(
&$database,
include_str!(concat!("../../../fixtures/", $name, ".json")),
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/fixtures/", $name, ".json")),
)
.await;

View File

@@ -12,9 +12,10 @@ description = "Revolt Backend: API Models"
serde = ["dep:serde", "revolt-permissions/serde", "indexmap/serde"]
schemas = ["dep:schemars", "revolt-permissions/schemas"]
validator = ["dep:validator"]
rocket = ["dep:rocket"]
partials = ["dep:revolt_optional_struct", "serde", "schemas"]
default = ["serde", "partials"]
default = ["serde", "partials", "rocket"]
[dependencies]
# Core
@@ -26,6 +27,9 @@ regex = "1"
indexmap = "1.9.3"
once_cell = "1.17.1"
# Rocket
rocket = { optional = true, version = "0.5.0-rc.2", default-features = false }
# Serialisation
revolt_optional_struct = { version = "0.2.0", optional = true }
serde = { version = "1", features = ["derive"], optional = true }

View File

@@ -3,6 +3,9 @@ use super::File;
use revolt_permissions::OverrideField;
use std::collections::{HashMap, HashSet};
#[cfg(feature = "rocket")]
use rocket::FromForm;
auto_derived!(
/// Channel
#[serde(tag = "channel_type")]
@@ -258,6 +261,13 @@ auto_derived!(
#[serde(skip_serializing_if = "Option::is_none")]
pub nsfw: Option<bool>,
}
/// Options when deleting a channel
#[cfg_attr(feature = "rocket", derive(FromForm))]
pub struct OptionsChannelDelete {
/// Whether to not send a leave message
pub leave_silently: Option<bool>,
}
);
impl Channel {

View File

@@ -169,16 +169,19 @@ auto_derived!(
#[derive(Default)]
#[cfg_attr(feature = "validator", derive(Validate))]
pub struct SendableEmbed {
#[validate(length(min = 1, max = 128))]
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 128)))]
pub icon_url: Option<String>,
#[validate(length(min = 1, max = 256))]
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 256)))]
pub url: Option<String>,
#[validate(length(min = 1, max = 100))]
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 100)))]
pub title: Option<String>,
#[validate(length(min = 1, max = 2000))]
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 2000)))]
pub description: Option<String>,
pub media: Option<String>,
#[validate(length(min = 1, max = 128), regex = "RE_COLOUR")]
#[cfg_attr(
feature = "validator",
validate(length(min = 1, max = 128), regex = "RE_COLOUR")
)]
pub colour: Option<String>,
}
@@ -196,11 +199,11 @@ auto_derived!(
/// Unique token to prevent duplicate message sending
///
/// **This is deprecated and replaced by `Idempotency-Key`!**
#[validate(length(min = 1, max = 64))]
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 64)))]
pub nonce: Option<String>,
/// Message content to send
#[validate(length(min = 0, max = 2000))]
#[cfg_attr(feature = "validator", validate(length(min = 0, max = 2000)))]
pub content: Option<String>,
/// Attachments to include in message
pub attachments: Option<Vec<String>>,
@@ -209,14 +212,25 @@ auto_derived!(
/// Embeds to include in message
///
/// Text embed content contributes to the content length cap
#[validate]
#[cfg_attr(feature = "validator", validate)]
pub embeds: Option<Vec<SendableEmbed>>,
/// Masquerade to apply to this message
#[validate]
#[cfg_attr(feature = "validator", validate)]
pub masquerade: Option<Masquerade>,
/// Information about how this message should be interacted with
pub interactions: Option<Interactions>,
}
/// Options for bulk deleting messages
#[cfg_attr(
feature = "validator",
cfg_attr(feature = "validator", derive(Validate))
)]
pub struct OptionsBulkDelete {
/// Message IDs
#[validate(length(min = 1, max = 100))]
pub ids: Vec<String>,
}
);
/// Message Author Abstraction