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:
2026-05-29 03:28:17 +02:00
parent 757c6d14ec
commit c1c8a37d0d
3 changed files with 38 additions and 16 deletions

View File

@@ -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: