feat: signed actor lookup and display_name on DbActor

Add display_name field to DbActor, populated from AP Person.name in
from_json. Expose LookedUpActor type and lookup_actor_by_handle method
on ActivityPubService — uses the existing signed webfinger_https path
so strict instances (Threads, etc.) return full actor data.
This commit is contained in:
2026-05-27 22:21:58 +02:00
parent 7901b29f7c
commit bc857b2c08
5 changed files with 51 additions and 2 deletions

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "k-ap" name = "k-ap"
version = "0.1.0" version = "0.1.4"
edition = "2024" edition = "2024"
description = "Generic ActivityPub protocol layer" description = "Generic ActivityPub protocol layer"
license = "MIT" license = "MIT"

View File

@@ -19,6 +19,7 @@ use crate::user::ApProfileField;
pub struct DbActor { pub struct DbActor {
pub user_id: uuid::Uuid, pub user_id: uuid::Uuid,
pub username: String, pub username: String,
pub display_name: Option<String>,
pub public_key_pem: String, pub public_key_pem: String,
pub private_key_pem: Option<String>, pub private_key_pem: Option<String>,
pub inbox_url: Url, pub inbox_url: Url,
@@ -152,6 +153,7 @@ pub async fn get_local_actor(
Ok(DbActor { Ok(DbActor {
user_id, user_id,
username: user.username, username: user.username,
display_name: None,
public_key_pem: public_key, public_key_pem: public_key,
private_key_pem: Some(private_key), private_key_pem: Some(private_key),
inbox_url, inbox_url,
@@ -219,6 +221,7 @@ impl Object for DbActor {
Ok(Some(DbActor { Ok(Some(DbActor {
user_id, user_id,
username: user.username, username: user.username,
display_name: None,
public_key_pem: public_key, public_key_pem: public_key,
private_key_pem: private_key, private_key_pem: private_key,
inbox_url, inbox_url,
@@ -327,6 +330,7 @@ impl Object for DbActor {
Ok(DbActor { Ok(DbActor {
user_id, user_id,
username: json.preferred_username.clone(), username: json.preferred_username.clone(),
display_name: json.name.clone(),
public_key_pem: json.public_key.public_key_pem, public_key_pem: json.public_key.public_key_pem,
private_key_pem: None, private_key_pem: None,
inbox_url, inbox_url,

View File

@@ -25,4 +25,4 @@ pub use repository::{
BlockedDomain, FederationRepository, Follower, FollowerStatus, FollowingStatus, RemoteActor, BlockedDomain, FederationRepository, Follower, FollowerStatus, FollowingStatus, RemoteActor,
}; };
pub use service::ActivityPubService; pub use service::ActivityPubService;
pub use user::{ApProfileField, ApUser, ApUserRepository}; pub use user::{ApProfileField, ApUser, ApUserRepository, LookedUpActor};

View File

@@ -330,6 +330,33 @@ impl ActivityPubService {
Ok(serde_json::to_string(&WithContext::new_default(person))?) Ok(serde_json::to_string(&WithContext::new_default(person))?)
} }
/// Resolve a `@user@domain` handle to actor data using a signed HTTP request.
/// Unlike a plain unauthenticated fetch, this works with instances (e.g. Threads)
/// that require HTTP signatures before returning full actor JSON.
pub async fn lookup_actor_by_handle(
&self,
handle: &str,
) -> anyhow::Result<crate::user::LookedUpActor> {
let data = self.federation_config.to_request_data();
let actor = Self::webfinger_https(handle, &data).await?;
let domain = actor.ap_id.host_str().unwrap_or("").to_string();
let handle = format!("{}@{}", actor.username, domain);
Ok(crate::user::LookedUpActor {
handle,
display_name: actor.display_name,
bio: actor.bio,
avatar_url: actor.avatar_url,
banner_url: actor.banner_url,
ap_url: actor.ap_id,
outbox_url: actor.outbox_url,
followers_url: actor.followers_url,
following_url: actor.following_url,
also_known_as: actor.also_known_as,
profile_url: actor.profile_url,
attachment: actor.attachment,
})
}
/// Returns the ActivityPub router compatible with any outer state `S`. /// Returns the ActivityPub router compatible with any outer state `S`.
/// Handlers only use `Data<FederationData>` injected by the middleware layer, /// Handlers only use `Data<FederationData>` injected by the middleware layer,
/// so the router is independent of the application state type. /// so the router is independent of the application state type.

View File

@@ -7,6 +7,24 @@ pub struct ApProfileField {
pub value: String, pub value: String,
} }
/// Resolved actor data returned by [`crate::service::ActivityPubService::lookup_actor_by_handle`].
/// Fetched via a signed HTTP request so strict instances (e.g. Threads) return full data.
#[derive(Debug, Clone)]
pub struct LookedUpActor {
pub handle: String,
pub display_name: Option<String>,
pub bio: Option<String>,
pub avatar_url: Option<Url>,
pub banner_url: Option<Url>,
pub ap_url: Url,
pub outbox_url: Url,
pub followers_url: Url,
pub following_url: Url,
pub also_known_as: Option<String>,
pub profile_url: Option<Url>,
pub attachment: Vec<ApProfileField>,
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ApUser { pub struct ApUser {
pub id: uuid::Uuid, pub id: uuid::Uuid,