refactor(domain): remove ap_id/inbox_url from User and Thought; use ActivityPubRepository lookups

This commit is contained in:
2026-05-15 13:21:21 +02:00
parent bf3e336d0f
commit e935c8973e
13 changed files with 131 additions and 135 deletions

View File

@@ -1,7 +1,7 @@
use domain::{
errors::DomainError,
events::DomainEvent,
models::thought::{Thought, Visibility},
models::thought::Visibility,
ports::{ActivityPubRepository, OutboundFederationPort, ThoughtRepository, UserRepository},
value_objects::ThoughtId,
};
@@ -18,11 +18,11 @@ pub struct FederationEventService {
}
impl FederationEventService {
fn object_ap_id(&self, thought: &Thought, thought_id: &ThoughtId) -> String {
thought
.ap_id
.clone()
.unwrap_or_else(|| format!("{}/thoughts/{}", self.base_url, thought_id))
async fn object_ap_id(&self, thought_id: &ThoughtId) -> Result<String, DomainError> {
if let Some(ap_id) = self.ap_repo.get_thought_ap_id(thought_id).await? {
return Ok(ap_id);
}
Ok(format!("{}/thoughts/{}", self.base_url, thought_id))
}
pub async fn process(&self, event: &DomainEvent) -> Result<(), DomainError> {
@@ -48,31 +48,24 @@ impl FederationEventService {
Some(u) => u,
None => return Ok(()),
};
// For replies to remote posts: in_reply_to_url is None but in_reply_to_id
// points to the locally-stored remote thought. Resolve its ap_id so the
// outbound Note includes inReplyTo and Mastodon threads it correctly.
let thought = if thought.in_reply_to_url.is_none() {
if let Some(ref reply_id) = thought.in_reply_to_id {
match self.thoughts.find_by_id(reply_id).await? {
Some(parent) => {
let parent_ap_url = parent.ap_id.unwrap_or_else(|| {
format!("{}/thoughts/{}", self.base_url, reply_id)
});
domain::models::thought::Thought {
in_reply_to_url: Some(parent_ap_url),
..thought
}
}
None => thought,
}
} else {
thought
}
// Resolve in_reply_to_url for the parent thought via AP repo.
let in_reply_to_url = if let Some(ref reply_id) = thought.in_reply_to_id {
let ap_id = self
.ap_repo
.get_thought_ap_id(reply_id)
.await?
.unwrap_or_else(|| format!("{}/thoughts/{}", self.base_url, reply_id));
Some(ap_id)
} else {
thought
None
};
self.ap
.broadcast_create(user_id, &thought, user.username.as_str())
.broadcast_create(
user_id,
&thought,
user.username.as_str(),
in_reply_to_url.as_deref(),
)
.await
}
@@ -106,8 +99,21 @@ impl FederationEventService {
Some(u) => u,
None => return Ok(()),
};
let in_reply_to_url = if let Some(ref reply_id) = thought.in_reply_to_id {
self.ap_repo
.get_thought_ap_id(reply_id)
.await?
.or_else(|| Some(format!("{}/thoughts/{}", self.base_url, reply_id)))
} else {
None
};
self.ap
.broadcast_update(user_id, &thought, user.username.as_str())
.broadcast_update(
user_id,
&thought,
user.username.as_str(),
in_reply_to_url.as_deref(),
)
.await
}
@@ -122,11 +128,10 @@ impl FederationEventService {
_ => return Ok(()),
};
let _ = booster;
let thought = match self.thoughts.find_by_id(thought_id).await? {
Some(t) => t,
None => return Ok(()),
};
let object_ap_id = self.object_ap_id(&thought, thought_id);
if self.thoughts.find_by_id(thought_id).await?.is_none() {
return Ok(());
}
let object_ap_id = self.object_ap_id(thought_id).await?;
self.ap.broadcast_announce(user_id, &object_ap_id).await
}
@@ -134,11 +139,10 @@ impl FederationEventService {
user_id,
thought_id,
} => {
let thought = match self.thoughts.find_by_id(thought_id).await? {
Some(t) => t,
None => return Ok(()),
};
let object_ap_id = self.object_ap_id(&thought, thought_id);
if self.thoughts.find_by_id(thought_id).await?.is_none() {
return Ok(());
}
let object_ap_id = self.object_ap_id(thought_id).await?;
self.ap
.broadcast_undo_announce(user_id, &object_ap_id)
.await
@@ -262,17 +266,19 @@ impl FederationEventService {
};
let _ = liker;
let thought = match self.thoughts.find_by_id(thought_id).await? {
Some(t) if t.ap_id.is_some() => t,
Some(t) => t,
_ => return Ok(()),
};
let author = match self.users.find_by_id(&thought.user_id).await? {
Some(u) if u.inbox_url.is_some() => u,
_ => return Ok(()),
let thought_ap_id = match self.ap_repo.get_thought_ap_id(thought_id).await? {
Some(id) => id,
None => return Ok(()), // local thought — no federation needed
};
let actor_urls = match self.ap_repo.get_actor_ap_urls(&thought.user_id).await? {
Some(u) => u,
None => return Ok(()),
};
let object_ap_id = thought.ap_id.unwrap();
let inbox_url = author.inbox_url.unwrap();
self.ap
.broadcast_like(user_id, &object_ap_id, &inbox_url)
.broadcast_like(user_id, &thought_ap_id, &actor_urls.inbox_url)
.await
}
@@ -286,17 +292,19 @@ impl FederationEventService {
};
let _ = liker;
let thought = match self.thoughts.find_by_id(thought_id).await? {
Some(t) if t.ap_id.is_some() => t,
Some(t) => t,
_ => return Ok(()),
};
let author = match self.users.find_by_id(&thought.user_id).await? {
Some(u) if u.inbox_url.is_some() => u,
_ => return Ok(()),
let thought_ap_id = match self.ap_repo.get_thought_ap_id(thought_id).await? {
Some(id) => id,
None => return Ok(()),
};
let actor_urls = match self.ap_repo.get_actor_ap_urls(&thought.user_id).await? {
Some(u) => u,
None => return Ok(()),
};
let object_ap_id = thought.ap_id.unwrap();
let inbox_url = author.inbox_url.unwrap();
self.ap
.broadcast_undo_like(user_id, &object_ap_id, &inbox_url)
.broadcast_undo_like(user_id, &thought_ap_id, &actor_urls.inbox_url)
.await
}
@@ -345,6 +353,7 @@ mod tests {
_: &UserId,
thought: &Thought,
_: &str,
_in_reply_to_url: Option<&str>,
) -> Result<(), DomainError> {
self.created.lock().unwrap().push(thought.id.clone());
Ok(())
@@ -358,6 +367,7 @@ mod tests {
_: &UserId,
thought: &Thought,
_: &str,
_in_reply_to_url: Option<&str>,
) -> Result<(), DomainError> {
self.updated.lock().unwrap().push(thought.id.clone());
Ok(())
@@ -460,10 +470,9 @@ mod tests {
async fn remote_thought_created_does_not_broadcast() {
let store = TestStore::default();
let alice = alice();
// Remote thought: local = false, ap_id = Some(...)
// Remote thought: local = false
let mut thought = local_thought(alice.id.clone());
thought.local = false;
thought.ap_id = Some("https://remote.example/notes/1".into());
store.users.lock().unwrap().push(alice.clone());
store.thoughts.lock().unwrap().push(thought.clone());
@@ -553,7 +562,10 @@ mod tests {
let alice = alice();
let mut thought = local_thought(alice.id.clone());
thought.local = false;
thought.ap_id = Some("https://mastodon.social/users/bob/statuses/123".into());
store.thought_ap_ids.lock().unwrap().insert(
thought.id.clone(),
"https://mastodon.social/users/bob/statuses/123".into(),
);
store.users.lock().unwrap().push(alice.clone());
store.thoughts.lock().unwrap().push(thought.clone());
@@ -702,7 +714,10 @@ mod tests {
let alice = alice();
let mut thought = local_thought(alice.id.clone());
thought.local = false;
thought.ap_id = Some("https://mastodon.social/users/bob/statuses/456".into());
store.thought_ap_ids.lock().unwrap().insert(
thought.id.clone(),
"https://mastodon.social/users/bob/statuses/456".into(),
);
store.thoughts.lock().unwrap().push(thought.clone());
let spy = Arc::new(SpyPort::default());
@@ -797,10 +812,19 @@ mod tests {
PasswordHash("h".into()),
);
author.local = false;
author.inbox_url = Some("https://mastodon.social/users/author/inbox".into());
store.actor_ap_urls.lock().unwrap().insert(
author.id.clone(),
domain::ports::ActorApUrls {
ap_id: "https://mastodon.social/users/author".into(),
inbox_url: "https://mastodon.social/users/author/inbox".into(),
},
);
let mut thought = local_thought(author.id.clone());
thought.ap_id = Some("https://mastodon.social/posts/123".into());
let thought = local_thought(author.id.clone());
store.thought_ap_ids.lock().unwrap().insert(
thought.id.clone(),
"https://mastodon.social/posts/123".into(),
);
let liker = alice();