fix: remove actor/followers/following routes from router()
These paths need content negotiation in real apps (AP JSON vs UI JSON).
k-ap can't serve the UI half, so the consuming app owns the route and
calls actor_json/followers_collection_json/following_collection_json
to produce the AP response.
The route conflict caused a panic when thoughts mounted its own
/users/{username}/... routes alongside service.router().
router() now registers only what k-ap fully owns:
- POST /inbox, POST /users/{id}/inbox (signature verification)
- GET /users/{id}/outbox
- GET /users/{id}/featured
- GET /.well-known/webfinger, nodeinfo, /nodeinfo/2.0
This commit is contained in:
@@ -86,6 +86,8 @@ service.broadcast_create_note(user_id, note_json, ApVisibility::Public, mentione
|
||||
|
||||
**`GET /users/{id}/featured` route** — serves an `OrderedCollection` of pinned posts via `ApContentReader::get_featured_objects`. Default is an empty collection.
|
||||
|
||||
**`router()` no longer registers `GET /users/{id}`, `/followers`, or `/following`** — these paths need content negotiation (AP JSON vs UI JSON) which k-ap can't do. Your application owns those routes and calls `actor_json`, `followers_collection_json`, `following_collection_json` to produce the AP response. See README for the pattern.
|
||||
|
||||
**`FederationEvent::BackfillRequested`** — when an `EventPublisher` is configured, `accept_follower` publishes this event instead of spawning an in-process task. Process it by calling `run_backfill_for_follower`.
|
||||
|
||||
**`run_backfill_for_follower(owner_user_id, follower_inbox_url)`** — public method for workers processing `BackfillRequested` events.
|
||||
|
||||
30
README.md
30
README.md
@@ -67,19 +67,37 @@ let router = Router::new().merge(service.router());
|
||||
|
||||
## What the service handles for you
|
||||
|
||||
`service.router()` registers only the routes k-ap fully owns:
|
||||
|
||||
| Route | Description |
|
||||
|-------|-------------|
|
||||
| `GET /users/{id}` | AP actor JSON with public key and security `@context` |
|
||||
| `POST /users/{id}/inbox` | Per-user inbox — verifies HTTP signatures, 1 MB limit |
|
||||
| `POST /inbox` | Shared inbox — same verification |
|
||||
| `POST /inbox` | Shared inbox — HTTP signature verification + dispatch, 1 MB limit |
|
||||
| `POST /users/{id}/inbox` | Per-user inbox — same |
|
||||
| `GET /users/{id}/outbox` | Cursor-based `OrderedCollection` |
|
||||
| `GET /users/{id}/followers` | Offset-paginated follower collection |
|
||||
| `GET /users/{id}/following` | Offset-paginated following collection |
|
||||
| `GET /users/{id}/featured` | Pinned posts `OrderedCollection` (from `get_featured_objects`) |
|
||||
| `GET /users/{id}/featured` | Pinned posts `OrderedCollection` |
|
||||
| `GET /.well-known/webfinger` | JRD with `aliases` field |
|
||||
| `GET /.well-known/nodeinfo` | NodeInfo well-known redirect |
|
||||
| `GET /nodeinfo/2.0` | NodeInfo 2.0 |
|
||||
|
||||
**Not registered by `router()`:** `GET /users/{id}`, `GET /users/{id}/followers`, `GET /users/{id}/following`.
|
||||
|
||||
These paths are dual-purpose in real applications — they must serve both AP JSON (`application/activity+json`) and the app's own UI JSON (content negotiation). k-ap can't do the UI half, so your application owns the route and calls k-ap's helper methods to produce the AP response:
|
||||
|
||||
```rust
|
||||
// In your axum actor handler — serve AP JSON or UI JSON based on Accept header
|
||||
async fn actor_handler(Path(username): Path<String>, headers: HeaderMap, ...) {
|
||||
if wants_ap_json(&headers) {
|
||||
let json = service.actor_json(&user.id.to_string()).await?;
|
||||
return ap_json_response(json);
|
||||
}
|
||||
// ... serve UI response
|
||||
}
|
||||
|
||||
// Similarly for followers/following:
|
||||
let json = service.followers_collection_json(user_id, page).await?;
|
||||
let json = service.following_collection_json(user_id, page).await?;
|
||||
```
|
||||
|
||||
## ApUser fields
|
||||
|
||||
Your `ApUserRepository` returns `ApUser`. All fields control how the actor is serialized:
|
||||
|
||||
@@ -5,13 +5,11 @@ use axum::{Router, extract::DefaultBodyLimit, routing::get, routing::post};
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
actor_handler::actor_handler,
|
||||
actors::{DbActor, get_local_actor},
|
||||
content::{ApContentReader, ApObjectHandler},
|
||||
data::FederationData,
|
||||
featured_handler::featured_handler,
|
||||
federation::ApFederationConfig,
|
||||
followers_handler::{followers_handler, following_handler},
|
||||
inbox::inbox_handler,
|
||||
nodeinfo::{nodeinfo_handler, nodeinfo_well_known_handler},
|
||||
outbox::outbox_handler,
|
||||
@@ -190,15 +188,19 @@ impl ActivityPubService {
|
||||
&self.base_url
|
||||
}
|
||||
|
||||
/// Returns the ActivityPub router. Inbox routes enforce a 1 MB body limit.
|
||||
/// Returns the ActivityPub router. Inbox routes enforce a 1 MB body limit.
|
||||
/// Returns the ActivityPub router.
|
||||
///
|
||||
/// Does NOT register `GET /users/{id}`, `GET /users/{id}/followers`,
|
||||
/// `GET /users/{id}/following`, or `GET /users/{id}/featured` — consuming
|
||||
/// applications typically own those paths (often behind content negotiation)
|
||||
/// and should wire the AP response themselves by calling `actor_json`,
|
||||
/// `followers_collection_json`, `following_collection_json`, and
|
||||
/// `get_featured_objects` from their own handlers.
|
||||
/// Registers only routes that k-ap fully owns:
|
||||
/// - `POST /inbox` + `POST /users/{id}/inbox` — signature verification + dispatch (1 MB limit)
|
||||
/// - `GET /users/{id}/outbox` — cursor-paginated OrderedCollection
|
||||
/// - `GET /users/{id}/featured` — pinned posts OrderedCollection
|
||||
/// - `GET /.well-known/webfinger`, `GET /.well-known/nodeinfo`, `GET /nodeinfo/2.0`
|
||||
///
|
||||
/// **Not registered:** `GET /users/{id}`, `GET /users/{id}/followers`,
|
||||
/// `GET /users/{id}/following`. Real applications need those paths to serve
|
||||
/// both AP JSON and their own UI JSON (content negotiation), so they must own
|
||||
/// the route. Call `actor_json`, `followers_collection_json`, and
|
||||
/// `following_collection_json` from your own handler to produce the AP response.
|
||||
pub fn router<S>(&self) -> Router<S>
|
||||
where
|
||||
S: Clone + Send + Sync + 'static,
|
||||
|
||||
Reference in New Issue
Block a user