diff --git a/Routes-Reference.md b/Routes-Reference.md new file mode 100644 index 0000000..9efdf57 --- /dev/null +++ b/Routes-Reference.md @@ -0,0 +1,69 @@ +# Routes Reference + +`service.router()` registers exactly these routes: + +| Method | Path | Description | +|--------|------|-------------| +| `POST` | `/inbox` | Shared inbox — HTTP sig verification + dispatch, 1 MB limit | +| `POST` | `/users/{id}/inbox` | Per-user inbox — same | +| `GET` | `/users/{id}/outbox` | Cursor-based `OrderedCollection` | +| `GET` | `/users/{id}/featured` | Pinned posts `OrderedCollection` | +| `GET` | `/.well-known/webfinger` | JRD with `aliases` field | +| `GET` | `/.well-known/nodeinfo` | Redirect to `/nodeinfo/2.0` | +| `GET` | `/nodeinfo/2.0` | NodeInfo 2.0 document | + +--- + +## Routes NOT registered + +`GET /users/{id}`, `GET /users/{id}/followers`, and `GET /users/{id}/following` are **not** registered by `service.router()`. + +These paths must serve both AP JSON (for federation) and your UI JSON (for your frontend) based on the `Accept` header. k-ap can't do the UI half, so your application owns the route and calls k-ap's helper methods for the AP half. + +--- + +## Content negotiation pattern + +```rust +use axum::{extract::{Path, State}, http::HeaderMap, response::IntoResponse, Json}; + +async fn actor_handler( + Path(username): Path, + headers: HeaderMap, + State(state): State, +) -> impl IntoResponse { + let user = state.db.find_user_by_username(&username).await?; + + if wants_ap_json(&headers) { + // actor_json takes a UUID as a string + let json = state.service.actor_json(&user.id.to_string()).await?; + return ( + [(axum::http::header::CONTENT_TYPE, "application/activity+json")], + json, + ).into_response(); + } + + // Return your UI JSON or HTML + Json(MyUserResponse::from(user)).into_response() +} + +fn wants_ap_json(headers: &HeaderMap) -> bool { + headers + .get("accept") + .and_then(|v| v.to_str().ok()) + .map(|v| v.contains("application/activity+json") || v.contains("application/ld+json")) + .unwrap_or(false) +} +``` + +The same pattern for followers and following. `page` is `Option` — pass `None` for the collection root, `Some(n)` for a page: + +```rust +// In your followers handler: +let json = state.service.followers_collection_json(user.id, page).await?; + +// In your following handler: +let json = state.service.following_collection_json(user.id, page).await?; +``` + +Both return JSON strings with the correct `@context` and `OrderedCollection` structure. \ No newline at end of file