Something went wrong. Try again.
A personal app view to see Bsky posts of your followers (for when their app view goes down)
Something went wrong. Try again.
7.3 kB · 230 lines
Rust
at test
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231use crate::external_data;use crate::server::AppState;use crate::server::XrpcErrorResponse;use crate::store;use crate::xrpc::get_blob_uri;
use axum::{Json, extract::State};use jacquard::deps::fluent_uri::Uri;use jacquard::types::string::AtIdentifier;use jacquard::types::string::RecordKey;use jacquard::xrpc::XrpcExt;use jacquard_api::app_bsky::actor::get_profile::GetProfileOutput;use jacquard_api::app_bsky::actor::get_profile::GetProfileRequest;use jacquard_api::app_bsky::actor::get_profiles::GetProfilesOutput;use jacquard_api::app_bsky::actor::get_profiles::GetProfilesRequest;use jacquard_api::app_bsky::actor::profile::Profile;use jacquard_api::app_bsky::actor::{ProfileViewBasic, ProfileViewDetailed};use jacquard_api::com_atproto::repo::get_record::GetRecord;use jacquard_axum::ExtractXrpc;use jacquard_axum::service_auth::ExtractOptionalServiceAuth;use jacquard_common::IntoStatic;use jacquard_common::types::handle;use jacquard_identity::JacquardResolver;use jacquard_identity::resolver::IdentityResolver;use serde::Deserialize;use std::str::FromStr;
pub async fn app_bsky_actor_get_profile( State(state): State<AppState>, ExtractOptionalServiceAuth(_auth): ExtractOptionalServiceAuth, ExtractXrpc(req): ExtractXrpc<GetProfileRequest>,) -> Result<Json<GetProfileOutput<'static>>, XrpcErrorResponse> { let profile = get_profile(state.resolver, state.pool, req.actor) .await .map_err(|err| err)?;
Ok(Json(GetProfileOutput { value: profile, extra_data: None, }))}
pub async fn app_bsky_actor_get_profiles( State(state): State<AppState>, ExtractOptionalServiceAuth(_auth): ExtractOptionalServiceAuth, ExtractXrpc(req): ExtractXrpc<GetProfilesRequest>,) -> Result<Json<GetProfilesOutput<'static>>, XrpcErrorResponse> { let mut results = Vec::new();
for actor in req.actors { let profile = get_profile(state.resolver.clone(), state.pool.clone(), actor) .await .map_err(|err| err)?;
results.push(profile); }
Ok(Json(GetProfilesOutput { profiles: results, extra_data: None, }))}
async fn get_profile<'a>( resolver: JacquardResolver, pool: sqlx::SqlitePool, actor: AtIdentifier<'a>,) -> Result<ProfileViewDetailed<'static>, XrpcErrorResponse> { let did = match actor { AtIdentifier::Did(did) => did.into_static(), AtIdentifier::Handle(h) => resolver.resolve_handle(&h).await.map_err(|err| { println!("error resolving handle: {err}"); XrpcErrorResponse::internal_server_error() })?, };
let did_doc = external_data::get_did_doc(resolver.clone(), &pool, &did.to_string()) .await .map_err(|err| { println!("getting did doc {err}"); XrpcErrorResponse::internal_server_error() })?;
let handle = handle::Handle::from_str(&did_doc.handle).map_err(|err| { println!("parsing handle {err}"); XrpcErrorResponse::internal_server_error() })?;
let profile = get_profile_from_pds(pool.clone(), did.to_string(), did_doc.pds_endpoint.clone()) .await .map_err(|err| { println!("getting profile: {err}"); XrpcErrorResponse::internal_server_error() })?;
let avatar_uri = get_blob_uri( profile.avatar, did.to_string(), did_doc.pds_endpoint.clone(), );
let banner_uri = get_blob_uri( profile.banner, did.to_string(), did_doc.pds_endpoint.clone(), );
let follows = store::get_follows(&pool).await;
Ok(ProfileViewDetailed { associated: None, avatar: avatar_uri, banner: banner_uri, created_at: None, did: did, debug: None, description: profile.description, display_name: profile.display_name, followers_count: None, // TODO: probably use constellation for backlink follows follows_count: Some(follows.len() as i64), handle: handle, indexed_at: None, joined_via_starter_pack: None, labels: None, pinned_post: profile.pinned_post, posts_count: None, pronouns: profile.pronouns, status: None, verification: None, viewer: None, website: profile.website, extra_data: None, })}
pub async fn get_profile_basic<'a>( resolver: JacquardResolver, pool: sqlx::SqlitePool, did: String,) -> Result<ProfileViewBasic<'static>, XrpcErrorResponse> { let did_doc = external_data::get_did_doc(resolver.clone(), &pool, &did) .await .map_err(|err| { println!("getting did doc {err}"); XrpcErrorResponse::internal_server_error() })?;
let at_id = AtIdentifier::from_str(&did_doc.did).map_err(|err| { println!("parsing did {err}"); XrpcErrorResponse::internal_server_error() })?;
let did = match at_id { AtIdentifier::Did(did) => did, AtIdentifier::Handle(h) => resolver.resolve_handle(&h).await.map_err(|err| { println!("error resolving handle: {err}"); XrpcErrorResponse::internal_server_error() //TODO: ignore this? })?, };
let profile = get_profile_from_pds(pool, did.to_string(), did_doc.pds_endpoint.clone()) .await .map_err(|err| { println!("getting profile--: {err}"); XrpcErrorResponse::internal_server_error() })?;
let avatar_uri = get_blob_uri(profile.avatar, did_doc.did, did_doc.pds_endpoint.clone());
let handle = handle::Handle::from_str(&did_doc.handle).map_err(|err| { println!("parsing handle {err}"); XrpcErrorResponse::internal_server_error() })?;
Ok(ProfileViewBasic { avatar: avatar_uri, created_at: None, did: did, display_name: profile.display_name, handle: handle, labels: None, extra_data: None, pronouns: None, associated: None, debug: None, status: None, verification: None, viewer: None, })}
async fn get_profile_from_pds( pool: sqlx::SqlitePool, did: String, pds_endpoint: String,) -> anyhow::Result<Profile<'static>> { let profile_blob = store::get_profile_blob_did(&pool, &did).await;
if let Some(blob) = profile_blob { let profile: Profile<'_> = serde_json::from_slice(&blob)?; return Ok(profile.into_static()); }
let rkey = RecordKey::from_str(&"self".to_string()).map_err(|err| err)?; let req = GetRecord::new() .repo(did.clone()) .collection("app.bsky.actor.profile".to_string()) .rkey(rkey) .build();
let http = reqwest::Client::new(); let base = Uri::parse(pds_endpoint) .to_owned() .map_err(|err| anyhow::Error::msg(err.1))?;
let response = http.xrpc(base.to_owned()).send(&req).await?;
let record = response.into_output()?;
let raw_json = serde_json::to_vec(&record.value)?;
store::insert_profile(&pool, did.clone(), raw_json).await;
let profile: Profile = Profile::deserialize(record.value).map_err(|err| { println!("decoding profile: {err}"); err })?;
Ok(profile)}