Compare commits
2 Commits
88fd1bfbdc
...
v0.3.0
| Author | SHA1 | Date | |
|---|---|---|---|
| c1c8a37d0d | |||
| 757c6d14ec |
@@ -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.
|
**`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`.
|
**`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.
|
**`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
|
## What the service handles for you
|
||||||
|
|
||||||
|
`service.router()` registers only the routes k-ap fully owns:
|
||||||
|
|
||||||
| Route | Description |
|
| Route | Description |
|
||||||
|-------|-------------|
|
|-------|-------------|
|
||||||
| `GET /users/{id}` | AP actor JSON with public key and security `@context` |
|
| `POST /inbox` | Shared inbox — HTTP signature verification + dispatch, 1 MB limit |
|
||||||
| `POST /users/{id}/inbox` | Per-user inbox — verifies HTTP signatures, 1 MB limit |
|
| `POST /users/{id}/inbox` | Per-user inbox — same |
|
||||||
| `POST /inbox` | Shared inbox — same verification |
|
|
||||||
| `GET /users/{id}/outbox` | Cursor-based `OrderedCollection` |
|
| `GET /users/{id}/outbox` | Cursor-based `OrderedCollection` |
|
||||||
| `GET /users/{id}/followers` | Offset-paginated follower collection |
|
| `GET /users/{id}/featured` | Pinned posts `OrderedCollection` |
|
||||||
| `GET /users/{id}/following` | Offset-paginated following collection |
|
|
||||||
| `GET /users/{id}/featured` | Pinned posts `OrderedCollection` (from `get_featured_objects`) |
|
|
||||||
| `GET /.well-known/webfinger` | JRD with `aliases` field |
|
| `GET /.well-known/webfinger` | JRD with `aliases` field |
|
||||||
| `GET /.well-known/nodeinfo` | NodeInfo well-known redirect |
|
| `GET /.well-known/nodeinfo` | NodeInfo well-known redirect |
|
||||||
| `GET /nodeinfo/2.0` | NodeInfo 2.0 |
|
| `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
|
## ApUser fields
|
||||||
|
|
||||||
Your `ApUserRepository` returns `ApUser`. All fields control how the actor is serialized:
|
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 url::Url;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
actor_handler::actor_handler,
|
|
||||||
actors::{DbActor, get_local_actor},
|
actors::{DbActor, get_local_actor},
|
||||||
content::{ApContentReader, ApObjectHandler},
|
content::{ApContentReader, ApObjectHandler},
|
||||||
data::FederationData,
|
data::FederationData,
|
||||||
featured_handler::featured_handler,
|
featured_handler::featured_handler,
|
||||||
federation::ApFederationConfig,
|
federation::ApFederationConfig,
|
||||||
followers_handler::{followers_handler, following_handler},
|
|
||||||
inbox::inbox_handler,
|
inbox::inbox_handler,
|
||||||
nodeinfo::{nodeinfo_handler, nodeinfo_well_known_handler},
|
nodeinfo::{nodeinfo_handler, nodeinfo_well_known_handler},
|
||||||
outbox::outbox_handler,
|
outbox::outbox_handler,
|
||||||
@@ -190,7 +188,19 @@ impl ActivityPubService {
|
|||||||
&self.base_url
|
&self.base_url
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the ActivityPub router. Inbox routes enforce a 1 MB body limit.
|
/// Returns the ActivityPub router.
|
||||||
|
///
|
||||||
|
/// 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>
|
pub fn router<S>(&self) -> Router<S>
|
||||||
where
|
where
|
||||||
S: Clone + Send + Sync + 'static,
|
S: Clone + Send + Sync + 'static,
|
||||||
@@ -203,14 +213,11 @@ impl ActivityPubService {
|
|||||||
"/inbox",
|
"/inbox",
|
||||||
post(inbox_handler).layer(DefaultBodyLimit::max(1024 * 1024)),
|
post(inbox_handler).layer(DefaultBodyLimit::max(1024 * 1024)),
|
||||||
)
|
)
|
||||||
.route("/users/{id}", get(actor_handler))
|
|
||||||
.route(
|
.route(
|
||||||
"/users/{id}/inbox",
|
"/users/{id}/inbox",
|
||||||
post(inbox_handler).layer(DefaultBodyLimit::max(1024 * 1024)),
|
post(inbox_handler).layer(DefaultBodyLimit::max(1024 * 1024)),
|
||||||
)
|
)
|
||||||
.route("/users/{id}/outbox", get(outbox_handler))
|
.route("/users/{id}/outbox", get(outbox_handler))
|
||||||
.route("/users/{id}/followers", get(followers_handler))
|
|
||||||
.route("/users/{id}/following", get(following_handler))
|
|
||||||
.route("/users/{id}/featured", get(featured_handler))
|
.route("/users/{id}/featured", get(featured_handler))
|
||||||
.layer(self.federation_config.middleware())
|
.layer(self.federation_config.middleware())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user