APIs for links and references in the ATmosphere
7
fork

Configure Feed

Select the types of activity you want to include in your feed.

first pass on a search endpoint

+67
+33
ufos/src/server/mod.rs
··· 589 589 OkCors(CollectionTimeseriesResponse { range, series }).into() 590 590 } 591 591 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 + 592 624 pub async fn serve(storage: impl StoreReader + 'static) -> Result<(), String> { 593 625 let log = ConfigLogging::StderrTerminal { 594 626 level: ConfigLoggingLevel::Info, ··· 606 638 api.register(get_collections).unwrap(); 607 639 api.register(get_prefix).unwrap(); 608 640 api.register(get_timeseries).unwrap(); 641 + api.register(search_collections).unwrap(); 609 642 610 643 let context = Context { 611 644 spec: Arc::new(
+2
ufos/src/storage.rs
··· 137 137 limit: usize, 138 138 expand_each_collection: bool, 139 139 ) -> StorageResult<Vec<UFOsRecord>>; 140 + 141 + async fn search_collections(&self, terms: Vec<String>) -> StorageResult<Vec<NsidCount>>; 140 142 }
+32
ufos/src/storage_fjall.rs
··· 982 982 } 983 983 Ok(merged) 984 984 } 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 + } 985 1013 } 986 1014 987 1015 #[async_trait] ··· 1061 1089 FjallReader::get_records_by_collections(&s, collections, limit, expand_each_collection) 1062 1090 }) 1063 1091 .await? 1092 + } 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? 1064 1096 } 1065 1097 } 1066 1098