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