Table of Contents
- Migration Guide — 0.1.x → 0.3.0
- Step 1: Update Cargo.toml
- Step 2: Rename backfill_outbox → import_remote_outbox
- Step 3: Split FederationRepository into four traits
- Step 4: Split ApObjectHandler into ApContentReader + ApObjectHandler
- Step 5: Update ApObjectHandler method signatures
- Step 6: Update broadcast_create_note and broadcast_update_note
- Step 7: Update also_known_as fields
- Step 8: Update the builder
- Step 9: Add content negotiation routes
- New opt-in features (no migration required)
Migration Guide — 0.1.x → 0.3.0
Work through each step top-to-bottom. Each step is independent after the previous one compiles.
Step 1: Update Cargo.toml
# Before
k-ap = { git = "...", tag = "v0.1.10" }
# After (registry)
k-ap = { version = "0.3.0", registry = "gitea" }
# After (git fallback)
k-ap = { git = "https://git.gabrielkaszewski.dev/GKaszewski/k-ap.git", tag = "v0.3.0" }
Step 2: Rename backfill_outbox → import_remote_outbox
// Before
service.backfill_outbox(outbox_url, actor_url).await?;
// After
service.import_remote_outbox(outbox_url, actor_url).await?;
Step 3: Split FederationRepository into four traits
In 0.1.x one FederationRepository trait had ~34 methods. In 0.3.0 it is split into four:
| Trait | Methods |
|---|---|
ActivityRepository |
is_activity_processed, mark_activity_processed |
FollowRepository |
20 methods — follower/following graph + migration |
ActorRepository |
7 methods — keypairs, cache, announce tracking |
BlocklistRepository |
8 methods — domain and actor blocklists |
Implement each on your existing struct. You will need to add these new required methods that did not exist in 0.1.x:
FollowRepository — new:
async fn count_accepted_followers(&self, local_user_id: Uuid) -> Result<usize>;
async fn get_accepted_followers_page(&self, local_user_id: Uuid, offset: u32, limit: usize) -> Result<Vec<RemoteActor>>;
async fn get_following_outbox_url(&self, local_user_id: Uuid, remote_actor_url: &str) -> Result<Option<String>>;
ActorRepository — new:
async fn remove_announce(&self, activity_id: &str, actor_url: &str) -> Result<()>;
async fn count_announces(&self, object_url: &str) -> Result<usize>;
Step 4: Split ApObjectHandler into ApContentReader + ApObjectHandler
// Before — read methods on ApObjectHandler
async fn get_local_objects_page(&self, user_id: Uuid, cursor: Option<String>, limit: u64) -> ...;
async fn count_local_posts(&self, user_id: Uuid) -> ...;
// After — split into a separate trait
impl ApContentReader for MyDb {
async fn get_local_objects_page(
&self,
user_id: Uuid,
before: Option<DateTime<Utc>>, // cursor changed to before-timestamp
limit: usize,
) -> anyhow::Result<Vec<(Url, serde_json::Value, DateTime<Utc>)>> {
// return (ap_id, object_json, published_at) tuples, newest-first
}
async fn count_local_posts(&self) -> anyhow::Result<u64> {
// NOTE: no user_id parameter — counts ALL local posts for NodeInfo
}
// get_featured_objects has a default empty impl — override for pinned posts
}
get_local_objects_for_user (if present) was removed as dead code.
Step 5: Update ApObjectHandler method signatures
Several callbacks changed signatures:
// Before
async fn on_create(&self, user_id: Uuid, object: Value) -> ...;
async fn on_update(&self, user_id: Uuid, object: Value) -> ...;
async fn on_delete(&self, user_id: Uuid, object_id: Url) -> ...;
async fn on_like(&self, user_id: Uuid, actor_url: Url, object_id: Url) -> ...;
async fn on_unlike(&self, user_id: Uuid, actor_url: Url, object_id: Url) -> ...;
async fn on_announce(&self, user_id: Uuid, actor_url: Url, object_id: Url) -> ...;
async fn on_mention(&self, user_id: Uuid, actor_url: Url, object: Value) -> ...;
// After
async fn on_create(&self, ap_id: &Url, actor_url: &Url, object: Value) -> ...;
async fn on_update(&self, ap_id: &Url, actor_url: &Url, object: Value) -> ...;
async fn on_delete(&self, ap_id: &Url, actor_url: &Url) -> ...;
async fn on_like(&self, object_url: &Url, actor_url: &Url) -> ...;
async fn on_unlike(&self, object_url: &Url, actor_url: &Url) -> ...;
async fn on_announce_received(&self, object_url: &Url, actor_url: &Url) -> ...; // renamed
async fn on_mention(&self, object_ap_id: &Url, mentioned_user_id: Uuid, actor_url: &Url) -> ...;
New required methods (no defaults):
async fn on_actor_removed(&self, actor_url: &Url) -> anyhow::Result<()>;
async fn on_announce_of_remote(&self, object_url: &Url, actor_url: &Url) -> anyhow::Result<()>;
New optional method (default no-op):
async fn on_announce_removed(&self, object_url: &Url, actor_url: &Url) -> anyhow::Result<()>;
Step 6: Update broadcast_create_note and broadcast_update_note
// Before
service.broadcast_create_note(user_id, note_json, ApVisibility::Public).await?;
// After — pass inboxes of mentioned non-followers, or vec![] if none
service.broadcast_create_note(user_id, note_json, ApVisibility::Public, mentioned_inboxes).await?;
ApVisibility is a new enum. Replace any visibility handling:
- Public post →
ApVisibility::Public - Followers only →
ApVisibility::FollowersOnly - No delivery →
ApVisibility::Private
Step 7: Update also_known_as fields
// Before — Option<String>
ApUser { also_known_as: Some("https://old.example/users/alice".to_string()), .. }
// After — Vec<String>
ApUser { also_known_as: vec!["https://old.example/users/alice".to_string()], .. }
Same change for LookedUpActor::also_known_as.
Step 8: Update the builder
// Before (positional arguments)
ActivityPubService::builder(repo, user_repo, handler, "https://example.com")
// After (named setters)
ActivityPubService::builder("https://example.com")
.activity_repo(db.clone())
.follow_repo(db.clone())
.actor_repo(db.clone())
.blocklist_repo(db.clone())
.user_repo(db.clone())
.content_reader(db.clone())
.object_handler(db.clone())
.build()
.await?
Step 9: Add content negotiation routes
In 0.3.0, GET /users/{id}, /followers, and /following are no longer registered by service.router().
Add these routes to your axum router and implement the content negotiation pattern. See Routes Reference for the full example with service.actor_json(), service.followers_collection_json(), and service.following_collection_json().
New opt-in features (no migration required)
These are new in 0.3.0 — no code breaks if you don't use them:
ApUser::discoverable: bool— was hard-codedtrue; now configurable per userApUser::actor_type: ApActorType— was hard-codedPerson; now configurableApUser::featured_url: Option<Url>— expose pinned posts; see Pinned Postsservice.block_actor/service.unblock_actor— block management from the service