gitdataai/libs/transport/search.rs

123 lines
3.3 KiB
Rust

use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchQuery {
pub query: String,
pub room_id: Option<Uuid>,
pub user_id: Option<Uuid>,
pub limit: u64,
pub offset: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchResult {
pub total: u64,
pub hits: Vec<SearchHit>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchHit {
pub message_id: Uuid,
pub room_id: Uuid,
pub content: String,
pub highlighted: String,
pub sender_id: Option<Uuid>,
pub send_at: chrono::DateTime<chrono::Utc>,
pub score: f64,
}
pub struct SearchEngine {
db: db::database::AppDatabase,
}
impl SearchEngine {
pub fn new(db: db::database::AppDatabase) -> Self {
Self { db }
}
pub async fn search(
&self,
query: SearchQuery,
) -> Result<SearchResult, crate::error::AppTransportError> {
use models::rooms::room_message;
use sea_orm::*;
let mut db_query = room_message::Entity::find();
if let Some(room_id) = query.room_id {
db_query = db_query.filter(room_message::Column::Room.eq(room_id));
}
if let Some(user_id) = query.user_id {
db_query = db_query.filter(room_message::Column::SenderId.eq(user_id));
}
let escaped_query = query
.query
.replace('\\', "\\\\")
.replace('%', "\\%")
.replace('_', "\\_");
let search_term = format!("%{}%", escaped_query);
db_query = db_query.filter(room_message::Column::Content.like(&search_term));
let total = db_query
.clone()
.count(&self.db)
.await
.map_err(|_| crate::error::AppTransportError::Internal)?;
let messages = db_query
.order_by_desc(room_message::Column::SendAt)
.limit(query.limit)
.offset(query.offset)
.all(&self.db)
.await
.map_err(|_| crate::error::AppTransportError::Internal)?;
let hits: Vec<SearchHit> = messages
.into_iter()
.map(|m| SearchHit {
message_id: m.id,
room_id: m.room,
content: m.content.clone(),
highlighted: highlight_text(&m.content, &query.query),
sender_id: m.sender_id,
send_at: m.send_at,
score: 1.0,
})
.collect();
Ok(SearchResult { total, hits })
}
pub async fn index_message(
&self,
_message_id: Uuid,
_content: &str,
) -> Result<(), crate::error::AppTransportError> {
Ok(())
}
pub async fn delete_message(
&self,
_message_id: Uuid,
) -> Result<(), crate::error::AppTransportError> {
Ok(())
}
}
fn highlight_text(content: &str, query: &str) -> String {
let lower_content = content.to_lowercase();
let lower_query = query.to_lowercase();
if let Some(pos) = lower_content.find(&lower_query) {
let before = &content[..pos];
let matched = &content[pos..pos + query.len()];
let after = &content[pos + query.len()..];
format!("{}<mark>{}</mark>{}", before, matched, after)
} else {
content.to_string()
}
}