Gabriel Kaszewski db6a451788 fix: address remaining 3 NOT DONE plan items
#18 featured collection: add featured_url to ApUser/DbActor/Person;
     serialized as featured field in AP JSON when set by consumer.

#19 Tombstone in Delete: broadcast_delete_to_followers now sends
     {"type":"Tombstone","id":"..."} instead of bare URL string.

#21 Backfill pagination: run_backfill uses get_local_objects_page
     with cursor-based loop — avoids loading all posts into memory;
     delivers newest-to-oldest in BATCH_SIZE chunks.
2026-05-29 00:52:37 +02:00
2026-05-17 22:54:03 +02:00

k-ap

Generic ActivityPub protocol layer for Rust services. Extracted from the thoughts and movies-diary projects.

Wraps activitypub_federation and provides the plumbing that every AP-enabled service needs: actor management, inbox/outbox routing, follower tracking, WebFinger, NodeInfo, and HTTP signature handling.

Not domain-specific — no opinions about what your content type looks like.

Add as dependency

[dependencies]
k-ap = { git = "https://git.gabrielkaszewski.dev/GKaszewski/k-ap.git", tag = "v0.1.10" }

What you implement

Three traits wire your data layer into k-ap:

// Your database layer for follows, keypairs, remote actors, blocks
impl FederationRepository for MyFederationRepo { ... }

// Your user lookup (id, username, bio, avatar, alsoKnownAs)
impl ApUserRepository for MyUserRepo { ... }

// Dispatch incoming AP objects to the right handler
impl ApObjectHandler for MyObjectHandler { ... }

Wire up the service

use k_ap::{ActivityPubService, FederationRepository, ApUserRepository, ApObjectHandler};

let service = ActivityPubService::builder(
    Arc::new(my_federation_repo),
    Arc::new(my_user_repo),
    Arc::new(my_object_handler),
    "https://example.com",
)
.allow_registration(true)
.software_name("my-app")
.build()
.await?;

// Mount the AP routes onto your axum router
let router = Router::new().merge(service.router());

What the service handles for you

  • ActorGET /users/:id serves the AP Person object with public key
  • InboxPOST /users/:id/inbox + POST /inbox (shared), verifies HTTP signatures, dispatches to your ApObjectHandler
  • OutboxGET /users/:id/outbox with OrderedCollection pagination
  • Followers / FollowingGET /users/:id/followers and /following
  • WebFingerGET /.well-known/webfinger
  • NodeInfoGET /.well-known/nodeinfo + GET /nodeinfo/2.0

Broadcast from your domain layer

// Fan out a new note to all accepted followers
service.broadcast_create_note(user_id, note_json).await?;
service.broadcast_update_note(user_id, note_json).await?;
service.broadcast_delete_to_followers(user_id, ap_id).await?;

// Announce / Undo Announce
service.broadcast_announce_to_followers(user_id, object_ap_id).await?;
service.broadcast_undo_announce_to_followers(user_id, object_ap_id).await?;

// Like / Unlike to a remote inbox
service.broadcast_like_to_inbox(user_id, object_ap_id, inbox_url).await?;
service.broadcast_undo_like_to_inbox(user_id, object_ap_id, inbox_url).await?;

// Actor profile update
service.broadcast_actor_update(user_id).await?;

// Account migration — sends a Move activity to all followers
// Pre-condition: set alsoKnownAs on the local actor before calling this
service.broadcast_move(user_id, new_actor_url).await?;

Follow management

// Outbound follows (resolves handle via WebFinger)
service.follow(local_user_id, "@user@remote.example").await?;
service.unfollow(local_user_id, remote_actor_url).await?;

// Inbound follow requests — full flow (DB update + AP delivery + backfill)
service.accept_follower(local_user_id, remote_actor_url).await?;
service.reject_follower(local_user_id, remote_actor_url).await?;

// Inbound follow requests — DB only (no AP delivery)
// Use these when delivering Accept/Reject from a separate worker process
service.mark_follower_accepted(local_user_id, remote_actor_url).await?;
service.mark_follower_rejected(local_user_id, remote_actor_url).await?;

Actor lookup

// Resolve a handle via WebFinger using a signed HTTP request.
// Works with strict instances (e.g. Threads) that require HTTP signatures.
let actor: LookedUpActor = service.lookup_actor_by_handle("@user@remote.example").await?;

Project-specific ports

k-ap does not define port traits tied to your domain (e.g. OutboundFederationPort, ActivityPubRepository<Thought>). Those belong in your adapter layer and are wired up there. See crates/adapters/activitypub/src/port.rs in thoughts for a reference implementation.

Key public types

Type Description
ActivityPubService Central service — build once, share via Arc
FederationData Request-scoped data passed through the federation layer
FederationRepository Trait: follows, keypairs, remote actors, blocks
ApUserRepository Trait: user lookup by id / username
ApObjectHandler Trait: dispatch incoming AP objects
LookedUpActor Resolved remote actor data from lookup_actor_by_handle
RemoteActor A federated actor record
Follower / FollowerStatus Follower with pending/accepted/rejected state
ApUser AP-serializable local user (includes also_known_as)
ApFederationConfig Wraps the activitypub_federation config
Error AP-layer error type

Inbound activity handling

The library handles the following inbound AP activities out of the box:

Follow, Accept, Reject, Undo (Follow, Like, Announce), Create, Update, Delete, Announce, Like, Add, Block, Move

Move is fully handled: verifies alsoKnownAs cross-reference on the target actor, migrates all local following records, and re-follows the new actor on behalf of affected users.

Actor types accepted: Person, Service, Application, Organization, Group.

Description
No description provided
Readme 117 MiB
Languages
Rust 99.8%
Makefile 0.2%