From fc60217dfdade5005c6b368076375c1b1fa82929 Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Fri, 29 May 2026 02:15:32 +0000 Subject: [PATCH] wiki: add Migration Guide page --- Migration-Guide.md | 196 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100644 Migration-Guide.md diff --git a/Migration-Guide.md b/Migration-Guide.md new file mode 100644 index 0000000..df46e93 --- /dev/null +++ b/Migration-Guide.md @@ -0,0 +1,196 @@ +# 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` + +```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` + +```rust +// 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:** +```rust +async fn count_accepted_followers(&self, local_user_id: Uuid) -> Result; +async fn get_accepted_followers_page(&self, local_user_id: Uuid, offset: u32, limit: usize) -> Result>; +async fn get_following_outbox_url(&self, local_user_id: Uuid, remote_actor_url: &str) -> Result>; +``` + +**`ActorRepository` — new:** +```rust +async fn remove_announce(&self, activity_id: &str, actor_url: &str) -> Result<()>; +async fn count_announces(&self, object_url: &str) -> Result; +``` + +--- + +## Step 4: Split `ApObjectHandler` into `ApContentReader` + `ApObjectHandler` + +```rust +// Before — read methods on ApObjectHandler +async fn get_local_objects_page(&self, user_id: Uuid, cursor: Option, 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>, // cursor changed to before-timestamp + limit: usize, + ) -> anyhow::Result)>> { + // return (ap_id, object_json, published_at) tuples, newest-first + } + + async fn count_local_posts(&self) -> anyhow::Result { + // 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: + +```rust +// 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): +```rust +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): +```rust +async fn on_announce_removed(&self, object_url: &Url, actor_url: &Url) -> anyhow::Result<()>; +``` + +--- + +## Step 6: Update `broadcast_create_note` and `broadcast_update_note` + +```rust +// 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 + +```rust +// Before — Option +ApUser { also_known_as: Some("https://old.example/users/alice".to_string()), .. } + +// After — Vec +ApUser { also_known_as: vec!["https://old.example/users/alice".to_string()], .. } +``` + +Same change for `LookedUpActor::also_known_as`. + +--- + +## Step 8: Update the builder + +```rust +// 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](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` — expose pinned posts; see [Pinned Posts](Pinned-Posts) +- `service.block_actor` / `service.unblock_actor` — block management from the service \ No newline at end of file