diff --git a/crates/core/database/src/models/messages/model.rs b/crates/core/database/src/models/messages/model.rs index e8ab5f65..819cc3a5 100644 --- a/crates/core/database/src/models/messages/model.rs +++ b/crates/core/database/src/models/messages/model.rs @@ -5,8 +5,8 @@ use iso8601_timestamp::Timestamp; use revolt_config::config; use revolt_models::{ v0::{ - self, DataMessageSend, Embed, MessageAuthor, MessageSort, MessageWebhook, PushNotification, - ReplyIntent, SendableEmbed, Text, RE_MENTION, + self, BulkMessageResponse, DataMessageSend, Embed, MessageAuthor, MessageSort, + MessageWebhook, PushNotification, ReplyIntent, SendableEmbed, Text, RE_MENTION, }, validator::Validate, }; @@ -488,6 +488,50 @@ impl Message { Ok(()) } + /// Helper function to fetch many messages with users + pub async fn fetch_with_users( + db: &Database, + query: MessageQuery, + perspective: &User, + include_users: Option, + server_id: Option, + ) -> Result { + let messages: Vec = db + .fetch_messages(query) + .await? + .into_iter() + .map(Into::into) + .collect(); + + if let Some(true) = include_users { + let user_ids = messages + .iter() + .map(|m| m.author.clone()) + .collect::>() + .into_iter() + .collect::>(); + let users = User::fetch_many_ids_as_mutuals(db, perspective, &user_ids).await?; + + Ok(BulkMessageResponse::MessagesAndUsers { + messages, + users, + members: if let Some(server_id) = server_id { + Some( + db.fetch_members(&server_id, &user_ids) + .await? + .into_iter() + .map(Into::into) + .collect(), + ) + } else { + None + }, + }) + } else { + Ok(BulkMessageResponse::JustMessages(messages)) + } + } + /// Append content to message pub async fn append( db: &Database, diff --git a/crates/core/models/src/v0/messages.rs b/crates/core/models/src/v0/messages.rs index 73ca4bd6..885abaed 100644 --- a/crates/core/models/src/v0/messages.rs +++ b/crates/core/models/src/v0/messages.rs @@ -250,13 +250,13 @@ auto_derived!( /// Maximum number of messages to fetch /// /// For fetching nearby messages, this is \`(limit + 1)\`. - #[validate(range(min = 1, max = 100))] + #[cfg_attr(feature = "validator", validate(range(min = 1, max = 100)))] pub limit: Option, /// Message id before which messages should be fetched - #[validate(length(min = 26, max = 26))] + #[cfg_attr(feature = "validator", validate(length(min = 26, max = 26)))] pub before: Option, /// Message id after which messages should be fetched - #[validate(length(min = 26, max = 26))] + #[cfg_attr(feature = "validator", validate(length(min = 26, max = 26)))] pub after: Option, /// Message sort direction pub sort: Option, @@ -265,33 +265,34 @@ auto_derived!( /// Specifying 'nearby' ignores 'before', 'after' and 'sort'. /// It will also take half of limit rounded as the limits to each side. /// It also fetches the message ID specified. - #[validate(length(min = 26, max = 26))] + #[cfg_attr(feature = "validator", validate(length(min = 26, max = 26)))] pub nearby: Option, /// Whether to include user (and member, if server channel) objects pub include_users: Option, } /// Options for searching for messages + #[cfg_attr(feature = "validator", derive(Validate))] pub struct DataMessageSearch { /// Full-text search query /// /// See [MongoDB documentation](https://docs.mongodb.com/manual/text-search/#-text-operator) for more information. - #[validate(length(min = 1, max = 64))] + #[cfg_attr(feature = "validator", validate(length(min = 1, max = 64)))] pub query: String, /// Maximum number of messages to fetch - #[validate(range(min = 1, max = 100))] + #[cfg_attr(feature = "validator", validate(range(min = 1, max = 100)))] pub limit: Option, /// Message id before which messages should be fetched - #[validate(length(min = 26, max = 26))] + #[cfg_attr(feature = "validator", validate(length(min = 26, max = 26)))] pub before: Option, /// Message id after which messages should be fetched - #[validate(length(min = 26, max = 26))] + #[cfg_attr(feature = "validator", validate(length(min = 26, max = 26)))] pub after: Option, /// Message sort direction /// /// By default, it will be sorted by latest. - #[serde(default = "MessageSort::default")] + #[cfg_attr(feature = "serde", serde(default = "MessageSort::default"))] pub sort: MessageSort, /// Whether to include user (and member, if server channel) objects pub include_users: Option, diff --git a/crates/delta/src/routes/channels/message_query.rs b/crates/delta/src/routes/channels/message_query.rs index f1237281..5193bd02 100644 --- a/crates/delta/src/routes/channels/message_query.rs +++ b/crates/delta/src/routes/channels/message_query.rs @@ -1,69 +1,42 @@ -use revolt_quark::{ - models::{ - message::{ - BulkMessageResponse, MessageFilter, MessageQuery, MessageSort, MessageTimePeriod, - }, - User, - }, - perms, Db, Error, Permission, Ref, Result, +use revolt_database::{ + util::{permissions::DatabasePermissionQuery, reference::Reference}, + Channel, Database, Message, MessageFilter, MessageQuery, MessageTimePeriod, User, }; -use rocket::serde::json::Json; -use serde::{Deserialize, Serialize}; +use revolt_models::v0::{self, MessageSort}; +use revolt_permissions::{calculate_channel_permissions, ChannelPermission}; +use revolt_result::{create_error, Result}; +use rocket::{serde::json::Json, State}; use validator::Validate; -/// # Query Parameters -#[derive(Validate, Serialize, Deserialize, JsonSchema, FromForm)] -pub struct OptionsQueryMessages { - /// Maximum number of messages to fetch - /// - /// For fetching nearby messages, this is \`(limit + 1)\`. - #[validate(range(min = 1, max = 100))] - limit: Option, - /// Message id before which messages should be fetched - #[validate(length(min = 26, max = 26))] - before: Option, - /// Message id after which messages should be fetched - #[validate(length(min = 26, max = 26))] - after: Option, - /// Message sort direction - sort: Option, - /// Message id to search around - /// - /// Specifying 'nearby' ignores 'before', 'after' and 'sort'. - /// It will also take half of limit rounded as the limits to each side. - /// It also fetches the message ID specified. - #[validate(length(min = 26, max = 26))] - nearby: Option, - /// Whether to include user (and member, if server channel) objects - include_users: Option, -} - /// # Fetch Messages /// /// Fetch multiple messages. #[openapi(tag = "Messaging")] #[get("//messages?")] -pub async fn req( - db: &Db, +pub async fn query( + db: &State, user: User, - target: Ref, - options: OptionsQueryMessages, -) -> Result> { - options - .validate() - .map_err(|error| Error::FailedValidation { error })?; + target: Reference, + options: v0::OptionsQueryMessages, +) -> Result> { + options.validate().map_err(|error| { + create_error!(FailedValidation { + error: error.to_string() + }) + })?; if let Some(MessageSort::Relevance) = options.sort { - return Err(Error::InvalidOperation); + return Err(create_error!(InvalidOperation)); } let channel = target.as_channel(db).await?; - perms(&user) - .channel(&channel) - .throw_permission_and_view_channel(db, Permission::ReadMessageHistory) - .await?; - let OptionsQueryMessages { + let mut query = DatabasePermissionQuery::new(db, &user).channel(&channel); + calculate_channel_permissions(&mut query) + .await + .throw_if_lacking_channel_permission(ChannelPermission::ReadMessageHistory)?; + + let v0::OptionsQueryMessages { limit, before, after, @@ -72,8 +45,9 @@ pub async fn req( include_users, } = options; - let messages = db - .fetch_messages(MessageQuery { + Message::fetch_with_users( + db, + MessageQuery { filter: MessageFilter { channel: Some(channel.id().to_string()), ..Default::default() @@ -88,10 +62,16 @@ pub async fn req( } }, limit, - }) - .await?; - - BulkMessageResponse::transform(db, Some(&channel), messages, &user, include_users) - .await - .map(Json) + }, + &user, + include_users, + match channel { + Channel::TextChannel { server, .. } | Channel::VoiceChannel { server, .. } => { + Some(server) + } + _ => None, + }, + ) + .await + .map(Json) } diff --git a/crates/delta/src/routes/channels/message_search.rs b/crates/delta/src/routes/channels/message_search.rs index 59518983..74d78540 100644 --- a/crates/delta/src/routes/channels/message_search.rs +++ b/crates/delta/src/routes/channels/message_search.rs @@ -1,71 +1,43 @@ -use revolt_quark::{ - models::{ - message::{ - BulkMessageResponse, MessageFilter, MessageQuery, MessageSort, MessageTimePeriod, - }, - User, - }, - perms, Db, Error, Permission, Ref, Result, +use revolt_database::{ + util::{permissions::DatabasePermissionQuery, reference::Reference}, + Channel, Database, Message, MessageFilter, MessageQuery, MessageTimePeriod, User, }; - -use rocket::serde::json::Json; -use serde::{Deserialize, Serialize}; +use revolt_models::v0; +use revolt_permissions::{calculate_channel_permissions, ChannelPermission}; +use revolt_result::{create_error, Result}; +use rocket::{serde::json::Json, State}; use validator::Validate; -/// # Search Parameters -#[derive(Validate, Serialize, Deserialize, JsonSchema)] -pub struct OptionsMessageSearch { - /// Full-text search query - /// - /// See [MongoDB documentation](https://docs.mongodb.com/manual/text-search/#-text-operator) for more information. - #[validate(length(min = 1, max = 64))] - query: String, - - /// Maximum number of messages to fetch - #[validate(range(min = 1, max = 100))] - limit: Option, - /// Message id before which messages should be fetched - #[validate(length(min = 26, max = 26))] - before: Option, - /// Message id after which messages should be fetched - #[validate(length(min = 26, max = 26))] - after: Option, - /// Message sort direction - /// - /// By default, it will be sorted by latest. - #[serde(default = "MessageSort::default")] - sort: MessageSort, - /// Whether to include user (and member, if server channel) objects - include_users: Option, -} - /// # Search for Messages /// /// This route searches for messages within the given parameters. #[openapi(tag = "Messaging")] #[post("//search", data = "")] -pub async fn req( - db: &Db, +pub async fn search( + db: &State, user: User, - target: Ref, - options: Json, -) -> Result> { + target: Reference, + options: Json, +) -> Result> { if user.bot.is_some() { - return Err(Error::IsBot); + return Err(create_error!(IsBot)); } let options = options.into_inner(); - options - .validate() - .map_err(|error| Error::FailedValidation { error })?; + options.validate().map_err(|error| { + create_error!(FailedValidation { + error: error.to_string() + }) + })?; let channel = target.as_channel(db).await?; - perms(&user) - .channel(&channel) - .throw_permission_and_view_channel(db, Permission::ReadMessageHistory) - .await?; - let OptionsMessageSearch { + let mut query = DatabasePermissionQuery::new(db, &user).channel(&channel); + calculate_channel_permissions(&mut query) + .await + .throw_if_lacking_channel_permission(ChannelPermission::ReadMessageHistory)?; + + let v0::DataMessageSearch { query, limit, before, @@ -74,8 +46,9 @@ pub async fn req( include_users, } = options; - let messages = db - .fetch_messages(MessageQuery { + Message::fetch_with_users( + db, + MessageQuery { filter: MessageFilter { channel: Some(channel.id().to_string()), query: Some(query), @@ -87,10 +60,16 @@ pub async fn req( sort: Some(sort), }, limit, - }) - .await?; - - BulkMessageResponse::transform(db, Some(&channel), messages, &user, include_users) - .await - .map(Json) + }, + &user, + include_users, + match channel { + Channel::TextChannel { server, .. } | Channel::VoiceChannel { server, .. } => { + Some(server) + } + _ => None, + }, + ) + .await + .map(Json) } diff --git a/crates/delta/src/routes/channels/mod.rs b/crates/delta/src/routes/channels/mod.rs index a5134815..da6a400f 100644 --- a/crates/delta/src/routes/channels/mod.rs +++ b/crates/delta/src/routes/channels/mod.rs @@ -35,8 +35,8 @@ pub fn routes() -> (Vec, OpenApi) { channel_edit::edit, invite_create::create_invite, message_send::message_send, - message_query::req, - message_search::req, + message_query::query, + message_search::search, message_fetch::fetch, message_edit::edit, message_bulk_delete::bulk_delete_messages,