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) = ¶ms.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(¶ms.actor)
116 } else {
117 ¶ms.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}