feat(presentation): add GET /users/me/friends handler and route

This commit is contained in:
2026-05-28 03:46:52 +02:00
parent d4da172398
commit 0e2b72b77a
4 changed files with 57 additions and 4 deletions

View File

@@ -4,11 +4,15 @@ use crate::{
errors::ApiError, errors::ApiError,
extractors::{AuthUser, Deps}, extractors::{AuthUser, Deps},
}; };
use api_types::requests::SetTopFriendsRequest; use api_types::requests::{PaginationQuery, SetTopFriendsRequest};
use api_types::responses::TopFriendsResponse; use api_types::responses::{PagedResponse, TopFriendsResponse, UserResponse};
use application::use_cases::profile::{get_top_friends, get_user_by_username, set_top_friends}; use application::use_cases::profile::{get_top_friends, get_user_by_username, set_top_friends};
use application::use_cases::social::*; use application::use_cases::social::*;
use axum::{extract::Path, http::StatusCode, Json}; use axum::{
extract::{Path, Query},
http::StatusCode,
Json,
};
use domain::{ use domain::{
ports::{ ports::{
BlockRepository, BoostRepository, EventPublisher, FederationActionPort, FollowRepository, BlockRepository, BoostRepository, EventPublisher, FederationActionPort, FollowRepository,
@@ -150,5 +154,33 @@ pub async fn get_top_friends_handler(
Ok(Json(TopFriendsResponse { top_friends })) Ok(Json(TopFriendsResponse { top_friends }))
} }
#[utoipa::path(
get, path = "/users/me/friends",
params(PaginationQuery),
responses(
(status = 200, description = "Local mutual follows (paginated)", body = inline(PagedResponse<UserResponse>)),
(status = 401, description = "Unauthorized"),
),
security(("bearer_auth" = []))
)]
pub async fn get_friends_handler(
Deps(d): Deps<SocialDeps>,
AuthUser(uid): AuthUser,
Query(q): Query<PaginationQuery>,
) -> Result<Json<PagedResponse<UserResponse>>, ApiError> {
use domain::models::feed::PageParams;
let page = PageParams {
page: q.page(),
per_page: q.per_page(),
};
let result = get_local_friends(&*d.follows, &uid, &page).await?;
Ok(Json(PagedResponse {
items: result.items.iter().map(to_user_response).collect(),
total: result.total,
page: result.page,
per_page: result.per_page,
}))
}
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;

View File

@@ -1,9 +1,10 @@
use super::get_friends_handler;
use super::*; use super::*;
use crate::testing::make_state; use crate::testing::make_state;
use axum::{ use axum::{
body::Body, body::Body,
http::Request, http::Request,
routing::{delete, post}, routing::{delete, get, post},
Router, Router,
}; };
use tower::ServiceExt; use tower::ServiceExt;
@@ -32,6 +33,24 @@ async fn follow_without_auth_returns_401() {
assert_eq!(resp.status(), 401); assert_eq!(resp.status(), 401);
} }
#[tokio::test]
async fn get_friends_without_auth_returns_401() {
let app = Router::new()
.route("/users/me/friends", get(get_friends_handler))
.with_state(make_state());
let resp = app
.oneshot(
Request::builder()
.method("GET")
.uri("/users/me/friends")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(resp.status(), 401);
}
#[tokio::test] #[tokio::test]
async fn unfollow_remote_without_auth_returns_401() { async fn unfollow_remote_without_auth_returns_401() {
let resp = app() let resp = app()

View File

@@ -14,6 +14,7 @@ use utoipa::OpenApi;
crate::handlers::social::delete_block, crate::handlers::social::delete_block,
crate::handlers::social::put_top_friends, crate::handlers::social::put_top_friends,
crate::handlers::social::get_top_friends_handler, crate::handlers::social::get_top_friends_handler,
crate::handlers::social::get_friends_handler,
), ),
components(schemas(SetTopFriendsRequest)) components(schemas(SetTopFriendsRequest))
)] )]

View File

@@ -26,6 +26,7 @@ pub fn router() -> Router<AppState> {
put(users::upload_banner).layer(DefaultBodyLimit::max(10 * 1024 * 1024)), put(users::upload_banner).layer(DefaultBodyLimit::max(10 * 1024 * 1024)),
) )
.route("/users/me/following", get(users::get_me_following)) .route("/users/me/following", get(users::get_me_following))
.route("/users/me/friends", get(social::get_friends_handler))
.route("/users/me/top-friends", put(social::put_top_friends)) .route("/users/me/top-friends", put(social::put_top_friends))
.route("/users/{username}", get(users::get_user)) .route("/users/{username}", get(users::get_user))
.route( .route(