slop slop slop sahuuuurrr
0
fork

Configure Feed

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

fixing

dawn 0966b6b0 626e6362

+426 -175
+426 -175
src/main.rs
··· 10 10 use hydrant::deps::futures::StreamExt; 11 11 use jacquard_common::IntoStatic; 12 12 use jacquard_common::types::ident::AtIdentifier; 13 - use serde::{Deserialize, Serialize}; 13 + use serde::Deserialize; 14 14 use tracing; 15 15 use tracing_subscriber::EnvFilter; 16 16 ··· 23 23 mutes: fjall::Keyspace, 24 24 notifications: fjall::Keyspace, 25 25 seen: fjall::Keyspace, 26 + cdn_url: String, 27 + } 28 + 29 + impl AppState { 30 + fn cdn(&self, category: &str, did: &str, link: &str) -> String { 31 + format!( 32 + "{}/img/{}/plain/{}/{}@jpeg", 33 + self.cdn_url, category, did, link 34 + ) 35 + } 26 36 } 27 37 28 38 #[tokio::main] ··· 80 90 "app.bsky.labeler.service".to_string(), 81 91 ]); 82 92 93 + let cdn_url = std::env::var("CDN_URL").unwrap_or_else(|_| "https://cdn.bsky.app".to_string()); 94 + 83 95 let hydrant = Hydrant::new(cfg).await?; 84 96 let hydrant_clone = hydrant.clone(); 85 97 let app_state = AppState { ··· 90 102 mutes, 91 103 notifications, 92 104 seen, 105 + cdn_url, 93 106 }; 94 107 95 108 if let Ok(seed_account) = std::env::var("SEED_ACCOUNT") { ··· 435 448 } 436 449 } 437 450 438 - Ok(serde_json::json!({ 451 + let mut post_view = serde_json::json!({ 439 452 "uri": uri_str, 440 453 "cid": record.cid.to_string(), 441 454 "author": author_profile, ··· 445 458 "likeCount": app_state.hydrant.backlinks.count(uri_str.to_string()).source("app.bsky.feed.like").run().await.unwrap_or(0), 446 459 "indexedAt": chrono::Utc::now().to_rfc3339(), 447 460 "viewer": viewer_state, 448 - })) 461 + "labels": [], 462 + }); 463 + 464 + let val_json = serde_json::to_value(&record.value).unwrap_or(serde_json::json!({})); 465 + if let Some(record_embed) = val_json.get("embed") { 466 + let t = record_embed 467 + .get("$type") 468 + .and_then(|v| v.as_str()) 469 + .unwrap_or(""); 470 + if t == "app.bsky.embed.images" { 471 + if let Some(images) = record_embed.get("images").and_then(|v| v.as_array()) { 472 + let mut view_images = Vec::new(); 473 + for img in images { 474 + let link = img 475 + .get("image") 476 + .and_then(|v| v.get("ref")) 477 + .and_then(|v| v.get("$link")) 478 + .and_then(|v| v.as_str()) 479 + .unwrap_or(""); 480 + let thumb = app_state.cdn("feed_thumbnail", repo.did.as_str(), link); 481 + let fullsize = app_state.cdn("feed_fullsize", repo.did.as_str(), link); 482 + view_images.push(serde_json::json!({ 483 + "thumb": thumb, 484 + "fullsize": fullsize, 485 + "alt": img.get("alt").and_then(|v| v.as_str()).unwrap_or(""), 486 + "aspectRatio": img.get("aspectRatio"), 487 + })); 488 + } 489 + post_view["embed"] = serde_json::json!({ 490 + "$type": "app.bsky.embed.images#view", 491 + "images": view_images, 492 + }); 493 + } 494 + } else if t == "app.bsky.embed.record" { 495 + if let Some(quoted_uri) = record_embed 496 + .get("record") 497 + .and_then(|v| v.get("uri")) 498 + .and_then(|v| v.as_str()) 499 + { 500 + // For now, let's just do a shallow hydration of the quoted post to avoid infinite recursion 501 + // We use None for viewer_did here to simplify and avoid auth-loops 502 + if let Ok(quoted_post) = Box::pin(get_post_view(app_state, quoted_uri, None)).await 503 + { 504 + post_view["embed"] = serde_json::json!({ 505 + "$type": "app.bsky.embed.record#view", 506 + "record": { 507 + "$type": "app.bsky.embed.record#viewRecord", 508 + "uri": quoted_post["uri"], 509 + "cid": quoted_post["cid"], 510 + "author": quoted_post["author"], 511 + "value": quoted_post["record"], 512 + "labels": quoted_post["labels"], 513 + "indexedAt": quoted_post["indexedAt"], 514 + "embeds": quoted_post.get("embed").map(|e| vec![e]).unwrap_or_default(), 515 + } 516 + }); 517 + } 518 + } 519 + } 520 + } 521 + 522 + Ok(post_view) 449 523 } 450 524 451 525 async fn get_thread_view_post( ··· 550 624 StatusCode::BAD_REQUEST 551 625 })?; 552 626 627 + let viewer_did = get_auth_did(&req); 553 628 match app_state.hydrant.repos.resolve(&ident).await { 554 629 Ok(repo) => { 555 - match get_profile_internal(&app_state, repo.did.as_str(), None).await { 630 + match get_profile_internal(&app_state, repo.did.as_str(), viewer_did.as_deref()).await { 556 631 Ok(profile) => return Ok(Json(profile).into_response()), 557 632 Err(e) => { 558 633 tracing::warn!("failed to get profile for {}: {e}", repo.did); ··· 698 773 "postsCount": posts_count, 699 774 "indexedAt": chrono::Utc::now().to_rfc3339(), 700 775 "viewer": viewer_state, 776 + "labels": [], 777 + "associated": { 778 + "chat": { "allowIncoming": "all" } 779 + } 701 780 }); 702 781 703 782 if let Some(rec) = profile_record { ··· 711 790 obj.insert("description".to_string(), description.clone()); 712 791 } 713 792 if let Some(avatar) = value.get("avatar") { 714 - obj.insert("avatar".to_string(), avatar.clone()); 793 + let link = avatar 794 + .get("ref") 795 + .and_then(|v| v.get("$link")) 796 + .and_then(|v| v.as_str()) 797 + .unwrap_or(""); 798 + obj.insert( 799 + "avatar".to_string(), 800 + serde_json::json!(app_state.cdn("avatar", did_str, link)), 801 + ); 715 802 } 716 803 if let Some(banner) = value.get("banner") { 717 - obj.insert("banner".to_string(), banner.clone()); 804 + let link = banner 805 + .get("ref") 806 + .and_then(|v| v.get("$link")) 807 + .and_then(|v| v.as_str()) 808 + .unwrap_or(""); 809 + obj.insert( 810 + "banner".to_string(), 811 + serde_json::json!(app_state.cdn("banner", did_str, link)), 812 + ); 718 813 } 719 814 } 720 815 } ··· 758 853 759 854 let limit = params.limit.unwrap_or(50).min(100); 760 855 761 - let record_list = match repo 856 + // Get posts 857 + let posts_list = match repo 762 858 .list_records("app.bsky.feed.post", limit, true, params.cursor.as_deref()) 763 859 .await 764 860 { 765 861 Ok(rl) => rl, 766 862 Err(e) => { 767 - tracing::error!("failed to list records for {}: {e}", did); 863 + tracing::error!("failed to list posts for {}: {e}", did); 768 864 return proxy_request(req).await; 769 865 } 770 866 }; 771 867 772 - let author_profile = match get_profile_internal(&app_state, did.as_str(), None).await { 773 - Ok(p) => p, 868 + // Get reposts 869 + let reposts_list = match repo 870 + .list_records( 871 + "app.bsky.feed.repost", 872 + limit, 873 + true, 874 + params.cursor.as_deref(), 875 + ) 876 + .await 877 + { 878 + Ok(rl) => rl, 774 879 Err(e) => { 775 - tracing::error!("failed to get profile for {}: {e}", did); 880 + tracing::error!("failed to list reposts for {}: {e}", did); 776 881 return proxy_request(req).await; 777 882 } 778 883 }; 779 884 885 + let viewer_did = get_auth_did(&req); 886 + let author_profile = 887 + match get_profile_internal(&app_state, did.as_str(), viewer_did.as_deref()).await { 888 + Ok(p) => p, 889 + Err(e) => { 890 + tracing::error!("failed to get profile for {}: {e}", did); 891 + return proxy_request(req).await; 892 + } 893 + }; 894 + 895 + let mut all_items = Vec::new(); 896 + for rec in posts_list.records { 897 + all_items.push((rec.rkey.as_str().to_string(), "post", rec)); 898 + } 899 + for rec in reposts_list.records { 900 + all_items.push((rec.rkey.as_str().to_string(), "repost", rec)); 901 + } 902 + 903 + // Sort by rkey (roughly chronological) descending 904 + all_items.sort_by(|a, b| b.0.cmp(&a.0)); 905 + all_items.truncate(limit); 906 + 780 907 let mut feed = Vec::new(); 781 - for rec in record_list.records { 782 - let uri = format!( 783 - "at://{}/app.bsky.feed.post/{}", 784 - did.as_str(), 785 - rec.rkey.as_str() 786 - ); 787 - feed.push(serde_json::json!({ 788 - "post": { 789 - "uri": uri, 790 - "cid": rec.cid.to_string(), 791 - "author": author_profile.clone(), 792 - "record": rec.value, 793 - "replyCount": app_state.hydrant.backlinks.count(uri.clone()).source("app.bsky.feed.post").run().await.unwrap_or(0), 794 - "repostCount": app_state.hydrant.backlinks.count(uri.clone()).source("app.bsky.feed.repost").run().await.unwrap_or(0), 795 - "likeCount": app_state.hydrant.backlinks.count(uri.clone()).source("app.bsky.feed.like").run().await.unwrap_or(0), 796 - "indexedAt": chrono::Utc::now().to_rfc3339(), 908 + for (rkey, kind, rec) in &all_items { 909 + if *kind == "post" { 910 + let uri = format!("at://{}/app.bsky.feed.post/{}", did.as_str(), rkey); 911 + let post = match get_post_view(&app_state, &uri, viewer_did.as_deref()).await { 912 + Ok(mut p) => { 913 + p["author"] = author_profile.clone(); 914 + p 915 + } 916 + Err(_) => { 917 + serde_json::json!({ 918 + "uri": uri, 919 + "cid": rec.cid.to_string(), 920 + "author": author_profile.clone(), 921 + "record": rec.value, 922 + "replyCount": app_state.hydrant.backlinks.count(uri.clone()).source("app.bsky.feed.post").run().await.unwrap_or(0), 923 + "repostCount": app_state.hydrant.backlinks.count(uri.clone()).source("app.bsky.feed.repost").run().await.unwrap_or(0), 924 + "likeCount": app_state.hydrant.backlinks.count(uri.clone()).source("app.bsky.feed.like").run().await.unwrap_or(0), 925 + "indexedAt": chrono::Utc::now().to_rfc3339(), 926 + "labels": [], 927 + }) 928 + } 929 + }; 930 + 931 + let val_json = serde_json::to_value(&rec.value).unwrap_or(serde_json::json!({})); 932 + let mut feed_item = serde_json::json!({ "post": post }); 933 + 934 + // Add reply context if it's a reply 935 + if let Some(reply) = val_json.get("reply") { 936 + if let Some(parent_uri) = reply 937 + .get("parent") 938 + .and_then(|p| p.get("uri")) 939 + .and_then(|u| u.as_str()) 940 + { 941 + if let Ok(parent_post) = 942 + get_post_view(&app_state, parent_uri, viewer_did.as_deref()).await 943 + { 944 + let root_uri = reply 945 + .get("root") 946 + .and_then(|p| p.get("uri")) 947 + .and_then(|u| u.as_str()) 948 + .unwrap_or(parent_uri); 949 + if let Ok(root_post) = 950 + get_post_view(&app_state, root_uri, viewer_did.as_deref()).await 951 + { 952 + feed_item["reply"] = serde_json::json!({ 953 + "root": root_post, 954 + "parent": parent_post, 955 + }); 956 + } 957 + } 958 + } 797 959 } 798 - })); 960 + 961 + feed.push(feed_item); 962 + } else if *kind == "repost" { 963 + let val_json = serde_json::to_value(&rec.value).unwrap_or(serde_json::json!({})); 964 + if let Some(subject_uri) = val_json 965 + .get("subject") 966 + .and_then(|s| s.get("uri")) 967 + .and_then(|u| u.as_str()) 968 + { 969 + if let Ok(post_view) = 970 + get_post_view(&app_state, subject_uri, viewer_did.as_deref()).await 971 + { 972 + feed.push(serde_json::json!({ 973 + "post": post_view, 974 + "reason": { 975 + "$type": "app.bsky.feed.defs#reasonRepost", 976 + "by": author_profile.clone(), 977 + "indexedAt": val_json.get("createdAt").and_then(|c| c.as_str()).map(|s| s.to_string()).unwrap_or_else(|| chrono::Utc::now().to_rfc3339()), 978 + } 979 + })); 980 + } 981 + } 982 + } 799 983 } 800 984 985 + let next_cursor = if posts_list.cursor.is_some() || reposts_list.cursor.is_some() { 986 + // This is a naive cursor but works for now as long as we sort by rkey 987 + all_items.last().map(|i| i.0.clone()) 988 + } else { 989 + None 990 + }; 991 + 801 992 Ok(Json(serde_json::json!({ 802 993 "feed": feed, 803 - "cursor": record_list.cursor.map(|c| c.to_string()) 994 + "cursor": next_cursor 804 995 })) 805 996 .into_response()) 806 997 } ··· 853 1044 }; 854 1045 855 1046 let mut likes = Vec::new(); 1047 + let viewer_did = get_auth_did(&req); 856 1048 for bl in backlinks_page.backlinks { 857 1049 let uri = match jacquard_common::types::string::AtUri::new(bl.uri.as_str()) { 858 1050 Ok(uri) => uri, ··· 863 1055 }; 864 1056 let author_ident = uri.authority(); 865 1057 866 - let profile = match get_profile_internal(&app_state, author_ident.as_str(), None).await { 867 - Ok(p) => p, 868 - Err(e) => { 869 - tracing::warn!("failed to get profile for {author_ident}: {e}"); 870 - continue; 871 - } 872 - }; 1058 + let profile = 1059 + match get_profile_internal(&app_state, author_ident.as_str(), viewer_did.as_deref()) 1060 + .await 1061 + { 1062 + Ok(p) => p, 1063 + Err(e) => { 1064 + tracing::warn!("failed to get profile for {author_ident}: {e}"); 1065 + continue; 1066 + } 1067 + }; 873 1068 let repo = match app_state.hydrant.repos.resolve(author_ident).await { 874 1069 Ok(repo) => repo, 875 1070 Err(e) => { ··· 956 1151 }; 957 1152 958 1153 let mut reposted_by = Vec::new(); 1154 + let viewer_did = get_auth_did(&req); 959 1155 for bl in backlinks_page.backlinks { 960 1156 let uri = match jacquard_common::types::string::AtUri::new(bl.uri.as_str()) { 961 1157 Ok(uri) => uri, ··· 965 1161 } 966 1162 }; 967 1163 let author_ident = uri.authority(); 968 - let profile = match get_profile_internal(&app_state, author_ident.as_str(), None).await { 969 - Ok(p) => p, 970 - Err(e) => { 971 - tracing::warn!("failed to get profile for {author_ident}: {e}"); 972 - continue; 973 - } 974 - }; 1164 + let profile = 1165 + match get_profile_internal(&app_state, author_ident.as_str(), viewer_did.as_deref()) 1166 + .await 1167 + { 1168 + Ok(p) => p, 1169 + Err(e) => { 1170 + tracing::warn!("failed to get profile for {author_ident}: {e}"); 1171 + continue; 1172 + } 1173 + }; 975 1174 reposted_by.push(profile); 976 1175 } 977 1176 ··· 992 1191 let prefix1 = format!("{}=", key); 993 1192 let prefix2 = format!("{}[]=", key); 994 1193 for part in query.split('&') { 995 - let mut val = None; 996 - if part.starts_with(&prefix1) { 997 - val = Some(&part[prefix1.len()..]); 998 - } else if part.starts_with(&prefix2) { 999 - val = Some(&part[prefix2.len()..]); 1000 - } 1194 + let val = part 1195 + .strip_prefix(&prefix1) 1196 + .or_else(|| part.strip_prefix(&prefix2)); 1001 1197 if let Some(v) = val { 1002 1198 if let Ok(decoded) = urlencoding::decode(v) { 1003 1199 res.push(decoded.into_owned()); ··· 1018 1214 return proxy_request(req).await; 1019 1215 } 1020 1216 1217 + let viewer_did = get_auth_did(&req); 1021 1218 let mut profiles = Vec::new(); 1022 1219 for actor in actors { 1023 1220 let ident = match AtIdentifier::new(&actor) { ··· 1029 1226 }; 1030 1227 match app_state.hydrant.repos.resolve(&ident).await { 1031 1228 Ok(repo) => { 1032 - match get_profile_internal(&app_state, repo.did.as_str(), None).await { 1229 + match get_profile_internal(&app_state, repo.did.as_str(), viewer_did.as_deref()) 1230 + .await 1231 + { 1033 1232 Ok(profile) => profiles.push(profile), 1034 1233 Err(e) => tracing::warn!("failed to get profile for {}: {e}", repo.did), 1035 1234 } ··· 1052 1251 return proxy_request(req).await; 1053 1252 } 1054 1253 1254 + let viewer_did = get_auth_did(&req); 1055 1255 let mut posts = Vec::new(); 1056 1256 for uri in uris { 1057 - match get_post_view(&app_state, &uri, None).await { 1257 + match get_post_view(&app_state, &uri, viewer_did.as_deref()).await { 1058 1258 Ok(post) => posts.push(post), 1059 1259 Err(e) => tracing::warn!("failed to get post view for {uri}: {e}"), 1060 1260 } ··· 1113 1313 }; 1114 1314 1115 1315 let mut follows = Vec::new(); 1316 + let viewer_did = get_auth_did(&req); 1116 1317 for rec in record_list.records { 1117 1318 let value = serde_json::to_value(&rec.value).unwrap_or(serde_json::json!({})); 1118 1319 if let Some(subject_did) = value.get("subject").and_then(|s| s.as_str()) { 1119 - match get_profile_internal(&app_state, subject_did, None).await { 1320 + match get_profile_internal(&app_state, subject_did, viewer_did.as_deref()).await { 1120 1321 Ok(profile) => follows.push(profile), 1121 1322 Err(e) => tracing::warn!("failed to get profile for {subject_did}: {e}"), 1122 1323 } 1123 1324 } 1124 1325 } 1125 1326 1126 - let subject_profile = match get_profile_internal(&app_state, repo.did.as_str(), None).await { 1127 - Ok(p) => p, 1128 - Err(e) => { 1129 - tracing::error!("failed to get profile for {}: {e}", repo.did); 1130 - return proxy_request(req).await; 1131 - } 1132 - }; 1327 + let subject_profile = 1328 + match get_profile_internal(&app_state, repo.did.as_str(), viewer_did.as_deref()).await { 1329 + Ok(p) => p, 1330 + Err(e) => { 1331 + tracing::error!("failed to get profile for {}: {e}", repo.did); 1332 + return proxy_request(req).await; 1333 + } 1334 + }; 1133 1335 1134 1336 Ok(Json(serde_json::json!({ 1135 1337 "subject": subject_profile, ··· 1185 1387 }; 1186 1388 1187 1389 let mut followers = Vec::new(); 1390 + let viewer_did = get_auth_did(&req); 1188 1391 for bl in backlinks_page.backlinks { 1189 1392 match jacquard_common::types::string::AtUri::new(bl.uri.as_str()) { 1190 1393 Ok(uri) => { 1191 1394 let author_ident = uri.authority(); 1192 - match get_profile_internal(&app_state, author_ident.as_str(), None).await { 1395 + match get_profile_internal(&app_state, author_ident.as_str(), viewer_did.as_deref()) 1396 + .await 1397 + { 1193 1398 Ok(profile) => followers.push(profile), 1194 1399 Err(e) => tracing::warn!("failed to get profile for {author_ident}: {e}"), 1195 1400 } ··· 1198 1403 } 1199 1404 } 1200 1405 1201 - let subject_profile = match get_profile_internal(&app_state, repo.did.as_str(), None).await { 1202 - Ok(p) => p, 1203 - Err(e) => { 1204 - tracing::error!("failed to get profile for {}: {e}", repo.did); 1205 - return proxy_request(req).await; 1206 - } 1207 - }; 1406 + let subject_profile = 1407 + match get_profile_internal(&app_state, repo.did.as_str(), viewer_did.as_deref()).await { 1408 + Ok(p) => p, 1409 + Err(e) => { 1410 + tracing::error!("failed to get profile for {}: {e}", repo.did); 1411 + return proxy_request(req).await; 1412 + } 1413 + }; 1208 1414 1209 1415 let cursor = backlinks_page 1210 1416 .next_cursor ··· 1227 1433 1228 1434 fn get_auth_did(req: &Request) -> Option<String> { 1229 1435 let auth = req.headers().get("authorization")?.to_str().ok()?; 1230 - if !auth.starts_with("Bearer ") { 1231 - return None; 1232 - } 1233 - let token = &auth[7..]; 1436 + let token = auth.strip_prefix("Bearer ")?; 1234 1437 let parts: Vec<&str> = token.split('.').collect(); 1235 1438 if parts.len() != 3 { 1236 1439 return None; ··· 1301 1504 })?; 1302 1505 1303 1506 let key = format!("bookmark:{}:{}", did, payload.uri); 1304 - app_state 1305 - .bookmarks 1306 - .remove(key.as_bytes()) 1307 - .map_err(|e| { 1308 - tracing::error!("failed to remove bookmark: {e}"); 1309 - StatusCode::INTERNAL_SERVER_ERROR 1310 - })?; 1507 + app_state.bookmarks.remove(key.as_bytes()).map_err(|e| { 1508 + tracing::error!("failed to remove bookmark: {e}"); 1509 + StatusCode::INTERNAL_SERVER_ERROR 1510 + })?; 1311 1511 1312 1512 Ok(Json(serde_json::json!({})).into_response()) 1313 1513 } ··· 1371 1571 let mut bookmarks = Vec::new(); 1372 1572 for (key, cid_bytes) in fetched_items { 1373 1573 let key_str = String::from_utf8_lossy(&key); 1374 - let uri_str = &key_str[prefix.len()..]; 1574 + let uri_str = key_str.strip_prefix(&prefix).unwrap_or(&key_str); 1375 1575 let cid_str = String::from_utf8_lossy(&cid_bytes); 1376 1576 1377 1577 match jacquard_common::types::string::AtUri::new(uri_str) { ··· 1381 1581 let rkey = uri.rkey().unwrap().0.as_str(); 1382 1582 1383 1583 match app_state.hydrant.repos.resolve(author_ident).await { 1384 - Ok(repo) => { 1385 - match repo.get_record(collection, rkey).await { 1386 - Ok(Some(record)) => { 1387 - match get_profile_internal(&app_state, repo.did.as_str(), None).await 1388 - { 1389 - Ok(author_profile) => { 1390 - bookmarks.push(serde_json::json!({ 1584 + Ok(repo) => match repo.get_record(collection, rkey).await { 1585 + Ok(Some(record)) => { 1586 + match get_profile_internal(&app_state, repo.did.as_str(), None).await { 1587 + Ok(author_profile) => { 1588 + bookmarks.push(serde_json::json!({ 1391 1589 "uri": uri_str, 1392 1590 "cid": cid_str.to_string(), 1393 1591 "author": author_profile, ··· 1397 1595 "likeCount": app_state.hydrant.backlinks.count(uri_str.to_string()).source("app.bsky.feed.like").run().await.unwrap_or(0), 1398 1596 "indexedAt": chrono::Utc::now().to_rfc3339(), 1399 1597 })); 1400 - } 1401 - Err(e) => { 1402 - tracing::warn!("failed to get profile for {}: {e}", repo.did) 1403 - } 1598 + } 1599 + Err(e) => { 1600 + tracing::warn!("failed to get profile for {}: {e}", repo.did) 1404 1601 } 1405 1602 } 1406 - Ok(None) => tracing::warn!("bookmark record not found: {uri_str}"), 1407 - Err(e) => tracing::warn!("failed to get bookmark record {uri_str}: {e}"), 1408 1603 } 1409 - } 1604 + Ok(None) => tracing::warn!("bookmark record not found: {uri_str}"), 1605 + Err(e) => tracing::warn!("failed to get bookmark record {uri_str}: {e}"), 1606 + }, 1410 1607 Err(e) => tracing::warn!("failed to resolve actor {author_ident}: {e}"), 1411 1608 } 1412 1609 } ··· 1504 1701 let tid = payload.draft.id; 1505 1702 let key = format!("draft:{}:{}", did, tid); 1506 1703 1507 - let existing = app_state 1508 - .drafts 1509 - .get(key.as_bytes()) 1510 - .map_err(|e| { 1511 - tracing::error!("failed to get draft from db: {e}"); 1512 - StatusCode::INTERNAL_SERVER_ERROR 1513 - })?; 1704 + let existing = app_state.drafts.get(key.as_bytes()).map_err(|e| { 1705 + tracing::error!("failed to get draft from db: {e}"); 1706 + StatusCode::INTERNAL_SERVER_ERROR 1707 + })?; 1514 1708 if let Some(existing_bytes) = existing.as_deref() { 1515 1709 match serde_json::from_slice::<serde_json::Value>(&existing_bytes) { 1516 1710 Ok(mut existing_obj) => { ··· 1782 1976 1783 1977 let mut feed = Vec::new(); 1784 1978 let mut next_cursor = None; 1979 + let viewer_did = get_auth_did(&req); 1785 1980 for (did, col, rkey, value) in all_items { 1786 1981 next_cursor = Some(rkey.clone()); 1787 1982 let uri = format!("at://{}/{}/{}", did.as_str(), col, rkey); ··· 1789 1984 let val_json = serde_json::to_value(&value).unwrap_or(serde_json::json!({})); 1790 1985 1791 1986 if col == "app.bsky.feed.post" { 1792 - match get_post_view(&app_state, &uri, None).await { 1987 + match get_post_view(&app_state, &uri, viewer_did.as_deref()).await { 1793 1988 Ok(post_view) => feed.push(serde_json::json!({ "post": post_view })), 1794 1989 Err(e) => tracing::warn!("failed to get post view for {uri}: {e}"), 1795 1990 } ··· 1799 1994 .and_then(|s| s.get("uri")) 1800 1995 .and_then(|u| u.as_str()) 1801 1996 { 1802 - match get_post_view(&app_state, subject_uri, None).await { 1997 + match get_post_view(&app_state, subject_uri, viewer_did.as_deref()).await { 1803 1998 Ok(post_view) => { 1804 - match get_profile_internal(&app_state, did.as_str(), None).await { 1999 + match get_profile_internal(&app_state, did.as_str(), viewer_did.as_deref()) 2000 + .await 2001 + { 1805 2002 Ok(reposter_profile) => { 1806 2003 feed.push(serde_json::json!({ 1807 2004 "post": post_view, ··· 1818 2015 } 1819 2016 } 1820 2017 Err(e) => { 1821 - tracing::warn!("failed to get post view for reposted post {subject_uri}: {e}"); 2018 + tracing::warn!( 2019 + "failed to get post view for reposted post {subject_uri}: {e}" 2020 + ); 1822 2021 } 1823 2022 } 1824 2023 } ··· 1910 2109 Ok(Some(rec)) => rec, 1911 2110 Ok(None) => continue, 1912 2111 Err(e) => { 1913 - tracing::error!("failed to get record {collection}/{rkey} from repo {author_ident}: {e}"); 2112 + tracing::error!( 2113 + "failed to get record {collection}/{rkey} from repo {author_ident}: {e}" 2114 + ); 1914 2115 continue; 1915 2116 } 1916 2117 }; ··· 1937 2138 }); 1938 2139 1939 2140 if is_quote { 1940 - match get_post_view(&app_state, bl.uri.as_str(), None).await { 2141 + let viewer_did = get_auth_did(&req); 2142 + match get_post_view(&app_state, bl.uri.as_str(), viewer_did.as_deref()).await { 1941 2143 Ok(post_view) => { 1942 2144 posts.push(post_view); 1943 2145 found += 1; ··· 1982 2184 })?; 1983 2185 1984 2186 let ident = AtIdentifier::new(&params.actor).map_err(|e| { 1985 - tracing::error!("failed to create AtIdentifier for actor {}: {e}", params.actor); 2187 + tracing::error!( 2188 + "failed to create AtIdentifier for actor {}: {e}", 2189 + params.actor 2190 + ); 1986 2191 StatusCode::BAD_REQUEST 1987 2192 })?; 1988 2193 let repo = match app_state.hydrant.repos.resolve(&ident).await { ··· 2007 2212 }; 2008 2213 2009 2214 let mut feed = Vec::new(); 2215 + let viewer_did = get_auth_did(&req); 2010 2216 for rec in record_list.records { 2011 2217 let value = serde_json::to_value(&rec.value).unwrap_or(serde_json::json!({})); 2012 2218 if let Some(subject_uri) = value ··· 2014 2220 .and_then(|s| s.get("uri")) 2015 2221 .and_then(|u| u.as_str()) 2016 2222 { 2017 - match get_post_view(&app_state, subject_uri, None).await { 2223 + match get_post_view(&app_state, subject_uri, viewer_did.as_deref()).await { 2018 2224 Ok(post_view) => feed.push(serde_json::json!({ "post": post_view })), 2019 - Err(e) => tracing::warn!("failed to get post view for liked post {subject_uri}: {e}"), 2225 + Err(e) => { 2226 + tracing::warn!("failed to get post view for liked post {subject_uri}: {e}") 2227 + } 2020 2228 } 2021 2229 } 2022 2230 } ··· 2050 2258 } 2051 2259 2052 2260 let ident = AtIdentifier::new(&params.actor).map_err(|e| { 2053 - tracing::error!("failed to create AtIdentifier for actor {}: {e}", params.actor); 2261 + tracing::error!( 2262 + "failed to create AtIdentifier for actor {}: {e}", 2263 + params.actor 2264 + ); 2054 2265 StatusCode::BAD_REQUEST 2055 2266 })?; 2056 2267 let repo = match app_state.hydrant.repos.resolve(&ident).await { ··· 2204 2415 cursor = rl.cursor.map(|c| c.to_string()); 2205 2416 } 2206 2417 Err(e) => { 2207 - tracing::error!("failed to list follows for auth user {}: {e}", auth_repo.did); 2418 + tracing::error!( 2419 + "failed to list follows for auth user {}: {e}", 2420 + auth_repo.did 2421 + ); 2208 2422 break; 2209 2423 } 2210 2424 } 2211 2425 } 2212 2426 2213 2427 let ident = AtIdentifier::new(&params.actor).map_err(|e| { 2214 - tracing::error!("failed to create AtIdentifier for actor {}: {e}", params.actor); 2428 + tracing::error!( 2429 + "failed to create AtIdentifier for actor {}: {e}", 2430 + params.actor 2431 + ); 2215 2432 StatusCode::BAD_REQUEST 2216 2433 })?; 2217 2434 let repo = match app_state.hydrant.repos.resolve(&ident).await { ··· 2346 2563 if let Some(subject_did) = value.get("subject").and_then(|s| s.as_str()) { 2347 2564 match get_profile_internal(&app_state, subject_did, None).await { 2348 2565 Ok(profile) => blocks.push(profile), 2349 - Err(e) => tracing::warn!("failed to get profile for blocked actor {subject_did}: {e}"), 2566 + Err(e) => { 2567 + tracing::warn!("failed to get profile for blocked actor {subject_did}: {e}") 2568 + } 2350 2569 } 2351 2570 } 2352 2571 } ··· 2409 2628 } 2410 2629 }; 2411 2630 2412 - let author_profile = match get_profile_internal(&app_state, repo.did.as_str(), None).await { 2413 - Ok(p) => p, 2414 - Err(e) => { 2415 - tracing::error!("failed to get author profile for {}: {e}", repo.did); 2416 - return proxy_request(req).await; 2417 - } 2418 - }; 2631 + let viewer_did = get_auth_did(&req); 2632 + let author_profile = 2633 + match get_profile_internal(&app_state, repo.did.as_str(), viewer_did.as_deref()).await { 2634 + Ok(p) => p, 2635 + Err(e) => { 2636 + tracing::error!("failed to get author profile for {}: {e}", repo.did); 2637 + return proxy_request(req).await; 2638 + } 2639 + }; 2419 2640 2420 2641 let val_json = serde_json::to_value(&record.value).unwrap_or(serde_json::json!({})); 2421 2642 ··· 2524 2745 })?; 2525 2746 2526 2747 let ident = AtIdentifier::new(&params.actor).map_err(|e| { 2527 - tracing::error!("failed to create AtIdentifier for actor {}: {e}", params.actor); 2748 + tracing::error!( 2749 + "failed to create AtIdentifier for actor {}: {e}", 2750 + params.actor 2751 + ); 2528 2752 StatusCode::BAD_REQUEST 2529 2753 })?; 2530 2754 let repo = match app_state.hydrant.repos.resolve(&ident).await { ··· 2535 2759 } 2536 2760 }; 2537 2761 2538 - let author_profile = match get_profile_internal(&app_state, repo.did.as_str(), None).await { 2539 - Ok(p) => p, 2540 - Err(e) => { 2541 - tracing::error!("failed to get author profile for {}: {e}", repo.did); 2542 - return proxy_request(req).await; 2543 - } 2544 - }; 2762 + let viewer_did = get_auth_did(&req); 2763 + let author_profile = 2764 + match get_profile_internal(&app_state, repo.did.as_str(), viewer_did.as_deref()).await { 2765 + Ok(p) => p, 2766 + Err(e) => { 2767 + tracing::error!("failed to get author profile for {}: {e}", repo.did); 2768 + return proxy_request(req).await; 2769 + } 2770 + }; 2545 2771 2546 2772 let limit = params.limit.unwrap_or(50).min(100); 2547 2773 let record_list = match repo ··· 2589 2815 })?; 2590 2816 2591 2817 let ident = AtIdentifier::new(&params.actor).map_err(|e| { 2592 - tracing::error!("failed to create AtIdentifier for actor {}: {e}", params.actor); 2818 + tracing::error!( 2819 + "failed to create AtIdentifier for actor {}: {e}", 2820 + params.actor 2821 + ); 2593 2822 StatusCode::BAD_REQUEST 2594 2823 })?; 2595 2824 let repo = match app_state.hydrant.repos.resolve(&ident).await { ··· 2612 2841 } 2613 2842 }; 2614 2843 2615 - let mut lists: Vec<serde_json::Value> = Vec::new(); 2844 + let lists: Vec<serde_json::Value> = Vec::new(); 2616 2845 for rec in record_list.records { 2617 - let uri = format!( 2846 + let _uri = format!( 2618 2847 "at://{}/app.bsky.graph.list/{}", 2619 2848 repo.did.as_str(), 2620 2849 rec.rkey.as_str() ··· 2642 2871 })?; 2643 2872 2644 2873 let ident = AtIdentifier::new(&params.actor).map_err(|e| { 2645 - tracing::error!("failed to create AtIdentifier for actor {}: {e}", params.actor); 2874 + tracing::error!( 2875 + "failed to create AtIdentifier for actor {}: {e}", 2876 + params.actor 2877 + ); 2646 2878 StatusCode::BAD_REQUEST 2647 2879 })?; 2648 2880 let repo = match app_state.hydrant.repos.resolve(&ident).await { ··· 2653 2885 } 2654 2886 }; 2655 2887 2656 - let author_profile = match get_profile_internal(&app_state, repo.did.as_str(), None).await { 2657 - Ok(p) => p, 2658 - Err(e) => { 2659 - tracing::error!("failed to get author profile for {}: {e}", repo.did); 2660 - return proxy_request(req).await; 2661 - } 2662 - }; 2888 + let viewer_did = get_auth_did(&req); 2889 + let author_profile = 2890 + match get_profile_internal(&app_state, repo.did.as_str(), viewer_did.as_deref()).await { 2891 + Ok(p) => p, 2892 + Err(e) => { 2893 + tracing::error!("failed to get author profile for {}: {e}", repo.did); 2894 + return proxy_request(req).await; 2895 + } 2896 + }; 2663 2897 2664 2898 let limit = params.limit.unwrap_or(50).min(100); 2665 2899 let record_list = match repo ··· 2756 2990 } 2757 2991 }; 2758 2992 2759 - let author_profile = match get_profile_internal(&app_state, repo.did.as_str(), None).await { 2760 - Ok(p) => p, 2761 - Err(e) => { 2762 - tracing::error!("failed to get author profile for {}: {e}", repo.did); 2763 - return proxy_request(req).await; 2764 - } 2765 - }; 2993 + let viewer_did = get_auth_did(&req); 2994 + let author_profile = 2995 + match get_profile_internal(&app_state, repo.did.as_str(), viewer_did.as_deref()).await { 2996 + Ok(p) => p, 2997 + Err(e) => { 2998 + tracing::error!("failed to get author profile for {}: {e}", repo.did); 2999 + return proxy_request(req).await; 3000 + } 3001 + }; 2766 3002 2767 3003 let val_json = serde_json::to_value(&record.value).unwrap_or(serde_json::json!({})); 2768 3004 ··· 2798 3034 } 2799 3035 2800 3036 let mut views = Vec::new(); 3037 + let viewer_did = get_auth_did(&req); 2801 3038 for feed_uri in feeds { 2802 3039 let uri = match jacquard_common::types::string::AtUri::new(&feed_uri) { 2803 3040 Ok(u) => u, ··· 2810 3047 let rkey = uri.rkey().unwrap().0.as_str().to_string(); 2811 3048 2812 3049 match app_state.hydrant.repos.resolve(author_ident).await { 2813 - Ok(repo) => { 2814 - match repo.get_record("app.bsky.feed.generator", &rkey).await { 2815 - Ok(Some(record)) => { 2816 - match get_profile_internal(&app_state, repo.did.as_str(), None).await { 2817 - Ok(author_profile) => { 2818 - let val_json = serde_json::to_value(&record.value) 2819 - .unwrap_or(serde_json::json!({})); 2820 - views.push(serde_json::json!({ 3050 + Ok(repo) => match repo.get_record("app.bsky.feed.generator", &rkey).await { 3051 + Ok(Some(record)) => { 3052 + match get_profile_internal(&app_state, repo.did.as_str(), viewer_did.as_deref()) 3053 + .await 3054 + { 3055 + Ok(author_profile) => { 3056 + let val_json = serde_json::to_value(&record.value) 3057 + .unwrap_or(serde_json::json!({})); 3058 + views.push(serde_json::json!({ 2821 3059 "uri": feed_uri, 2822 3060 "cid": record.cid.to_string(), 2823 3061 "did": val_json.get("did").and_then(|v| v.as_str()).unwrap_or(""), ··· 2828 3066 "likeCount": app_state.hydrant.backlinks.count(feed_uri.clone()).source("app.bsky.feed.like").run().await.unwrap_or(0), 2829 3067 "indexedAt": chrono::Utc::now().to_rfc3339(), 2830 3068 })); 2831 - } 2832 - Err(e) => { 2833 - tracing::warn!("failed to get author profile for {}: {e}", repo.did) 2834 - } 3069 + } 3070 + Err(e) => { 3071 + tracing::warn!("failed to get author profile for {}: {e}", repo.did) 2835 3072 } 2836 3073 } 2837 - Ok(None) => tracing::warn!("feed generator record not found: {author_ident}/{rkey}"), 2838 - Err(e) => tracing::warn!( 2839 - "failed to get feed generator record {author_ident}/{rkey}: {e}" 2840 - ), 3074 + } 3075 + Ok(None) => { 3076 + tracing::warn!("feed generator record not found: {author_ident}/{rkey}") 3077 + } 3078 + Err(e) => { 3079 + tracing::warn!("failed to get feed generator record {author_ident}/{rkey}: {e}") 2841 3080 } 2842 - } 3081 + }, 2843 3082 Err(e) => tracing::warn!("failed to resolve repo for {author_ident}: {e}"), 2844 3083 } 2845 3084 } ··· 3032 3271 let mut next_cursor = None; 3033 3272 for key in fetched_keys { 3034 3273 let key_str = String::from_utf8_lossy(&key); 3035 - let muted_did = &key_str[prefix.len()..]; 3274 + let muted_did = key_str.strip_prefix(&prefix).unwrap_or(&key_str); 3036 3275 match get_profile_internal(&app_state, muted_did, None).await { 3037 3276 Ok(profile) => mutes.push(profile), 3038 3277 Err(e) => tracing::warn!("failed to get profile for muted actor {muted_did}: {e}"), ··· 3079 3318 Some(subj_uri.to_string()), 3080 3319 )); 3081 3320 } 3082 - Err(e) => tracing::warn!("failed to parse notification like uri {subj_uri}: {e}"), 3321 + Err(e) => { 3322 + tracing::warn!("failed to parse notification like uri {subj_uri}: {e}") 3323 + } 3083 3324 } 3084 3325 } 3085 3326 } ··· 3097 3338 Some(subj_uri.to_string()), 3098 3339 )); 3099 3340 } 3100 - Err(e) => tracing::warn!("failed to parse notification repost uri {subj_uri}: {e}"), 3341 + Err(e) => tracing::warn!( 3342 + "failed to parse notification repost uri {subj_uri}: {e}" 3343 + ), 3101 3344 } 3102 3345 } 3103 3346 } ··· 3121 3364 Some(parent_uri.to_string()), 3122 3365 )); 3123 3366 } 3124 - Err(e) => tracing::warn!("failed to parse notification reply uri {parent_uri}: {e}"), 3367 + Err(e) => tracing::warn!( 3368 + "failed to parse notification reply uri {parent_uri}: {e}" 3369 + ), 3125 3370 } 3126 3371 } 3127 3372 ··· 3172 3417 Some(qu.to_string()), 3173 3418 )); 3174 3419 } 3175 - Err(e) => tracing::warn!("failed to parse notification quote uri {qu}: {e}"), 3420 + Err(e) => tracing::warn!( 3421 + "failed to parse notification quote uri {qu}: {e}" 3422 + ), 3176 3423 } 3177 3424 } 3178 3425 } ··· 3307 3554 notif["author"] = author_profile; 3308 3555 } 3309 3556 Err(e) => { 3310 - tracing::warn!("failed to get author profile for notification from {author_did}: {e}"); 3557 + tracing::warn!( 3558 + "failed to get author profile for notification from {author_did}: {e}" 3559 + ); 3311 3560 continue; 3312 3561 } 3313 3562 } ··· 3374 3623 3375 3624 #[derive(Deserialize)] 3376 3625 struct UpdateSeenReq { 3377 - seenAt: String, 3626 + #[serde(rename = "seenAt")] 3627 + seen_at: String, 3378 3628 } 3379 3629 3380 3630 async fn update_seen( ··· 3396 3646 let seen_key = format!("seen:{}", did); 3397 3647 app_state 3398 3648 .seen 3399 - .insert(seen_key.as_bytes(), payload.seenAt.as_bytes()) 3649 + .insert(seen_key.as_bytes(), payload.seen_at.as_bytes()) 3400 3650 .map_err(|e| { 3401 3651 tracing::error!("failed to insert seen: {e}"); 3402 3652 StatusCode::INTERNAL_SERVER_ERROR ··· 3463 3713 return Err(StatusCode::BAD_REQUEST); 3464 3714 }; 3465 3715 3466 - let service_did_parsed = jacquard_common::types::string::Did::new(service_did).map_err(|e| { 3467 - tracing::error!("failed to parse service did {service_did}: {e}"); 3468 - StatusCode::BAD_REQUEST 3469 - })?; 3716 + let service_did_parsed = 3717 + jacquard_common::types::string::Did::new(service_did).map_err(|e| { 3718 + tracing::error!("failed to parse service did {service_did}: {e}"); 3719 + StatusCode::BAD_REQUEST 3720 + })?; 3470 3721 3471 3722 let (doc_data, _) = match app_state 3472 3723 .hydrant