use adminx_core::search::Indexer;
use adminx_core::storage::StorageError;
use async_trait::async_trait;
use searchez::{Backend, Document, IndexDoc, Query};
use serde_json::{Map, Value};
pub struct SearchezIndexer {
backend: Box<dyn Backend>,
}
impl SearchezIndexer {
pub fn new(backend: impl Backend + 'static) -> Self {
Self {
backend: Box::new(backend),
}
}
}
#[async_trait]
impl Indexer for SearchezIndexer {
async fn index(
&self,
index: &str,
id: &str,
document: Map<String, Value>,
) -> Result<(), StorageError> {
self.backend
.upsert(
index,
vec![IndexDoc {
id: id.to_string(),
document: Document::from(document),
}],
)
.await
.map_err(to_storage_err)
}
async fn remove(&self, index: &str, id: &str) -> Result<(), StorageError> {
self.backend
.delete(index, &[id.to_string()])
.await
.map_err(to_storage_err)
}
async fn search(
&self,
index: &str,
query: &str,
limit: usize,
) -> Result<Vec<String>, StorageError> {
let hits = self
.backend
.search(index, &Query::text(query).limit(limit))
.await
.map_err(to_storage_err)?;
Ok(hits.into_iter().map(|h| h.id).collect())
}
}
pub fn init(backend: impl Backend + 'static) {
adminx_core::set_indexer(Box::new(SearchezIndexer::new(backend)));
tracing::info!("adminx-search: full-text search enabled");
}
pub fn init_memory() {
init(searchez::MemoryBackend::new());
}
pub async fn reindex<R: adminx_core::resource::Resource + ?Sized>(
resource: &R,
rows: &[Value],
) -> Result<usize, StorageError> {
let Some(indexer) = adminx_core::search::indexer() else {
return Ok(0);
};
let fields = resource.search_fields();
let pk = resource.primary_key();
let mut n = 0;
for row in rows {
let Some(id) = row.get(pk).map(value_to_id) else {
continue;
};
let doc = adminx_core::search::document_for(row, &fields);
indexer.index(resource.base_path(), &id, doc).await?;
n += 1;
}
tracing::info!("adminx-search: reindexed {n} row(s) into `{}`", resource.base_path());
Ok(n)
}
fn value_to_id(v: &Value) -> String {
match v {
Value::String(s) => s.clone(),
Value::Number(n) => n.to_string(),
other => other.to_string(),
}
}
fn to_storage_err(e: searchez::SearchError) -> StorageError {
StorageError::Backend(format!("search: {e}"))
}