feat: image storage generalization, user profile, and federation polish

- Replace PosterStorage with generic ImageStorage port (IMAGE_STORAGE_BACKEND/PATH env vars)
- Rename poster-storage crate to image-storage; serve at /images/{*key}
- Add bio and avatar_path to User model (migration 0009_user_profile)
- update_profile use case with avatar upload, mime validation, old avatar cleanup
- GET/PUT /api/v1/profile and GET/POST /settings/profile HTML page
- Enrich AP Person actor with summary, icon, url, discoverable fields
- Store remote actor avatar_url (migration 0010_ap_remote_actor_avatar)
- Shared inbox delivery via collect_inboxes deduplication
- Broadcast Update(Person) to followers on UserUpdated event
- Paginated outbox: OrderedCollection + OrderedCollectionPage with cursor
- Announce/boost tracking in ap_announces table (migration 0011_ap_announces)
This commit is contained in:
2026-05-11 22:59:52 +02:00
parent 8a254346f4
commit 80f620c840
89 changed files with 2231 additions and 499 deletions

View File

@@ -74,8 +74,14 @@ fn html_routes(rate_limit: u64) -> Router<AppState> {
routing::post(handlers::html::post_delete_review),
)
.route(
"/posters/{*path}",
routing::get(handlers::posters::get_poster),
"/images/{*key}",
routing::get(handlers::images::get_image),
)
.route(
"/posters/{path}",
routing::get(|axum::extract::Path(p): axum::extract::Path<String>| async move {
axum::response::Redirect::permanent(&format!("/images/{}", p))
}),
)
.route("/diary/export", routing::get(handlers::html::get_export))
.route("/import", routing::get(handlers::import::get_import_page))
@@ -89,6 +95,11 @@ fn html_routes(rate_limit: u64) -> Router<AppState> {
.route(
"/users/{id}/feed.rss",
routing::get(handlers::rss::get_user_feed),
)
.route(
"/settings/profile",
routing::get(handlers::html::get_profile_settings)
.post(handlers::html::post_profile_settings),
);
#[cfg(feature = "federation")]
@@ -171,7 +182,8 @@ fn api_routes(rate_limit: u64) -> Router<AppState> {
.route("/import/sessions/{id}/mapping", routing::put(handlers::import::api_put_mapping))
.route("/import/sessions/{id}/confirm", routing::post(handlers::import::api_post_confirm))
.route("/import/profiles", routing::get(handlers::import::api_get_profiles).post(handlers::import::api_post_profile))
.route("/import/profiles/{id}", routing::delete(handlers::import::api_delete_profile));
.route("/import/profiles/{id}", routing::delete(handlers::import::api_delete_profile))
.route("/profile", routing::get(handlers::api::get_profile).put(handlers::api::update_profile_handler));
#[cfg(feature = "federation")]
let base = base.merge(federation_api_routes());