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 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 32 let placeholder_post = PostView { 33 uri: like.record.subject.uri.clone(), 34 cid: like.record.subject.cid.clone(), 35 author: crate::api::read_after_write::AuthorView { 36 did: String::new(), 37 handle: String::new(), 38 display_name: None, 39 avatar: None, 40 extra: HashMap::new(), 41 }, 42 record: Value::Null, 43 indexed_at: like.indexed_at.to_rfc3339(), 44 embed: None, 45 reply_count: 0, 46 repost_count: 0, 47 like_count: 0, 48 quote_count: 0, 49 extra: HashMap::new(), 50 }; 51 52 feed.insert( 53 idx, 54 FeedViewPost { 55 post: placeholder_post, 56 reply: None, 57 reason: None, 58 feed_context: None, 59 extra: HashMap::new(), 60 }, 61 ); 62 } 63} 64 65pub async fn get_actor_likes( 66 State(state): State<AppState>, 67 headers: axum::http::HeaderMap, 68 Query(params): Query<GetActorLikesParams>, 69) -> Response { 70 let auth_header = headers.get("Authorization").and_then(|h| h.to_str().ok()); 71 72 let auth_did = if let Some(h) = auth_header { 73 if let Some(token) = crate::auth::extract_bearer_token_from_header(Some(h)) { 74 match crate::auth::validate_bearer_token(&state.db, &token).await { 75 Ok(user) => Some(user.did), 76 Err(_) => None, 77 } 78 } else { 79 None 80 } 81 } else { 82 None 83 }; 84 85 let mut query_params = HashMap::new(); 86 query_params.insert("actor".to_string(), params.actor.clone()); 87 if let Some(limit) = params.limit { 88 query_params.insert("limit".to_string(), limit.to_string()); 89 } 90 if let Some(cursor) = &params.cursor { 91 query_params.insert("cursor".to_string(), cursor.clone()); 92 } 93 94 let proxy_result = 95 match proxy_to_appview("app.bsky.feed.getActorLikes", &query_params, auth_header).await { 96 Ok(r) => r, 97 Err(e) => return e, 98 }; 99 100 if !proxy_result.status.is_success() { 101 return (proxy_result.status, proxy_result.body).into_response(); 102 } 103 104 let rev = match extract_repo_rev(&proxy_result.headers) { 105 Some(r) => r, 106 None => return (proxy_result.status, proxy_result.body).into_response(), 107 }; 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.status, proxy_result.body).into_response(); 114 } 115 }; 116 117 let requester_did = match auth_did { 118 Some(d) => d, 119 None => return (StatusCode::OK, Json(feed_output)).into_response(), 120 }; 121 122 let actor_did = if params.actor.starts_with("did:") { 123 params.actor.clone() 124 } else { 125 match sqlx::query_scalar!("SELECT did FROM users WHERE handle = $1", params.actor) 126 .fetch_optional(&state.db) 127 .await 128 { 129 Ok(Some(did)) => did, 130 Ok(None) => return (StatusCode::OK, Json(feed_output)).into_response(), 131 Err(e) => { 132 warn!("Database error resolving actor handle: {:?}", e); 133 return (proxy_result.status, proxy_result.body).into_response(); 134 } 135 } 136 }; 137 138 if actor_did != requester_did { 139 return (StatusCode::OK, Json(feed_output)).into_response(); 140 } 141 142 let local_records = match get_records_since_rev(&state, &requester_did, &rev).await { 143 Ok(r) => r, 144 Err(e) => { 145 warn!("Failed to get local records: {}", e); 146 return (proxy_result.status, proxy_result.body).into_response(); 147 } 148 }; 149 150 if local_records.likes.is_empty() { 151 return (StatusCode::OK, Json(feed_output)).into_response(); 152 } 153 154 insert_likes_into_feed(&mut feed_output.feed, &local_records.likes); 155 156 let lag = get_local_lag(&local_records); 157 format_munged_response(feed_output, lag) 158}