this repo has no description
1use crate::api::read_after_write::{ 2 FeedOutput, FeedViewPost, LikeRecord, PostView, RecordDescript, extract_repo_rev, 3 format_munged_response, get_local_lag, get_records_since_rev, proxy_to_appview_via_registry, 4}; 5use crate::state::AppState; 6use axum::{ 7 Json, 8 extract::{Query, State}, 9 http::StatusCode, 10 response::{IntoResponse, Response}, 11}; 12use serde::Deserialize; 13use serde_json::Value; 14use std::collections::HashMap; 15use tracing::warn; 16 17#[derive(Deserialize)] 18pub struct GetActorLikesParams { 19 pub actor: String, 20 pub limit: Option<u32>, 21 pub cursor: Option<String>, 22} 23 24fn insert_likes_into_feed(feed: &mut Vec<FeedViewPost>, likes: &[RecordDescript<LikeRecord>]) { 25 for like in likes { 26 let like_time = &like.indexed_at.to_rfc3339(); 27 let idx = feed 28 .iter() 29 .position(|fi| &fi.post.indexed_at < like_time) 30 .unwrap_or(feed.len()); 31 let placeholder_post = PostView { 32 uri: like.record.subject.uri.clone(), 33 cid: like.record.subject.cid.clone(), 34 author: crate::api::read_after_write::AuthorView { 35 did: String::new(), 36 handle: String::new(), 37 display_name: None, 38 avatar: None, 39 extra: HashMap::new(), 40 }, 41 record: Value::Null, 42 indexed_at: like.indexed_at.to_rfc3339(), 43 embed: None, 44 reply_count: 0, 45 repost_count: 0, 46 like_count: 0, 47 quote_count: 0, 48 extra: HashMap::new(), 49 }; 50 feed.insert( 51 idx, 52 FeedViewPost { 53 post: placeholder_post, 54 reply: None, 55 reason: None, 56 feed_context: None, 57 extra: HashMap::new(), 58 }, 59 ); 60 } 61} 62 63pub async fn get_actor_likes( 64 State(state): State<AppState>, 65 headers: axum::http::HeaderMap, 66 Query(params): Query<GetActorLikesParams>, 67) -> Response { 68 let auth_header = headers.get("Authorization").and_then(|h| h.to_str().ok()); 69 let auth_user = if let Some(h) = auth_header { 70 if let Some(token) = crate::auth::extract_bearer_token_from_header(Some(h)) { 71 crate::auth::validate_bearer_token(&state.db, &token) 72 .await 73 .ok() 74 } else { 75 None 76 } 77 } else { 78 None 79 }; 80 let auth_did = auth_user.as_ref().map(|u| u.did.clone()); 81 let auth_key_bytes = auth_user.as_ref().and_then(|u| u.key_bytes.clone()); 82 let mut query_params = HashMap::new(); 83 query_params.insert("actor".to_string(), params.actor.clone()); 84 if let Some(limit) = params.limit { 85 query_params.insert("limit".to_string(), limit.to_string()); 86 } 87 if let Some(cursor) = &params.cursor { 88 query_params.insert("cursor".to_string(), cursor.clone()); 89 } 90 let proxy_result = match proxy_to_appview_via_registry( 91 &state, 92 "app.bsky.feed.getActorLikes", 93 &query_params, 94 auth_did.as_deref().unwrap_or(""), 95 auth_key_bytes.as_deref(), 96 ) 97 .await 98 { 99 Ok(r) => r, 100 Err(e) => return e, 101 }; 102 if !proxy_result.status.is_success() { 103 return proxy_result.into_response(); 104 } 105 let rev = match extract_repo_rev(&proxy_result.headers) { 106 Some(r) => r, 107 None => return proxy_result.into_response(), 108 }; 109 let mut feed_output: FeedOutput = match serde_json::from_slice(&proxy_result.body) { 110 Ok(f) => f, 111 Err(e) => { 112 warn!("Failed to parse actor likes response: {:?}", e); 113 return proxy_result.into_response(); 114 } 115 }; 116 let requester_did = match &auth_did { 117 Some(d) => d.clone(), 118 None => return (StatusCode::OK, Json(feed_output)).into_response(), 119 }; 120 let actor_did = if params.actor.starts_with("did:") { 121 params.actor.clone() 122 } else { 123 let hostname = std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| "localhost".to_string()); 124 let suffix = format!(".{}", hostname); 125 let short_handle = if params.actor.ends_with(&suffix) { 126 params.actor.strip_suffix(&suffix).unwrap_or(&params.actor) 127 } else { 128 &params.actor 129 }; 130 match sqlx::query_scalar!("SELECT did FROM users WHERE handle = $1", short_handle) 131 .fetch_optional(&state.db) 132 .await 133 { 134 Ok(Some(did)) => did, 135 Ok(None) => return (StatusCode::OK, Json(feed_output)).into_response(), 136 Err(e) => { 137 warn!("Database error resolving actor handle: {:?}", e); 138 return proxy_result.into_response(); 139 } 140 } 141 }; 142 if actor_did != requester_did { 143 return (StatusCode::OK, Json(feed_output)).into_response(); 144 } 145 let local_records = match get_records_since_rev(&state, &requester_did, &rev).await { 146 Ok(r) => r, 147 Err(e) => { 148 warn!("Failed to get local records: {}", e); 149 return proxy_result.into_response(); 150 } 151 }; 152 if local_records.likes.is_empty() { 153 return (StatusCode::OK, Json(feed_output)).into_response(); 154 } 155 insert_likes_into_feed(&mut feed_output.feed, &local_records.likes); 156 let lag = get_local_lag(&local_records); 157 format_munged_response(feed_output, lag) 158}