use bobbin_edge_index::EdgeStore; use bobbin_types::{ ids::{EdgeKey, SubjectRef, nsid_static, owner_did_from_aturi}, sh_tangled::{ actor, feed::star::StarRecord, graph::follow::FollowRecord, repo::{self, Repo, RepoRecord}, }, }; use jacquard_common::{ DefaultStr, types::{aturi::AtUri, did::Did, string::Handle}, xrpc::XrpcResp as _, }; use tracing::error; use crate::{AppState, XrpcError, fetch}; pub(crate) async fn build_profile_basic( state: &AppState, viewer: Option<&Did>, did: &Did, ) -> actor::ProfileViewBasic { let handle = resolve_handle(state, did); let avatar = None; // state.avatar.as_ref().and_then(|s| s.url(did)); let viewer_state = viewer .filter(|viewer| *viewer != did) .map(|viewer| build_actor_viewer_state(&state.edges, viewer, did)); actor::ProfileViewBasic::new() .did(did.clone()) .handle(handle) .maybe_avatar(avatar) .maybe_viewer(viewer_state) .build() } pub(crate) async fn build_profile_detailed( state: &AppState, viewer: Option<&Did>, did: &Did, ) -> actor::ProfileViewDetailed { let basic = build_profile_basic(state, viewer, did).await; let followers = state.edges.count(&EdgeKey::new( nsid_static(FollowRecord::NSID), SubjectRef::Did(did.clone()), )) as i64; let follows = state.edges.count(&EdgeKey::new( nsid_static("sh.tangled.graph.follow.by"), SubjectRef::Did(did.clone()), )) as i64; actor::ProfileViewDetailed::new() .did(basic.did) .handle(basic.handle) .followers_count(followers) .follows_count(follows) .maybe_avatar(basic.avatar) .maybe_viewer(basic.viewer) .build() } fn build_actor_viewer_state(edges: &EdgeStore, viewer: &Did, subject: &Did) -> actor::ViewerState { let following = edges.viewer_source( &EdgeKey::new( nsid_static(FollowRecord::NSID), SubjectRef::Did(subject.clone()), ), viewer.as_str(), ); let followed_by = edges.viewer_source( &EdgeKey::new( nsid_static(FollowRecord::NSID), SubjectRef::Did(viewer.clone()), ), subject.as_str(), ); actor::ViewerState { following, followed_by, ..Default::default() } } pub(crate) async fn build_repo_view_basic( state: &AppState, viewer: Option<&Did>, did: &Did, ) -> Result { let ident = state .resolver .lookup_by_repo_did(did) .await .ok_or(XrpcError::NotFound)?; let uri = AtUri::::from_parts_owned( ident.owner.as_str(), RepoRecord::NSID, ident.rkey.as_str(), ) .map_err(|e| XrpcError::InvalidRecord(format!("repo uri assembly: {e}")))?; let (_, repo) = fetch::>(state, &uri).await?; build_repo_view_basic_inner(state, viewer, &uri, &repo, did.clone()).await } async fn build_repo_view_basic_inner( state: &AppState, viewer: Option<&Did>, repo_uri: &AtUri, repo_record: &Repo, repo_did: Did, ) -> Result { let owner_did = owner_did_from_aturi(repo_uri) .ok_or_else(|| XrpcError::InvalidParams("repo uri authority must be a did".into()))?; let owner = build_profile_basic(state, viewer, &owner_did).await; let slug = repo_record.name.clone().unwrap_or_else(|| { repo_uri .rkey() .map(|r| DefaultStr::from(r.as_ref())) .unwrap_or_default() }); let star_key = EdgeKey::new( nsid_static(StarRecord::NSID), SubjectRef::Did(repo_did.clone()), ); let star_count = state.edges.count(&star_key) as i64; let viewer = viewer.map(|v| { let mut viewer = repo::ViewerState::default(); viewer.star = state.edges.viewer_source(&star_key, v.as_str()); viewer }); Ok(repo::RepoViewBasic::new() .did(repo_did) .owner(owner) .slug(slug) .created_at(repo_record.created_at.clone()) .description(repo_record.description.clone()) .star_count(star_count) .viewer(viewer) .build()) } fn resolve_handle(state: &AppState, did: &Did) -> Handle { match state.identity.get_by_did(did) { Ok(doc) => doc.handle, Err(err) => { error!(error = %err, did = %did, "failed to resolve did"); Handle::new_static("handle.invalid").expect("handle.invalid is a valid handle") } } }