Constellation, Spacedust, Slingshot, UFOs: atproto crates and services for microcosm
0

Configure Feed

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

blobbbbbbbbssssyyyyyyy (wip branch: needs account context)

+51 -4
+22 -4
slingshot/src/proxy.rs
··· 225 225 Ok(out) 226 226 } 227 227 228 - #[derive(Debug, Clone, Copy, PartialEq)] 228 + #[derive(Debug, Clone, PartialEq)] 229 229 pub enum RefShape { 230 230 StrongRef, 231 231 AtUri, ··· 233 233 Did, 234 234 Handle, 235 235 AtIdentifier, 236 + Blob, 237 + // TODO: blob with type? 236 238 } 237 239 238 240 impl TryFrom<&str> for RefShape { ··· 245 247 "did" => Ok(Self::Did), 246 248 "handle" => Ok(Self::Handle), 247 249 "at-identifier" => Ok(Self::AtIdentifier), 250 + "blob" => Ok(Self::Blob), 248 251 _ => Err(format!("unknown shape: {s}")), 249 252 } 250 253 } ··· 257 260 cid: Option<String>, 258 261 }, 259 262 Identifier(String), 263 + Blob { 264 + link: String, 265 + mime: String, 266 + size: u64, 267 + } 260 268 } 261 269 262 - pub fn match_shape(shape: RefShape, val: &Value) -> Option<MatchedRef> { 270 + pub fn match_shape(shape: &RefShape, val: &Value) -> Option<MatchedRef> { 263 271 // TODO: actually validate at-uri format 264 272 // TODO: actually validate everything else also 265 273 // TODO: should this function normalize identifiers to DIDs probably? ··· 301 309 RefShape::AtIdentifier => { 302 310 Some(MatchedRef::Identifier(val.as_str()?.to_string())) 303 311 } 312 + RefShape::Blob => { 313 + let o = val.as_object()?; 314 + if o.get("$type")? != "blob" { 315 + return None; 316 + } 317 + let link = o.get("ref")?.as_object()?.get("$link")?.as_str()?.to_string(); 318 + let mime = o.get("mimeType")?.as_str()?.to_string(); 319 + let size = o.get("size")?.as_u64()?; 320 + Some(MatchedRef::Blob { link, mime, size }) 321 + } 304 322 } 305 323 } 306 324 ··· 325 343 let mut out = Vec::new(); 326 344 for (path_parts, shape) in sources { 327 345 for val in PathWalker::new(&path_parts, skeleton) { 328 - if let Some(matched) = match_shape(shape, val) { 346 + if let Some(matched) = match_shape(&shape, val) { 329 347 out.push(matched); 330 348 } 331 349 } ··· 480 498 ]; 481 499 for (shape, val, expected) in cases { 482 500 let s = shape.try_into().unwrap(); 483 - let matched = match_shape(s, &val); 501 + let matched = match_shape(&s, &val); 484 502 assert_eq!(matched, expected, "shape: {shape:?}, val: {val:?}"); 485 503 } 486 504 }
+29
slingshot/src/server.rs
··· 218 218 mini_doc: MiniDocResponseObject, 219 219 } 220 220 221 + #[derive(Object)] 222 + #[oai(rename_all = "camelCase")] 223 + struct ProxyHydrationBlobFound { 224 + /// cdn url 225 + link: String, 226 + mime_type: String, 227 + size: u64, 228 + } 229 + 221 230 // todo: there's gotta be a supertrait that collects these? 222 231 use poem_openapi::types::{Type, ToJSON, ParseFromJSON, IsObjectType}; 223 232 ··· 237 246 /// Any hydrated records 238 247 records: HashMap<String, Hydration<ProxyHydrationRecordFound>>, 239 248 /// Any hydrated identifiers 249 + /// 250 + /// TODO: "identifiers" feels wrong as the name, probably "identities"? 240 251 identifiers: HashMap<String, Hydration<ProxyHydrationIdentifierFound>>, 252 + /// Any hydrated blob CDN urls 253 + blobs: HashMap<String, Hydration<ProxyHydrationBlobFound>>, 241 254 } 242 255 impl Example for ProxyHydrateResponseObject { 243 256 fn example() -> Self { ··· 247 260 ("asdf".into(), Hydration::Pending(ProxyHydrationPending { url: "todo".into() })), 248 261 ]), 249 262 identifiers: HashMap::new(), 263 + blobs: HashMap::new(), 250 264 } 251 265 } 252 266 } ··· 715 729 }; 716 730 let mut records = HashMap::new(); 717 731 let mut identifiers = HashMap::new(); 732 + let mut blobs = HashMap::new(); 718 733 719 734 enum GetThing { 720 735 Record(String, Hydration<ProxyHydrationRecordFound>), 721 736 Identifier(String, Hydration<ProxyHydrationIdentifierFound>), 737 + Blob(String, Hydration<ProxyHydrationBlobFound>), 722 738 } 723 739 724 740 let (tx, mut rx) = mpsc::channel(1); ··· 805 821 tx.send(GetThing::Identifier(id, res)).await 806 822 }); 807 823 } 824 + MatchedRef::Blob { link, mime, size: _ } => { 825 + if blobs.contains_key(&link) { 826 + continue; 827 + } 828 + if mime != "image/jpeg" { 829 + Hydration::<ProxyHydrationBlobFound>::Error(ProxyHydrationError { 830 + reason: "only image/jpeg supported for now".to_string(), 831 + }); 832 + } 833 + todo!("oops we need to know the account too") 834 + } 808 835 } 809 836 } 810 837 // so the channel can close when all are completed ··· 815 842 match hydration { 816 843 GetThing::Record(uri, h) => { records.insert(uri, h); } 817 844 GetThing::Identifier(uri, md) => { identifiers.insert(uri, md); } 845 + GetThing::Blob(cid, asdf) => { blobs.insert(cid, asdf); } 818 846 }; 819 847 } 820 848 ··· 822 850 output: skeleton, 823 851 records, 824 852 identifiers, 853 + blobs, 825 854 })) 826 855 } 827 856 Err(e) => {