2 Commits

Author SHA1 Message Date
902d38cc76 fix: clippy warnings, clean up accept.rs
All checks were successful
CI / fmt (push) Successful in 34s
CI / clippy (push) Successful in 4m29s
CI / test (push) Successful in 6m32s
2026-07-10 17:13:22 +02:00
f461f633b3 feat: emit OutboundFollowAccepted when remote accepts our follow
v0.4.2 — consumers can now react to outbound follow acceptance
(e.g. import_remote_outbox to backfill). Previously the accept
handler silently updated the DB with no event for consumers.
2026-07-10 16:59:46 +02:00
6 changed files with 42 additions and 13 deletions

View File

@@ -1,5 +1,13 @@
# Changelog # Changelog
## [0.4.2] — 2026-07-10
### New features
- `FederationEvent::OutboundFollowAccepted { local_user_id, remote_actor_url, outbox_url }` — emitted when a remote actor accepts our outbound follow request. Consumers should call `import_remote_outbox(outbox_url, actor_url)` to backfill the remote user's content. Without this event, outbound follow acceptance was silent and consumers had no hook to trigger backfill.
---
## [0.4.1] — 2026-06-30 ## [0.4.1] — 2026-06-30
### Bug fixes ### Bug fixes

2
Cargo.lock generated
View File

@@ -1368,7 +1368,7 @@ dependencies = [
[[package]] [[package]]
name = "k-ap" name = "k-ap"
version = "0.4.0" version = "0.4.2"
dependencies = [ dependencies = [
"activitypub_federation", "activitypub_federation",
"anyhow", "anyhow",

View File

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

View File

@@ -49,14 +49,28 @@ impl Activity for AcceptActivity {
} }
let local_user_id = crate::urls::extract_user_id_from_url(self.object.actor.inner()) let local_user_id = crate::urls::extract_user_id_from_url(self.object.actor.inner())
.ok_or_else(|| Error::bad_request(anyhow::anyhow!("invalid actor URL in Follow")))?; .ok_or_else(|| Error::bad_request(anyhow::anyhow!("invalid actor URL in Follow")))?;
let remote_actor_url = self.actor.inner().as_str().to_string();
data.follow_repo data.follow_repo
.update_following_status( .update_following_status(local_user_id, &remote_actor_url, FollowingStatus::Accepted)
local_user_id,
self.actor.inner().as_str(),
FollowingStatus::Accepted,
)
.await?; .await?;
tracing::info!(remote_actor = %self.actor.inner(), "follow accepted by remote"); tracing::info!(remote_actor = %remote_actor_url, "follow accepted by remote");
if let Some(publisher) = &data.event_publisher {
let outbox_url = data
.actor_repo
.get_remote_actor(&remote_actor_url)
.await
.ok()
.flatten()
.and_then(|a| a.outbox_url);
let _ = publisher
.publish(crate::data::FederationEvent::OutboundFollowAccepted {
local_user_id,
remote_actor_url,
outbox_url,
})
.await;
}
Ok(()) Ok(())
} }
} }

View File

@@ -119,11 +119,11 @@ impl ActorUrls {
fn build(base_url: &str, user_id: uuid::Uuid) -> Self { fn build(base_url: &str, user_id: uuid::Uuid) -> Self {
let ap_id = crate::urls::actor_url(base_url, user_id); let ap_id = crate::urls::actor_url(base_url, user_id);
Self { Self {
inbox_url: Url::parse(&format!("{}/inbox", &ap_id)).expect("valid url"), inbox_url: Url::parse(&format!("{}/inbox", ap_id)).expect("valid url"),
shared_inbox_url: Url::parse(&format!("{}/inbox", base_url)).ok(), shared_inbox_url: Url::parse(&format!("{}/inbox", base_url)).ok(),
outbox_url: Url::parse(&format!("{}/outbox", &ap_id)).expect("valid url"), outbox_url: Url::parse(&format!("{}/outbox", ap_id)).expect("valid url"),
followers_url: Url::parse(&format!("{}/followers", &ap_id)).expect("valid url"), followers_url: Url::parse(&format!("{}/followers", ap_id)).expect("valid url"),
following_url: Url::parse(&format!("{}/following", &ap_id)).expect("valid url"), following_url: Url::parse(&format!("{}/following", ap_id)).expect("valid url"),
ap_id, ap_id,
} }
} }
@@ -285,7 +285,7 @@ impl Object for DbActor {
async fn into_json(self, data: &Data<Self::DataType>) -> Result<Self::Kind, Self::Error> { async fn into_json(self, data: &Data<Self::DataType>) -> Result<Self::Kind, Self::Error> {
let public_key = PublicKey { let public_key = PublicKey {
id: format!("{}#main-key", &self.ap_id), id: format!("{}#main-key", self.ap_id),
owner: self.ap_id.clone(), owner: self.ap_id.clone(),
public_key_pem: self.public_key_pem.clone(), public_key_pem: self.public_key_pem.clone(),
}; };

View File

@@ -41,6 +41,13 @@ pub enum FederationEvent {
owner_user_id: uuid::Uuid, owner_user_id: uuid::Uuid,
follower_inbox_url: String, follower_inbox_url: String,
}, },
/// A remote actor accepted our outbound follow request.
/// Call `ActivityPubService::import_remote_outbox(outbox_url, actor_url)` to backfill.
OutboundFollowAccepted {
local_user_id: uuid::Uuid,
remote_actor_url: String,
outbox_url: Option<String>,
},
} }
/// Receives typed federation events. /// Receives typed federation events.