forked from jmug/stoatchat
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:
@@ -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> {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<()>;
|
||||
|
||||
|
||||
@@ -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(|_| ())
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -2,6 +2,4 @@ pub mod bridge;
|
||||
pub mod idempotency;
|
||||
pub mod permissions;
|
||||
pub mod reference;
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod test_fixtures;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user