From 6f8802ab5f98d219a229a6eb35625bf06a022c8e Mon Sep 17 00:00:00 2001 From: Paul <paulmakles@gmail.com> Date: Fri, 9 Jul 2021 14:34:01 +0100 Subject: [PATCH] Feature: W.I.P. search route --- set_version.sh | 2 +- src/database/migrations/init.rs | 17 ++++ src/database/migrations/scripts.rs | 24 ++++- src/routes/channels/message_search.rs | 121 ++++++++++++++++++++++++++ src/routes/channels/mod.rs | 2 + src/version.rs | 2 +- 6 files changed, 165 insertions(+), 3 deletions(-) create mode 100644 src/routes/channels/message_search.rs diff --git a/set_version.sh b/set_version.sh index e256b67..990e58a 100755 --- a/set_version.sh +++ b/set_version.sh @@ -1,3 +1,3 @@ #!/bin/bash -export version=0.5.1-alpha.6 +export version=0.5.1-alpha.7 echo "pub const VERSION: &str = \"${version}\";" > src/version.rs diff --git a/src/database/migrations/init.rs b/src/database/migrations/init.rs index 1bb7277..be71b39 100644 --- a/src/database/migrations/init.rs +++ b/src/database/migrations/init.rs @@ -122,6 +122,23 @@ pub async fn create_database() { .await .expect("Failed to create username index."); + db.run_command( + doc! { + "createIndexes": "messages", + "indexes": [ + { + "key": { + "content": "text" + }, + "name": "content" + } + ] + }, + None, + ) + .await + .expect("Failed to create message index."); + db.collection("migrations") .insert_one( doc! { diff --git a/src/database/migrations/scripts.rs b/src/database/migrations/scripts.rs index 770c109..342f52f 100644 --- a/src/database/migrations/scripts.rs +++ b/src/database/migrations/scripts.rs @@ -11,7 +11,7 @@ struct MigrationInfo { revision: i32, } -pub const LATEST_REVISION: i32 = 6; +pub const LATEST_REVISION: i32 = 7; pub async fn migrate_database() { let migrations = get_collection("migrations"); @@ -181,6 +181,28 @@ pub async fn run_migrations(revision: i32) -> i32 { .expect("Failed to migrate servers."); } + if revision <= 6 { + info!("Running migration [revision 6 / 2021-07-09]: Add message text index."); + + get_db() + .run_command( + doc! { + "createIndexes": "messages", + "indexes": [ + { + "key": { + "content": "text" + }, + "name": "content" + } + ] + }, + None, + ) + .await + .expect("Failed to create message index."); + } + // Reminder to update LATEST_REVISION when adding new migrations. LATEST_REVISION } diff --git a/src/routes/channels/message_search.rs b/src/routes/channels/message_search.rs new file mode 100644 index 0000000..214d937 --- /dev/null +++ b/src/routes/channels/message_search.rs @@ -0,0 +1,121 @@ +use std::collections::HashSet; + +use crate::database::*; +use crate::util::result::{Error, Result}; + +use futures::StreamExt; +use mongodb::{ + bson::{doc, from_document}, + options::FindOptions, +}; +use rocket_contrib::json::{Json, JsonValue}; +use serde::{Deserialize, Serialize}; +use validator::Validate; + +#[derive(Serialize, Deserialize, FromFormValue)] +pub enum Sort { + Relevance, + Latest, + Oldest, +} + +#[derive(Validate, Serialize, Deserialize, FromForm)] +pub struct Options { + #[validate(length(min = 1, max = 64))] + query: String, + + #[validate(range(min = 1, max = 100))] + limit: Option<i64>, + #[validate(length(min = 26, max = 26))] + before: Option<String>, + #[validate(length(min = 26, max = 26))] + after: Option<String>, + sort: Option<Sort>, + include_users: Option<bool>, +} + +#[post("/<target>/search", data = "<options>")] +pub async fn req(user: User, target: Ref, options: Json<Options>) -> Result<JsonValue> { + options + .validate() + .map_err(|error| Error::FailedValidation { error })?; + + let target = target.fetch_channel().await?; + target.has_messaging()?; + + let perm = permissions::PermissionCalculator::new(&user) + .with_channel(&target) + .for_channel() + .await?; + if !perm.get_view() { + Err(Error::MissingPermission)? + } + + let mut messages = vec![]; + let limit = options.limit.unwrap_or(50); + + let mut cursor = get_collection("messages") + .find( + doc! { + "channel": target.id(), + "$text": { + "$search": &options.query + } + }, + FindOptions::builder() + .projection(doc! { + "score": { + "$meta": "textScore" + } + }) + .limit(limit) + .sort(doc! { + "score": { + "$meta": "textScore" + } + }) + .build(), + ) + .await + .map_err(|_| Error::DatabaseError { + operation: "find", + with: "messages", + })?; + + while let Some(result) = cursor.next().await { + if let Ok(doc) = result { + messages.push( + from_document::<Message>(doc).map_err(|_| Error::DatabaseError { + operation: "from_document", + with: "message", + })?, + ); + } + } + + if options.include_users.unwrap_or_else(|| false) { + let mut ids = HashSet::new(); + for message in &messages { + ids.insert(message.author.clone()); + } + + ids.remove(&user.id); + let user_ids = ids.into_iter().collect(); + let users = user.fetch_multiple_users(user_ids).await?; + + if let Channel::TextChannel { server, .. } = target { + Ok(json!({ + "messages": messages, + "users": users, + "members": Server::fetch_members(&server).await? + })) + } else { + Ok(json!({ + "messages": messages, + "users": users, + })) + } + } else { + Ok(json!(messages)) + } +} diff --git a/src/routes/channels/mod.rs b/src/routes/channels/mod.rs index 29f75f9..cc3a4d1 100644 --- a/src/routes/channels/mod.rs +++ b/src/routes/channels/mod.rs @@ -14,6 +14,7 @@ mod message_delete; mod message_edit; mod message_fetch; mod message_query; +mod message_search; mod message_query_stale; mod message_send; mod permissions_set; @@ -29,6 +30,7 @@ pub fn routes() -> Vec<Route> { invite_create::req, message_send::req, message_query::req, + message_search::req, message_query_stale::req, message_fetch::req, message_edit::req, diff --git a/src/version.rs b/src/version.rs index 818c98d..99ae5c6 100644 --- a/src/version.rs +++ b/src/version.rs @@ -1 +1 @@ -pub const VERSION: &str = "0.5.1-alpha.6"; +pub const VERSION: &str = "0.5.1-alpha.7"; -- GitLab