feat(ports): add get_thought_ap_id and get_actor_ap_urls to ActivityPubRepository

This commit is contained in:
2026-05-15 13:09:37 +02:00
parent 711b3ec63b
commit bf3e336d0f
3 changed files with 60 additions and 1 deletions

View File

@@ -10,7 +10,7 @@ use url::Url;
use domain::{ use domain::{
errors::DomainError, errors::DomainError,
models::thought::{Thought, Visibility}, models::thought::{Thought, Visibility},
ports::{ActivityPubRepository, OutboxEntry}, ports::{ActivityPubRepository, ActorApUrls, OutboxEntry},
value_objects::{Content, ThoughtId, UserId, Username}, value_objects::{Content, ThoughtId, UserId, Username},
}; };
@@ -297,6 +297,34 @@ impl ActivityPubRepository for PgActivityPubRepository {
.into_domain()?; .into_domain()?;
Ok(n as u64) Ok(n as u64)
} }
async fn get_thought_ap_id(
&self,
thought_id: &ThoughtId,
) -> Result<Option<String>, DomainError> {
sqlx::query_scalar::<_, String>(
"SELECT ap_id FROM thoughts WHERE id = $1 AND ap_id IS NOT NULL",
)
.bind(thought_id.as_uuid())
.fetch_optional(&self.pool)
.await
.into_domain()
}
async fn get_actor_ap_urls(
&self,
user_id: &UserId,
) -> Result<Option<ActorApUrls>, DomainError> {
sqlx::query_as::<_, (String, String)>(
"SELECT ap_id, inbox_url FROM users \
WHERE id = $1 AND ap_id IS NOT NULL AND inbox_url IS NOT NULL",
)
.bind(user_id.as_uuid())
.fetch_optional(&self.pool)
.await
.into_domain()
.map(|opt| opt.map(|(ap_id, inbox_url)| ActorApUrls { ap_id, inbox_url }))
}
} }
#[cfg(test)] #[cfg(test)]

View File

@@ -331,6 +331,13 @@ pub trait SearchPort: Send + Sync {
) -> Result<Paginated<User>, DomainError>; ) -> Result<Paginated<User>, DomainError>;
} }
/// AP-protocol endpoints for a locally-stored user (local or interned remote).
#[derive(Debug, Clone)]
pub struct ActorApUrls {
pub ap_id: String,
pub inbox_url: String,
}
/// A local thought ready for AP serialization, with the author's username /// A local thought ready for AP serialization, with the author's username
/// pre-joined so the handler can build AP URLs without a second query. /// pre-joined so the handler can build AP URLs without a second query.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -413,6 +420,18 @@ pub trait ActivityPubRepository: Send + Sync {
/// Total locally-authored thought count for NodeInfo responses. /// Total locally-authored thought count for NodeInfo responses.
async fn count_local_notes(&self) -> Result<u64, DomainError>; async fn count_local_notes(&self) -> Result<u64, DomainError>;
/// Return the ActivityPub object URL for a thought, if one is stored.
/// Returns None for local thoughts (caller constructs URL from base_url + thought_id).
async fn get_thought_ap_id(
&self,
thought_id: &ThoughtId,
) -> Result<Option<String>, DomainError>;
/// Return the AP actor URL and inbox URL for a user, if stored.
/// Returns None for users that have not been federated.
async fn get_actor_ap_urls(&self, user_id: &UserId)
-> Result<Option<ActorApUrls>, DomainError>;
} }
#[async_trait] #[async_trait]

View File

@@ -882,6 +882,18 @@ impl ActivityPubRepository for TestStore {
.filter(|t| t.local) .filter(|t| t.local)
.count() as u64) .count() as u64)
} }
async fn get_thought_ap_id(
&self,
_thought_id: &ThoughtId,
) -> Result<Option<String>, DomainError> {
Ok(None)
}
async fn get_actor_ap_urls(
&self,
_user_id: &UserId,
) -> Result<Option<ActorApUrls>, DomainError> {
Ok(None)
}
} }
#[async_trait] #[async_trait]