this repo has no description
1use crate::api::read_after_write::{
2 extract_repo_rev, format_local_post, format_munged_response, get_local_lag,
3 get_records_since_rev, insert_posts_into_feed, proxy_to_appview, FeedOutput, FeedViewPost,
4 ProfileRecord, RecordDescript,
5};
6use crate::state::AppState;
7use axum::{
8 extract::{Query, State},
9 http::StatusCode,
10 response::{IntoResponse, Response},
11 Json,
12};
13use serde::Deserialize;
14use std::collections::HashMap;
15use tracing::warn;
16
17#[derive(Deserialize)]
18pub struct GetAuthorFeedParams {
19 pub actor: String,
20 pub limit: Option<u32>,
21 pub cursor: Option<String>,
22 pub filter: Option<String>,
23 #[serde(rename = "includePins")]
24 pub include_pins: Option<bool>,
25}
26
27fn update_author_profile_in_feed(
28 feed: &mut [FeedViewPost],
29 author_did: &str,
30 local_profile: &RecordDescript<ProfileRecord>,
31) {
32 for item in feed.iter_mut() {
33 if item.post.author.did == author_did {
34 if let Some(ref display_name) = local_profile.record.display_name {
35 item.post.author.display_name = Some(display_name.clone());
36 }
37 }
38 }
39}
40
41pub async fn get_author_feed(
42 State(state): State<AppState>,
43 headers: axum::http::HeaderMap,
44 Query(params): Query<GetAuthorFeedParams>,
45) -> Response {
46 let auth_header = headers.get("Authorization").and_then(|h| h.to_str().ok());
47
48 let auth_did = if let Some(h) = auth_header {
49 if let Some(token) = crate::auth::extract_bearer_token_from_header(Some(h)) {
50 match crate::auth::validate_bearer_token(&state.db, &token).await {
51 Ok(user) => Some(user.did),
52 Err(_) => None,
53 }
54 } else {
55 None
56 }
57 } else {
58 None
59 };
60
61 let mut query_params = HashMap::new();
62 query_params.insert("actor".to_string(), params.actor.clone());
63 if let Some(limit) = params.limit {
64 query_params.insert("limit".to_string(), limit.to_string());
65 }
66 if let Some(cursor) = ¶ms.cursor {
67 query_params.insert("cursor".to_string(), cursor.clone());
68 }
69 if let Some(filter) = ¶ms.filter {
70 query_params.insert("filter".to_string(), filter.clone());
71 }
72 if let Some(include_pins) = params.include_pins {
73 query_params.insert("includePins".to_string(), include_pins.to_string());
74 }
75
76 let proxy_result =
77 match proxy_to_appview("app.bsky.feed.getAuthorFeed", &query_params, auth_header).await {
78 Ok(r) => r,
79 Err(e) => return e,
80 };
81
82 if !proxy_result.status.is_success() {
83 return (proxy_result.status, proxy_result.body).into_response();
84 }
85
86 let rev = match extract_repo_rev(&proxy_result.headers) {
87 Some(r) => r,
88 None => return (proxy_result.status, proxy_result.body).into_response(),
89 };
90
91 let mut feed_output: FeedOutput = match serde_json::from_slice(&proxy_result.body) {
92 Ok(f) => f,
93 Err(e) => {
94 warn!("Failed to parse author feed response: {:?}", e);
95 return (proxy_result.status, proxy_result.body).into_response();
96 }
97 };
98
99 let requester_did = match auth_did {
100 Some(d) => d,
101 None => return (StatusCode::OK, Json(feed_output)).into_response(),
102 };
103
104 let actor_did = if params.actor.starts_with("did:") {
105 params.actor.clone()
106 } else {
107 match sqlx::query_scalar!("SELECT did FROM users WHERE handle = $1", params.actor)
108 .fetch_optional(&state.db)
109 .await
110 {
111 Ok(Some(did)) => did,
112 Ok(None) => return (StatusCode::OK, Json(feed_output)).into_response(),
113 Err(e) => {
114 warn!("Database error resolving actor handle: {:?}", e);
115 return (proxy_result.status, proxy_result.body).into_response();
116 }
117 }
118 };
119
120 if actor_did != requester_did {
121 return (StatusCode::OK, Json(feed_output)).into_response();
122 }
123
124 let local_records = match get_records_since_rev(&state, &requester_did, &rev).await {
125 Ok(r) => r,
126 Err(e) => {
127 warn!("Failed to get local records: {}", e);
128 return (proxy_result.status, proxy_result.body).into_response();
129 }
130 };
131
132 if local_records.count == 0 {
133 return (StatusCode::OK, Json(feed_output)).into_response();
134 }
135
136 let handle = match sqlx::query_scalar!("SELECT handle FROM users WHERE did = $1", requester_did)
137 .fetch_optional(&state.db)
138 .await
139 {
140 Ok(Some(h)) => h,
141 Ok(None) => requester_did.clone(),
142 Err(e) => {
143 warn!("Database error fetching handle: {:?}", e);
144 requester_did.clone()
145 }
146 };
147
148 if let Some(ref local_profile) = local_records.profile {
149 update_author_profile_in_feed(&mut feed_output.feed, &requester_did, local_profile);
150 }
151
152 let local_posts: Vec<_> = local_records
153 .posts
154 .iter()
155 .map(|p| {
156 format_local_post(
157 p,
158 &requester_did,
159 &handle,
160 local_records.profile.as_ref(),
161 )
162 })
163 .collect();
164
165 insert_posts_into_feed(&mut feed_output.feed, local_posts);
166
167 let lag = get_local_lag(&local_records);
168 format_munged_response(feed_output, lag)
169}