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) = ¶ms.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}