use crate::request::ReqCtx;
use crate::storage::StorageError;
use async_trait::async_trait;
use once_cell::sync::OnceCell;
use serde_json::{Map, Value};
#[async_trait]
pub trait Indexer: Send + Sync {
async fn index(
&self,
index: &str,
id: &str,
document: Map<String, Value>,
) -> Result<(), StorageError>;
async fn remove(&self, index: &str, id: &str) -> Result<(), StorageError>;
async fn search(
&self,
index: &str,
query: &str,
limit: usize,
) -> Result<Vec<String>, StorageError>;
}
static INDEXER: OnceCell<Box<dyn Indexer>> = OnceCell::new();
pub fn set_indexer(indexer: Box<dyn Indexer>) {
if INDEXER.set(indexer).is_err() {
tracing::warn!("adminx indexer already initialized; ignoring reset");
}
}
pub fn indexer() -> Option<&'static dyn Indexer> {
INDEXER.get().map(|b| b.as_ref())
}
pub fn is_enabled() -> bool {
INDEXER.get().is_some()
}
pub fn document_for(row: &Value, fields: &[&str]) -> Map<String, Value> {
let mut doc = Map::new();
if let Value::Object(map) = row {
for f in fields {
if let Some(v) = map.get(*f) {
doc.insert((*f).to_string(), v.clone());
}
}
}
doc
}
pub async fn index_record(index: &str, id: &str, document: Map<String, Value>) {
if let Some(indexer) = indexer() {
if let Err(e) = indexer.index(index, id, document).await {
tracing::error!("adminx: failed to index {index}/{id}: {e}");
}
}
}
pub async fn remove_record(index: &str, id: &str) {
if let Some(indexer) = indexer() {
if let Err(e) = indexer.remove(index, id).await {
tracing::error!("adminx: failed to de-index {index}/{id}: {e}");
}
}
}
pub async fn search_ids(index: &str, query: &str, limit: usize) -> Vec<String> {
let Some(indexer) = indexer() else {
return Vec::new();
};
match indexer.search(index, query, limit).await {
Ok(ids) => ids,
Err(e) => {
tracing::error!("adminx: search on {index} failed: {e}");
Vec::new()
}
}
}
pub fn query_term(ctx: &ReqCtx) -> Option<String> {
let params: std::collections::HashMap<String, String> =
serde_urlencoded::from_str(&ctx.query).unwrap_or_default();
params
.get("q")
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
}