···136136- [x] either count or estimate the total number of links added (distinct from link targets)
137137- [x] jetstream: don't crash on connection refused (retry * backoff)
138138- [x] allow cors requests (ie. atproto-browser. (but it's really meant for backends))
139139+- [~] api: get distinct linking dids (https://bsky.app/profile/bnewbold.net/post/3lhhzejv7zc2h)
140140+ - [~] endpoint for count
141141+ - [~] endpoint for listing them
142142+ - [ ] add to exploratory /all endpoint
139143140144cache
141145- [ ] set api response headers
+86-4
constellation/src/storage/mem_store.rs
···22use anyhow::Result;
33use constellation::{ActionableEvent, Did, RecordId};
44use links::CollectedLink;
55-use std::collections::HashMap;
55+use std::collections::{HashMap, HashSet};
66use std::sync::{Arc, Mutex};
7788// hopefully-correct simple hashmap version, intended only for tests to verify disk impl
···131131 let Some(paths) = data.targets.get(&Target::new(target)) else {
132132 return Ok(0);
133133 };
134134- let Some(dids) = paths.get(&Source::new(collection, path)) else {
134134+ let Some(linkers) = paths.get(&Source::new(collection, path)) else {
135135 return Ok(0);
136136 };
137137- Ok(dids.iter().flatten().count().try_into()?)
137137+ Ok(linkers.iter().flatten().count() as u64)
138138+ }
139139+140140+ fn get_distinct_did_count(&self, target: &str, collection: &str, path: &str) -> Result<u64> {
141141+ let data = self.0.lock().unwrap();
142142+ let Some(paths) = data.targets.get(&Target::new(target)) else {
143143+ return Ok(0);
144144+ };
145145+ let Some(linkers) = paths.get(&Source::new(collection, path)) else {
146146+ return Ok(0);
147147+ };
148148+ Ok(linkers
149149+ .iter()
150150+ .flatten()
151151+ .map(|(did, _)| did)
152152+ .collect::<HashSet<_>>()
153153+ .len() as u64)
138154 }
139155140156 fn get_links(
···190206 })
191207 }
192208209209+ fn get_distinct_dids(
210210+ &self,
211211+ target: &str,
212212+ collection: &str,
213213+ path: &str,
214214+ limit: u64,
215215+ until: Option<u64>,
216216+ ) -> Result<PagedAppendingCollection<Did>> {
217217+ let data = self.0.lock().unwrap();
218218+ let Some(paths) = data.targets.get(&Target::new(target)) else {
219219+ return Ok(PagedAppendingCollection {
220220+ version: (0, 0),
221221+ items: Vec::new(),
222222+ next: None,
223223+ });
224224+ };
225225+ let Some(did_rkeys) = paths.get(&Source::new(collection, path)) else {
226226+ return Ok(PagedAppendingCollection {
227227+ version: (0, 0),
228228+ items: Vec::new(),
229229+ next: None,
230230+ });
231231+ };
232232+233233+ let dids: Vec<Option<Did>> = {
234234+ let mut seen = HashSet::new();
235235+ did_rkeys
236236+ .iter()
237237+ .map(|o| {
238238+ o.clone().and_then(|(did, _)| {
239239+ if seen.contains(&did) {
240240+ None
241241+ } else {
242242+ seen.insert(did.clone());
243243+ Some(did)
244244+ }
245245+ })
246246+ })
247247+ .collect()
248248+ };
249249+250250+ let total = dids.len();
251251+ let end = until
252252+ .map(|u| std::cmp::min(u as usize, total))
253253+ .unwrap_or(total);
254254+ let begin = end.saturating_sub(limit as usize);
255255+ let next = if begin == 0 { None } else { Some(begin as u64) };
256256+257257+ let alive = dids.iter().flatten().count();
258258+ let gone = total - alive;
259259+260260+ let items: Vec<Did> = dids[begin..end]
261261+ .iter()
262262+ .rev()
263263+ .flatten()
264264+ .filter(|did| *data.dids.get(did).expect("did must be in dids"))
265265+ .cloned()
266266+ .collect();
267267+268268+ Ok(PagedAppendingCollection {
269269+ version: (total as u64, gone as u64),
270270+ items,
271271+ next,
272272+ })
273273+ }
274274+193275 fn get_all_counts(&self, target: &str) -> Result<HashMap<String, HashMap<String, u64>>> {
194276 let data = self.0.lock().unwrap();
195277 let mut out: HashMap<String, HashMap<String, u64>> = HashMap::new();
196278 if let Some(asdf) = data.targets.get(&Target::new(target)) {
197279 for (Source { collection, path }, linkers) in asdf {
198198- let count = linkers.iter().flatten().count().try_into()?;
280280+ let count = linkers.iter().flatten().count() as u64;
199281 out.entry(collection.to_string())
200282 .or_default()
201283 .insert(path.to_string(), count);