forked from jmug/stoatchat
@@ -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,
|
||||||
|
|||||||
@@ -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>,
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user