move repos into monorepo

This commit is contained in:
Timur Gordon
2025-11-13 20:44:00 +01:00
commit 4b23e5eb7f
204 changed files with 33737 additions and 0 deletions

View File

@@ -0,0 +1,140 @@
use crate::error::Result;
use crate::store::{HeroDbClient, OsirisObject};
/// Field indexing for fast filtering by tags and metadata
#[derive(Debug, Clone)]
pub struct FieldIndex {
client: HeroDbClient,
}
impl FieldIndex {
/// Create a new field index
pub fn new(client: HeroDbClient) -> Self {
Self { client }
}
/// Index an object (add to field indexes)
pub async fn index_object(&self, obj: &OsirisObject) -> Result<()> {
// Index tags
for (key, value) in &obj.meta.tags {
let field_key = format!("field:tag:{}={}", key, value);
self.client.sadd(&field_key, &obj.id).await?;
}
// Index MIME type if present
if let Some(mime) = &obj.meta.mime {
let field_key = format!("field:mime:{}", mime);
self.client.sadd(&field_key, &obj.id).await?;
}
// Index title if present (for exact match)
if let Some(title) = &obj.meta.title {
let field_key = format!("field:title:{}", title);
self.client.sadd(&field_key, &obj.id).await?;
}
// Add to scan index for text search
self.client.sadd("scan:index", &obj.id).await?;
Ok(())
}
/// Remove an object from indexes
pub async fn deindex_object(&self, obj: &OsirisObject) -> Result<()> {
// Remove from tag indexes
for (key, value) in &obj.meta.tags {
let field_key = format!("field:tag:{}={}", key, value);
self.client.srem(&field_key, &obj.id).await?;
}
// Remove from MIME index
if let Some(mime) = &obj.meta.mime {
let field_key = format!("field:mime:{}", mime);
self.client.srem(&field_key, &obj.id).await?;
}
// Remove from title index
if let Some(title) = &obj.meta.title {
let field_key = format!("field:title:{}", title);
self.client.srem(&field_key, &obj.id).await?;
}
// Remove from scan index
self.client.srem("scan:index", &obj.id).await?;
Ok(())
}
/// Update object indexes (remove old, add new)
pub async fn reindex_object(&self, old_obj: &OsirisObject, new_obj: &OsirisObject) -> Result<()> {
self.deindex_object(old_obj).await?;
self.index_object(new_obj).await?;
Ok(())
}
/// Get all IDs matching a tag filter
pub async fn get_ids_by_tag(&self, key: &str, value: &str) -> Result<Vec<String>> {
let field_key = format!("field:tag:{}={}", key, value);
self.client.smembers(&field_key).await
}
/// Get all IDs matching a MIME type
pub async fn get_ids_by_mime(&self, mime: &str) -> Result<Vec<String>> {
let field_key = format!("field:mime:{}", mime);
self.client.smembers(&field_key).await
}
/// Get all IDs matching a title
pub async fn get_ids_by_title(&self, title: &str) -> Result<Vec<String>> {
let field_key = format!("field:title:{}", title);
self.client.smembers(&field_key).await
}
/// Get all IDs in the scan index
pub async fn get_all_ids(&self) -> Result<Vec<String>> {
self.client.smembers("scan:index").await
}
/// Get intersection of multiple field filters
pub async fn get_ids_by_filters(&self, filters: &[(String, String)]) -> Result<Vec<String>> {
if filters.is_empty() {
return self.get_all_ids().await;
}
let keys: Vec<String> = filters
.iter()
.map(|(k, v)| {
if k == "mime" {
format!("field:mime:{}", v)
} else if k == "title" {
format!("field:title:{}", v)
} else {
format!("field:tag:{}={}", k, v)
}
})
.collect();
self.client.sinter(&keys).await
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
#[ignore]
async fn test_index_object() {
let client = HeroDbClient::new("redis://localhost:6379", 1).unwrap();
let index = FieldIndex::new(client);
let mut obj = OsirisObject::new("test".to_string(), Some("Hello".to_string()));
obj.set_tag("topic".to_string(), "rust".to_string());
obj.set_mime(Some("text/plain".to_string()));
index.index_object(&obj).await.unwrap();
let ids = index.get_ids_by_tag("topic", "rust").await.unwrap();
assert!(ids.contains(&obj.id));
}
}

View File

@@ -0,0 +1,3 @@
pub mod field_index;
pub use field_index::FieldIndex;