Search not working
This commit is contained in:
parent
6d487dd5c7
commit
427a6cb597
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
/target
|
||||
/migration/target
|
||||
node_modules
|
||||
/indices
|
||||
|
@ -6,6 +6,26 @@ use tantivy::query::QueryParser;
|
||||
use tantivy::{doc, Index, IndexWriter, ReloadPolicy, Searcher};
|
||||
use tantivy::{schema::*, TantivyError};
|
||||
|
||||
#[derive(Debug, Clone, derive_more::Deref)]
|
||||
pub struct Search(pub Addr<SearchEngine>);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RecipeRecord {
|
||||
pub id: u64,
|
||||
pub title: String,
|
||||
pub summary: Option<String>,
|
||||
}
|
||||
|
||||
impl From<crate::entities::recipies::Model> for RecipeRecord {
|
||||
fn from(value: crate::entities::recipies::Model) -> Self {
|
||||
Self {
|
||||
id: value.id as u64,
|
||||
title: value.title,
|
||||
summary: value.summary,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Inner {
|
||||
writer: IndexWriter,
|
||||
schema: Schema,
|
||||
@ -18,13 +38,17 @@ pub struct SearchEngine(Arc<Mutex<Inner>>);
|
||||
impl SearchEngine {
|
||||
pub fn build() -> Result<Self, tantivy::TantivyError> {
|
||||
let index_path = std::path::Path::new("./indices");
|
||||
std::fs::create_dir_all(&index_path).expect("Failed to create indices directory");
|
||||
|
||||
let mut schema_builder = Schema::builder();
|
||||
schema_builder.add_u64_field("id", INDEXED);
|
||||
schema_builder.add_text_field("title", TEXT);
|
||||
schema_builder.add_text_field("summary", TEXT);
|
||||
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_dir(&index_path, schema.clone())?;
|
||||
let index = Index::create_in_dir(&index_path, schema.clone())
|
||||
.or_else(|_| Index::open_in_dir(&index_path))
|
||||
.expect("Failed to construct indices directory");
|
||||
let index_writer: IndexWriter = index.writer(50_000_000)?;
|
||||
|
||||
let reader = index
|
||||
@ -50,9 +74,7 @@ impl actix::Actor for SearchEngine {
|
||||
#[derive(Debug, Message)]
|
||||
#[rtype(result = "Result<u64,TantivyError>")]
|
||||
pub struct CreateRecipe {
|
||||
id: u64,
|
||||
title: String,
|
||||
summary: String,
|
||||
pub record: RecipeRecord,
|
||||
}
|
||||
|
||||
impl Handler<CreateRecipe> for SearchEngine {
|
||||
@ -68,10 +90,11 @@ impl Handler<CreateRecipe> for SearchEngine {
|
||||
let title = shared.schema.get_field("summary").unwrap();
|
||||
let summary = shared.schema.get_field("summary").unwrap();
|
||||
|
||||
let msg = msg.record;
|
||||
let n = shared.writer.add_document(doc! {
|
||||
id => msg.id,
|
||||
title => msg.title,
|
||||
summary => msg.summary,
|
||||
summary => msg.summary.unwrap_or_default(),
|
||||
})?;
|
||||
shared.writer.commit()?;
|
||||
|
||||
@ -85,7 +108,7 @@ impl Handler<CreateRecipe> for SearchEngine {
|
||||
#[derive(Debug, Message)]
|
||||
#[rtype(result = "Result<Vec<u64>,TantivyError>")]
|
||||
pub struct Find {
|
||||
query: String,
|
||||
pub query: String,
|
||||
}
|
||||
|
||||
impl Handler<Find> for SearchEngine {
|
||||
@ -102,19 +125,38 @@ impl Handler<Find> for SearchEngine {
|
||||
let summary = shared.schema.get_field("summary").unwrap();
|
||||
|
||||
let query_parser = QueryParser::for_index(&shared.index, vec![title, summary]);
|
||||
let query = query_parser.parse_query(&msg.query)?;
|
||||
let query = msg
|
||||
.query
|
||||
.split_whitespace()
|
||||
.map(|piece| {
|
||||
piece
|
||||
.chars()
|
||||
.filter(|c| c.is_alphabetic())
|
||||
.collect::<String>()
|
||||
})
|
||||
.filter(|s| !s.trim().is_empty())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" OR ");
|
||||
tracing::debug!("Query is: {query:?}");
|
||||
let query = query_parser.parse_query(&query).expect("invalid query");
|
||||
|
||||
let rows = shared.searcher.search(&query, &TopDocs::with_limit(100))?;
|
||||
|
||||
let ids = rows
|
||||
.into_iter()
|
||||
.filter_map(|row| {
|
||||
tracing::debug!("tantivy row: {row:?}");
|
||||
let doc: Option<TantivyDocument> = shared.searcher.doc(row.1).ok();
|
||||
doc
|
||||
})
|
||||
.fold(Vec::with_capacity(1_000), |agg, doc| {
|
||||
let json = doc.to_json(&shared.schema);
|
||||
tracing::debug!("tantivy doc: {doc:?} {json:?}");
|
||||
doc.get_all(id)
|
||||
.filter_map(|id| id.as_u64())
|
||||
.filter_map(|id| {
|
||||
tracing::debug!("tantivy id: {id:?}");
|
||||
id.as_u64()
|
||||
})
|
||||
.fold(agg, |mut agg, id| {
|
||||
agg.push(id);
|
||||
agg
|
||||
@ -127,3 +169,41 @@ impl Handler<Find> for SearchEngine {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Message)]
|
||||
#[rtype(result = "Result<(), TantivyError>")]
|
||||
pub struct Refresh {
|
||||
pub records: Vec<RecipeRecord>,
|
||||
}
|
||||
|
||||
impl Handler<Refresh> for SearchEngine {
|
||||
type Result = actix::ResponseActFuture<Self, Result<(), TantivyError>>;
|
||||
|
||||
fn handle(&mut self, msg: Refresh, _ctx: &mut Self::Context) -> Self::Result {
|
||||
let inner = self.0.clone();
|
||||
Box::pin(
|
||||
async move {
|
||||
let mut shared = inner.lock().unwrap();
|
||||
|
||||
let id = shared.schema.get_field("id").unwrap();
|
||||
let title = shared.schema.get_field("summary").unwrap();
|
||||
let summary = shared.schema.get_field("summary").unwrap();
|
||||
|
||||
shared.writer.delete_all_documents()?;
|
||||
|
||||
for msg in msg.records {
|
||||
tracing::debug!("creating search index for {msg:?}");
|
||||
let _n = shared.writer.add_document(doc! {
|
||||
id => msg.id,
|
||||
title => msg.title,
|
||||
summary => msg.summary.unwrap_or_default(),
|
||||
})?;
|
||||
}
|
||||
shared.writer.commit()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
.into_actor(self),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
57
src/main.rs
57
src/main.rs
@ -1,3 +1,4 @@
|
||||
use actix::Actor;
|
||||
use actix_files::Files;
|
||||
use actix_identity::IdentityMiddleware;
|
||||
use actix_session::{storage::RedisSessionStore, SessionMiddleware};
|
||||
@ -15,6 +16,30 @@ pub mod types;
|
||||
|
||||
const SESSION_KEY: &'static str = "session";
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
struct Q {
|
||||
q: String,
|
||||
}
|
||||
#[actix_web::get("/s")]
|
||||
async fn test_search(
|
||||
q: actix_web::web::Query<Q>,
|
||||
search: Data<crate::actors::search::Search>,
|
||||
) -> actix_web::HttpResponse {
|
||||
let res = search
|
||||
.send(crate::actors::search::Find {
|
||||
query: q.into_inner().q,
|
||||
})
|
||||
.await;
|
||||
tracing::debug!("search res: {res:?}");
|
||||
actix_web::HttpResponse::Ok().body(
|
||||
serde_json::to_string(&match res {
|
||||
Ok(Ok(res)) => res,
|
||||
_ => Vec::new(),
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
}
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() {
|
||||
let _ = tracing_subscriber::fmt::init();
|
||||
@ -55,10 +80,38 @@ async fn main() {
|
||||
}
|
||||
};
|
||||
drop(secret);
|
||||
tracing::info!("{:?}", secret_key.master());
|
||||
tracing::debug!("{:?}", secret_key.master());
|
||||
let redis_store = RedisSessionStore::new(redis_url.as_str()).await.unwrap();
|
||||
|
||||
let search = {
|
||||
let search_addr = crate::actors::search::SearchEngine::build()
|
||||
.unwrap()
|
||||
.start();
|
||||
use crate::actors::search::*;
|
||||
use sea_orm::prelude::*;
|
||||
let records = entities::prelude::Recipies::find()
|
||||
.all(&db)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(|recipe| RecipeRecord::from(recipe))
|
||||
.collect::<Vec<_>>();
|
||||
let title = records
|
||||
.first()
|
||||
.map(|rec| rec.title.clone())
|
||||
.unwrap_or_else(|| "test".into());
|
||||
let _ = search_addr.send(Refresh { records }).await;
|
||||
let res = search_addr
|
||||
.send(Find { query: title })
|
||||
.await
|
||||
.expect("Search can't fail")
|
||||
.expect("Must find something");
|
||||
tracing::debug!("Test search result: {res:?}");
|
||||
Search(search_addr)
|
||||
};
|
||||
|
||||
// Transform to data
|
||||
let search = Data::new(search);
|
||||
let admins = Data::new(admins);
|
||||
let db = Data::new(db);
|
||||
let redis = Data::new(redis);
|
||||
@ -80,7 +133,9 @@ async fn main() {
|
||||
.app_data(admins.clone())
|
||||
.app_data(db.clone())
|
||||
.app_data(redis.clone())
|
||||
.app_data(search.clone())
|
||||
.service(Files::new("/pages", "./pages"))
|
||||
.service(test_search)
|
||||
.configure(routes::configure)
|
||||
})
|
||||
.bind(&bind)
|
||||
|
Loading…
Reference in New Issue
Block a user