Skip to content

Commit ec33fd7

Browse files
committed
first pass on a search endpoint
1 parent 354e39e commit ec33fd7

File tree

3 files changed

+67
-0
lines changed

3 files changed

+67
-0
lines changed

ufos/src/server/mod.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -589,6 +589,38 @@ async fn get_timeseries(
589589
OkCors(CollectionTimeseriesResponse { range, series }).into()
590590
}
591591

592+
#[derive(Debug, Deserialize, JsonSchema)]
593+
struct SearchQuery {
594+
/// Query
595+
///
596+
/// at least two alphanumeric (+hyphen) characters must be present
597+
q: String,
598+
}
599+
#[derive(Debug, Serialize, JsonSchema)]
600+
struct SearchResponse {
601+
matches: Vec<NsidCount>,
602+
}
603+
/// Search lexicons
604+
#[endpoint {
605+
method = GET,
606+
path = "/search"
607+
}]
608+
async fn search_collections(
609+
ctx: RequestContext<Context>,
610+
query: Query<SearchQuery>,
611+
) -> OkCorsResponse<SearchResponse> {
612+
let Context { storage, .. } = ctx.context();
613+
let q = query.into_inner();
614+
// TODO: query validation
615+
// TODO: also handle multi-space stuff (ufos-app tries to on client)
616+
let terms: Vec<String> = q.q.split(' ').map(Into::into).collect();
617+
let matches = storage
618+
.search_collections(terms)
619+
.await
620+
.map_err(|e| HttpError::for_internal_error(format!("oh ugh: {e:?}")))?;
621+
OkCors(SearchResponse { matches }).into()
622+
}
623+
592624
pub async fn serve(storage: impl StoreReader + 'static) -> Result<(), String> {
593625
let log = ConfigLogging::StderrTerminal {
594626
level: ConfigLoggingLevel::Info,
@@ -606,6 +638,7 @@ pub async fn serve(storage: impl StoreReader + 'static) -> Result<(), String> {
606638
api.register(get_collections).unwrap();
607639
api.register(get_prefix).unwrap();
608640
api.register(get_timeseries).unwrap();
641+
api.register(search_collections).unwrap();
609642

610643
let context = Context {
611644
spec: Arc::new(

ufos/src/storage.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,4 +137,6 @@ pub trait StoreReader: Send + Sync {
137137
limit: usize,
138138
expand_each_collection: bool,
139139
) -> StorageResult<Vec<UFOsRecord>>;
140+
141+
async fn search_collections(&self, terms: Vec<String>) -> StorageResult<Vec<NsidCount>>;
140142
}

ufos/src/storage_fjall.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -982,6 +982,34 @@ impl FjallReader {
982982
}
983983
Ok(merged)
984984
}
985+
986+
fn search_collections(&self, terms: Vec<String>) -> StorageResult<Vec<NsidCount>> {
987+
let start = AllTimeRollupKey::start()?;
988+
let end = AllTimeRollupKey::end()?;
989+
let mut matches = Vec::new();
990+
let limit = 16; // TODO: param
991+
for kv in self.rollups.range((start, end)) {
992+
let (key_bytes, val_bytes) = kv?;
993+
let key = db_complete::<AllTimeRollupKey>(&key_bytes)?;
994+
let nsid = key.collection().as_str().to_string();
995+
for term in &terms {
996+
if nsid.contains(term) {
997+
let counts = db_complete::<CountsValue>(&val_bytes)?;
998+
matches.push(NsidCount {
999+
nsid: nsid.clone(),
1000+
creates: counts.counts().creates,
1001+
dids_estimate: counts.dids().estimate() as u64,
1002+
});
1003+
break;
1004+
}
1005+
}
1006+
if matches.len() >= limit {
1007+
break;
1008+
}
1009+
}
1010+
// TODO: indicate incomplete results
1011+
Ok(matches)
1012+
}
9851013
}
9861014

9871015
#[async_trait]
@@ -1062,6 +1090,10 @@ impl StoreReader for FjallReader {
10621090
})
10631091
.await?
10641092
}
1093+
async fn search_collections(&self, terms: Vec<String>) -> StorageResult<Vec<NsidCount>> {
1094+
let s = self.clone();
1095+
tokio::task::spawn_blocking(move || FjallReader::search_collections(&s, terms)).await?
1096+
}
10651097
}
10661098

10671099
#[derive(Clone)]

0 commit comments

Comments
 (0)