refactor(quark): port message_query, message_search

#283
This commit is contained in:
Paul Makles
2024-04-07 23:13:45 +01:00
parent 301676fb54
commit 6bc74749d2
5 changed files with 136 additions and 132 deletions

View File

@@ -5,8 +5,8 @@ use iso8601_timestamp::Timestamp;
use revolt_config::config; use revolt_config::config;
use revolt_models::{ use revolt_models::{
v0::{ v0::{
self, DataMessageSend, Embed, MessageAuthor, MessageSort, MessageWebhook, PushNotification, self, BulkMessageResponse, DataMessageSend, Embed, MessageAuthor, MessageSort,
ReplyIntent, SendableEmbed, Text, RE_MENTION, MessageWebhook, PushNotification, ReplyIntent, SendableEmbed, Text, RE_MENTION,
}, },
validator::Validate, validator::Validate,
}; };
@@ -488,6 +488,50 @@ impl Message {
Ok(()) Ok(())
} }
/// Helper function to fetch many messages with users
pub async fn fetch_with_users(
db: &Database,
query: MessageQuery,
perspective: &User,
include_users: Option<bool>,
server_id: Option<String>,
) -> Result<BulkMessageResponse> {
let messages: Vec<v0::Message> = 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::<HashSet<String>>()
.into_iter()
.collect::<Vec<String>>();
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 /// Append content to message
pub async fn append( pub async fn append(
db: &Database, db: &Database,

View File

@@ -250,13 +250,13 @@ auto_derived!(
/// Maximum number of messages to fetch /// Maximum number of messages to fetch
/// ///
/// For fetching nearby messages, this is \`(limit + 1)\`. /// 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<i64>, pub limit: Option<i64>,
/// Message id before which messages should be fetched /// 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<String>, pub before: Option<String>,
/// Message id after which messages should be fetched /// 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<String>, pub after: Option<String>,
/// Message sort direction /// Message sort direction
pub sort: Option<MessageSort>, pub sort: Option<MessageSort>,
@@ -265,33 +265,34 @@ auto_derived!(
/// Specifying 'nearby' ignores 'before', 'after' and 'sort'. /// Specifying 'nearby' ignores 'before', 'after' and 'sort'.
/// It will also take half of limit rounded as the limits to each side. /// It will also take half of limit rounded as the limits to each side.
/// It also fetches the message ID specified. /// 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<String>, pub nearby: Option<String>,
/// Whether to include user (and member, if server channel) objects /// Whether to include user (and member, if server channel) objects
pub include_users: Option<bool>, pub include_users: Option<bool>,
} }
/// Options for searching for messages /// Options for searching for messages
#[cfg_attr(feature = "validator", derive(Validate))]
pub struct DataMessageSearch { pub struct DataMessageSearch {
/// Full-text search query /// Full-text search query
/// ///
/// See [MongoDB documentation](https://docs.mongodb.com/manual/text-search/#-text-operator) for more information. /// 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, pub query: String,
/// Maximum number of messages to fetch /// 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<i64>, pub limit: Option<i64>,
/// Message id before which messages should be fetched /// 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<String>, pub before: Option<String>,
/// Message id after which messages should be fetched /// 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<String>, pub after: Option<String>,
/// Message sort direction /// Message sort direction
/// ///
/// By default, it will be sorted by latest. /// By default, it will be sorted by latest.
#[serde(default = "MessageSort::default")] #[cfg_attr(feature = "serde", serde(default = "MessageSort::default"))]
pub sort: MessageSort, pub sort: MessageSort,
/// Whether to include user (and member, if server channel) objects /// Whether to include user (and member, if server channel) objects
pub include_users: Option<bool>, pub include_users: Option<bool>,

View File

@@ -1,69 +1,42 @@
use revolt_quark::{ use revolt_database::{
models::{ util::{permissions::DatabasePermissionQuery, reference::Reference},
message::{ Channel, Database, Message, MessageFilter, MessageQuery, MessageTimePeriod, User,
BulkMessageResponse, MessageFilter, MessageQuery, MessageSort, MessageTimePeriod,
},
User,
},
perms, Db, Error, Permission, Ref, Result,
}; };
use rocket::serde::json::Json; use revolt_models::v0::{self, MessageSort};
use serde::{Deserialize, Serialize}; use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
use validator::Validate; 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<i64>,
/// Message id before which messages should be fetched
#[validate(length(min = 26, max = 26))]
before: Option<String>,
/// Message id after which messages should be fetched
#[validate(length(min = 26, max = 26))]
after: Option<String>,
/// Message sort direction
sort: Option<MessageSort>,
/// 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<String>,
/// Whether to include user (and member, if server channel) objects
include_users: Option<bool>,
}
/// # Fetch Messages /// # Fetch Messages
/// ///
/// Fetch multiple messages. /// Fetch multiple messages.
#[openapi(tag = "Messaging")] #[openapi(tag = "Messaging")]
#[get("/<target>/messages?<options..>")] #[get("/<target>/messages?<options..>")]
pub async fn req( pub async fn query(
db: &Db, db: &State<Database>,
user: User, user: User,
target: Ref, target: Reference,
options: OptionsQueryMessages, options: v0::OptionsQueryMessages,
) -> Result<Json<BulkMessageResponse>> { ) -> Result<Json<v0::BulkMessageResponse>> {
options options.validate().map_err(|error| {
.validate() create_error!(FailedValidation {
.map_err(|error| Error::FailedValidation { error })?; error: error.to_string()
})
})?;
if let Some(MessageSort::Relevance) = options.sort { if let Some(MessageSort::Relevance) = options.sort {
return Err(Error::InvalidOperation); return Err(create_error!(InvalidOperation));
} }
let channel = target.as_channel(db).await?; 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, limit,
before, before,
after, after,
@@ -72,8 +45,9 @@ pub async fn req(
include_users, include_users,
} = options; } = options;
let messages = db Message::fetch_with_users(
.fetch_messages(MessageQuery { db,
MessageQuery {
filter: MessageFilter { filter: MessageFilter {
channel: Some(channel.id().to_string()), channel: Some(channel.id().to_string()),
..Default::default() ..Default::default()
@@ -88,10 +62,16 @@ pub async fn req(
} }
}, },
limit, limit,
}) },
.await?; &user,
include_users,
BulkMessageResponse::transform(db, Some(&channel), messages, &user, include_users) match channel {
.await Channel::TextChannel { server, .. } | Channel::VoiceChannel { server, .. } => {
.map(Json) Some(server)
}
_ => None,
},
)
.await
.map(Json)
} }

View File

@@ -1,71 +1,43 @@
use revolt_quark::{ use revolt_database::{
models::{ util::{permissions::DatabasePermissionQuery, reference::Reference},
message::{ Channel, Database, Message, MessageFilter, MessageQuery, MessageTimePeriod, User,
BulkMessageResponse, MessageFilter, MessageQuery, MessageSort, MessageTimePeriod,
},
User,
},
perms, Db, Error, Permission, Ref, Result,
}; };
use revolt_models::v0;
use rocket::serde::json::Json; use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
use serde::{Deserialize, Serialize}; use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
use validator::Validate; 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<i64>,
/// Message id before which messages should be fetched
#[validate(length(min = 26, max = 26))]
before: Option<String>,
/// Message id after which messages should be fetched
#[validate(length(min = 26, max = 26))]
after: Option<String>,
/// 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<bool>,
}
/// # Search for Messages /// # Search for Messages
/// ///
/// This route searches for messages within the given parameters. /// This route searches for messages within the given parameters.
#[openapi(tag = "Messaging")] #[openapi(tag = "Messaging")]
#[post("/<target>/search", data = "<options>")] #[post("/<target>/search", data = "<options>")]
pub async fn req( pub async fn search(
db: &Db, db: &State<Database>,
user: User, user: User,
target: Ref, target: Reference,
options: Json<OptionsMessageSearch>, options: Json<v0::DataMessageSearch>,
) -> Result<Json<BulkMessageResponse>> { ) -> Result<Json<v0::BulkMessageResponse>> {
if user.bot.is_some() { if user.bot.is_some() {
return Err(Error::IsBot); return Err(create_error!(IsBot));
} }
let options = options.into_inner(); let options = options.into_inner();
options options.validate().map_err(|error| {
.validate() create_error!(FailedValidation {
.map_err(|error| Error::FailedValidation { error })?; error: error.to_string()
})
})?;
let channel = target.as_channel(db).await?; 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, query,
limit, limit,
before, before,
@@ -74,8 +46,9 @@ pub async fn req(
include_users, include_users,
} = options; } = options;
let messages = db Message::fetch_with_users(
.fetch_messages(MessageQuery { db,
MessageQuery {
filter: MessageFilter { filter: MessageFilter {
channel: Some(channel.id().to_string()), channel: Some(channel.id().to_string()),
query: Some(query), query: Some(query),
@@ -87,10 +60,16 @@ pub async fn req(
sort: Some(sort), sort: Some(sort),
}, },
limit, limit,
}) },
.await?; &user,
include_users,
BulkMessageResponse::transform(db, Some(&channel), messages, &user, include_users) match channel {
.await Channel::TextChannel { server, .. } | Channel::VoiceChannel { server, .. } => {
.map(Json) Some(server)
}
_ => None,
},
)
.await
.map(Json)
} }

View File

@@ -35,8 +35,8 @@ pub fn routes() -> (Vec<Route>, OpenApi) {
channel_edit::edit, channel_edit::edit,
invite_create::create_invite, invite_create::create_invite,
message_send::message_send, message_send::message_send,
message_query::req, message_query::query,
message_search::req, message_search::search,
message_fetch::fetch, message_fetch::fetch,
message_edit::edit, message_edit::edit,
message_bulk_delete::bulk_delete_messages, message_bulk_delete::bulk_delete_messages,