mirror of
https://github.com/rustmailer/bichon.git
synced 2026-08-03 07:48:34 +02:00
feat(search-ui): optimize search UI
This commit is contained in:
@@ -16,6 +16,8 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::modules::account::migration::AccountModel;
|
||||
use crate::modules::cache::imap::mailbox::MailBox;
|
||||
use crate::modules::error::code::ErrorCode;
|
||||
@@ -155,9 +157,9 @@ impl Envelope {
|
||||
let id = create_hash(account_id, &message_id);
|
||||
let full_text = extract_string_field(doc, fields.f_text)?;
|
||||
|
||||
// Take up to the first 120 characters as a preview;
|
||||
let preview = if full_text.chars().count() > 120 {
|
||||
full_text.chars().take(120).collect::<String>() + "..."
|
||||
// Take up to the first 500 characters as a preview;
|
||||
let preview = if full_text.chars().count() > 500 {
|
||||
full_text.chars().take(500).collect::<String>() + "..."
|
||||
} else {
|
||||
full_text
|
||||
};
|
||||
@@ -204,3 +206,28 @@ impl Envelope {
|
||||
Ok(envelope)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn extract_contacts(doc: &TantivyDocument) -> BichonResult<HashSet<String>> {
|
||||
let fields = SchemaTools::envelope_fields();
|
||||
let mut all_contacts = HashSet::new();
|
||||
|
||||
if let Ok(from_val) = extract_string_field(doc, fields.f_from) {
|
||||
if !from_val.is_empty() {
|
||||
all_contacts.insert(from_val);
|
||||
}
|
||||
}
|
||||
|
||||
let multi_fields = [fields.f_to, fields.f_cc, fields.f_bcc];
|
||||
|
||||
for field in multi_fields {
|
||||
if let Ok(vals) = extract_vec_string_field(doc, field) {
|
||||
for v in vals {
|
||||
if !v.is_empty() {
|
||||
all_contacts.insert(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(all_contacts)
|
||||
}
|
||||
|
||||
@@ -24,7 +24,10 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use crate::modules::message::{search::SortBy, tags::TagCount};
|
||||
use crate::modules::{
|
||||
indexer::envelope::extract_contacts,
|
||||
message::{search::SortBy, tags::TagCount},
|
||||
};
|
||||
use crate::{
|
||||
modules::{
|
||||
account::migration::AccountModel,
|
||||
@@ -57,7 +60,10 @@ use tantivy::{
|
||||
AggregationCollector, Key,
|
||||
},
|
||||
collector::{Count, FacetCollector, TopDocs},
|
||||
query::{AllQuery, BooleanQuery, EmptyQuery, Occur, Query, QueryParser, RangeQuery, RegexQuery, TermQuery},
|
||||
query::{
|
||||
AllQuery, BooleanQuery, EmptyQuery, Occur, Query, QueryParser, RangeQuery, RegexQuery,
|
||||
TermQuery,
|
||||
},
|
||||
schema::{Facet, IndexRecordOption, Value},
|
||||
store::{Compressor, ZstdCompressor},
|
||||
DocAddress, Index, IndexBuilder, IndexReader, IndexSettings, IndexWriter, Order,
|
||||
@@ -307,10 +313,7 @@ impl EnvelopeIndexManager {
|
||||
] {
|
||||
if let Some(ref v) = opt_value {
|
||||
if let Ok(query) = RegexQuery::from_pattern(v.as_str(), field) {
|
||||
subqueries.push((
|
||||
Occur::Must,
|
||||
Box::new(query),
|
||||
));
|
||||
subqueries.push((Occur::Must, Box::new(query)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -329,10 +332,7 @@ impl EnvelopeIndexManager {
|
||||
|
||||
if let Some(ref name) = filter.attachment_name {
|
||||
if let Ok(query) = RegexQuery::from_pattern(name.as_str(), f.f_attachments) {
|
||||
subqueries.push((
|
||||
Occur::Must,
|
||||
Box::new(query),
|
||||
));
|
||||
subqueries.push((Occur::Must, Box::new(query)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -530,6 +530,48 @@ impl EnvelopeIndexManager {
|
||||
Ok(all_facets)
|
||||
}
|
||||
|
||||
pub async fn get_all_contacts(
|
||||
&self,
|
||||
accounts: Option<HashSet<u64>>,
|
||||
) -> BichonResult<HashSet<String>> {
|
||||
let searcher = self.create_searcher()?;
|
||||
|
||||
let query: Box<dyn Query> = match accounts {
|
||||
Some(ref ids) if !ids.is_empty() => {
|
||||
let mut subqueries = Vec::new();
|
||||
for &id in ids {
|
||||
let term =
|
||||
Term::from_field_u64(SchemaTools::envelope_fields().f_account_id, id);
|
||||
subqueries.push((
|
||||
Occur::Should,
|
||||
Box::new(TermQuery::new(term, IndexRecordOption::Basic)) as Box<dyn Query>,
|
||||
));
|
||||
}
|
||||
Box::new(BooleanQuery::new(subqueries))
|
||||
}
|
||||
Some(_) => Box::new(EmptyQuery),
|
||||
None => Box::new(AllQuery),
|
||||
};
|
||||
|
||||
let mut contacts_set: HashSet<String> = HashSet::new();
|
||||
|
||||
let top_docs = searcher
|
||||
.search(&query, &TopDocs::with_limit(1_000_000))
|
||||
.map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?;
|
||||
|
||||
for (_score, doc_address) in top_docs {
|
||||
let doc: TantivyDocument = searcher
|
||||
.doc_async(doc_address)
|
||||
.await
|
||||
.map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?;
|
||||
let contacts = extract_contacts(&doc).await?;
|
||||
for value in contacts {
|
||||
contacts_set.insert(value);
|
||||
}
|
||||
}
|
||||
Ok(contacts_set)
|
||||
}
|
||||
|
||||
pub async fn delete_envelopes_multi_account(
|
||||
&self,
|
||||
deletes: &HashMap<u64, Vec<u64>>, // HashMap<account_id, envelope_ids>
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
use poem_openapi::Object;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)]
|
||||
pub struct Contact {
|
||||
pub email: String,
|
||||
pub name: Option<String>,
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
pub mod append;
|
||||
pub mod contacts;
|
||||
pub mod content;
|
||||
pub mod delete;
|
||||
pub mod list;
|
||||
|
||||
@@ -323,4 +323,25 @@ impl MessageApi {
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[oai(
|
||||
path = "/all-contacts",
|
||||
method = "get",
|
||||
operation_id = "get_all_contacts"
|
||||
)]
|
||||
async fn get_all_contacts(&self, context: ClientContext) -> ApiResult<Json<HashSet<String>>> {
|
||||
let authorized_ids: Option<HashSet<u64>> = if context
|
||||
.has_permission(None, Permission::DATA_READ_ALL)
|
||||
.await
|
||||
{
|
||||
None
|
||||
} else {
|
||||
Some(context.user.account_access_map.keys().cloned().collect())
|
||||
};
|
||||
Ok(Json(
|
||||
ENVELOPE_INDEX_MANAGER
|
||||
.get_all_contacts(authorized_ids)
|
||||
.await?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user