1
Migration Guide
Gabriel Kaszewski edited this page 2026-05-29 02:15:32 +00:00

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_outboximport_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-coded true; now configurable per user
  • ApUser::actor_type: ApActorType — was hard-coded Person; now configurable
  • ApUser::featured_url: Option<Url> — expose pinned posts; see Pinned Posts
  • service.block_actor / service.unblock_actor — block management from the service