diff --git a/crates/presentation/src/handlers/federation_management.rs b/crates/presentation/src/handlers/federation_management.rs index dbcf0f7..fca2c44 100644 --- a/crates/presentation/src/handlers/federation_management.rs +++ b/crates/presentation/src/handlers/federation_management.rs @@ -3,7 +3,7 @@ use crate::{ errors::ApiError, extractors::{AuthUser, Deps}, }; -use api_types::responses::{ProfileField, RemoteActorResponse}; +use api_types::responses::{ErrorResponse, ProfileField, RemoteActorResponse}; use application::use_cases::federation_management::{ accept_follow_request, get_remote_friends, initiate_actor_move, list_pending_requests, list_remote_followers, list_remote_following, reject_follow_request, remove_remote_following, @@ -12,18 +12,21 @@ use axum::{http::StatusCode, Json}; use domain::ports::{EventPublisher, FederationActionPort, FollowRepository, UserRepository}; use serde::Deserialize; -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub struct ActorUrlBody { + /// Full ActivityPub actor URL pub actor_url: String, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub struct HandleBody { + /// Fediverse handle (`@user@instance.tld`) pub handle: String, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub struct MoveBody { + /// New actor URL to migrate to pub new_actor_url: String, } @@ -54,6 +57,11 @@ fn to_response(a: domain::models::remote_actor::RemoteActor) -> RemoteActorRespo } } +#[utoipa::path( + get, path = "/federation/me/followers/pending", + responses((status = 200, description = "Pending inbound follow requests", body = Vec)), + security(("bearer_auth" = [])) +)] pub async fn get_pending_requests( Deps(d): Deps, AuthUser(uid): AuthUser, @@ -62,6 +70,15 @@ pub async fn get_pending_requests( Ok(Json(actors.into_iter().map(to_response).collect())) } +#[utoipa::path( + post, path = "/federation/me/followers/accept", + request_body = ActorUrlBody, + responses( + (status = 204, description = "Follow request accepted"), + (status = 400, description = "Invalid request", body = ErrorResponse), + ), + security(("bearer_auth" = [])) +)] pub async fn post_accept_request( Deps(d): Deps, AuthUser(uid): AuthUser, @@ -71,6 +88,15 @@ pub async fn post_accept_request( Ok(StatusCode::NO_CONTENT) } +#[utoipa::path( + delete, path = "/federation/me/followers", + request_body = ActorUrlBody, + responses( + (status = 204, description = "Follower removed / request rejected"), + (status = 400, description = "Invalid request", body = ErrorResponse), + ), + security(("bearer_auth" = [])) +)] pub async fn delete_follower( Deps(d): Deps, AuthUser(uid): AuthUser, @@ -80,6 +106,11 @@ pub async fn delete_follower( Ok(StatusCode::NO_CONTENT) } +#[utoipa::path( + get, path = "/federation/me/followers", + responses((status = 200, description = "Accepted remote followers", body = Vec)), + security(("bearer_auth" = [])) +)] pub async fn get_remote_followers( Deps(d): Deps, AuthUser(uid): AuthUser, @@ -88,6 +119,11 @@ pub async fn get_remote_followers( Ok(Json(actors.into_iter().map(to_response).collect())) } +#[utoipa::path( + get, path = "/federation/me/following", + responses((status = 200, description = "Remote accounts I follow", body = Vec)), + security(("bearer_auth" = [])) +)] pub async fn get_remote_following( Deps(d): Deps, AuthUser(uid): AuthUser, @@ -96,6 +132,11 @@ pub async fn get_remote_following( Ok(Json(actors.into_iter().map(to_response).collect())) } +#[utoipa::path( + get, path = "/federation/me/friends", + responses((status = 200, description = "Remote mutual follows (I follow them and they follow me)", body = Vec)), + security(("bearer_auth" = [])) +)] pub async fn get_remote_friends_handler( Deps(d): Deps, AuthUser(uid): AuthUser, @@ -104,6 +145,15 @@ pub async fn get_remote_friends_handler( Ok(Json(actors.into_iter().map(to_response).collect())) } +#[utoipa::path( + delete, path = "/federation/me/following", + request_body = HandleBody, + responses( + (status = 204, description = "Unfollowed remote account"), + (status = 400, description = "Invalid handle", body = ErrorResponse), + ), + security(("bearer_auth" = [])) +)] pub async fn delete_following( Deps(d): Deps, AuthUser(uid): AuthUser, @@ -121,6 +171,15 @@ pub async fn delete_following( Ok(StatusCode::NO_CONTENT) } +#[utoipa::path( + post, path = "/federation/me/move", + request_body = MoveBody, + responses( + (status = 204, description = "Account move initiated"), + (status = 400, description = "Invalid URL", body = ErrorResponse), + ), + security(("bearer_auth" = [])) +)] pub async fn post_move_account( Deps(d): Deps, AuthUser(uid): AuthUser, @@ -132,11 +191,18 @@ pub async fn post_move_account( Ok(StatusCode::NO_CONTENT) } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub struct AlsoKnownAsBody { + /// Actor URL of the account this identity is also known as (for migration verification) pub also_known_as: Option, } +#[utoipa::path( + patch, path = "/federation/me/also-known-as", + request_body = AlsoKnownAsBody, + responses((status = 204, description = "Also-known-as updated")), + security(("bearer_auth" = [])) +)] pub async fn patch_also_known_as( Deps(d): Deps, AuthUser(uid): AuthUser, diff --git a/crates/presentation/src/openapi/federation_management.rs b/crates/presentation/src/openapi/federation_management.rs new file mode 100644 index 0000000..43ecf43 --- /dev/null +++ b/crates/presentation/src/openapi/federation_management.rs @@ -0,0 +1,25 @@ +use utoipa::OpenApi; + +#[derive(OpenApi)] +#[openapi( + paths( + crate::handlers::federation_management::get_pending_requests, + crate::handlers::federation_management::post_accept_request, + crate::handlers::federation_management::delete_follower, + crate::handlers::federation_management::get_remote_followers, + crate::handlers::federation_management::get_remote_following, + crate::handlers::federation_management::get_remote_friends_handler, + crate::handlers::federation_management::delete_following, + crate::handlers::federation_management::post_move_account, + crate::handlers::federation_management::patch_also_known_as, + ), + components(schemas( + api_types::responses::RemoteActorResponse, + api_types::responses::ProfileField, + crate::handlers::federation_management::ActorUrlBody, + crate::handlers::federation_management::HandleBody, + crate::handlers::federation_management::MoveBody, + crate::handlers::federation_management::AlsoKnownAsBody, + )), +)] +pub struct FederationManagementDoc;