Compare commits
55 Commits
e406464f9f
...
de1d2f7ec7
| Author | SHA1 | Date | |
|---|---|---|---|
| de1d2f7ec7 | |||
| e8d42b01dc | |||
| 4038a6b554 | |||
| be4a37546c | |||
| 7a2d8308d9 | |||
| 4a5d5df884 | |||
| 421cb463e3 | |||
| 925f4f8bf3 | |||
| e5c8380ba7 | |||
| 97bc918bbc | |||
| 805240aaf8 | |||
| cd6148eff9 | |||
| 6f1a0572df | |||
| 0841554dbe | |||
| c30243f1c8 | |||
| 1ad02e0806 | |||
| 23a8444b5c | |||
| b20b60ad10 | |||
| 54bd2b60d0 | |||
| 94193f2d2e | |||
| f54fb543b2 | |||
| a460428be1 | |||
| 95dea06c55 | |||
| c085067318 | |||
| d831784489 | |||
| 4c203bed1d | |||
| 21b8684608 | |||
| 74eeb9fcb9 | |||
| 7ee22ae79f | |||
| 3f26456d77 | |||
| 379f31e27d | |||
| 9c99f7a7a8 | |||
| 636d3d453d | |||
| 9172c82d54 | |||
| cd2eb48ddb | |||
| c5d9833c8b | |||
| f39c1a614d | |||
| 30c8a17168 | |||
| 6a8c8b1fb8 | |||
| 4ec0725ff8 | |||
| 31e0f2958c | |||
| 555121ea75 | |||
| 9e795eefdc | |||
| 18cf2c9f54 | |||
| b58c96b843 | |||
| 8ea24461ba | |||
| e14a9f90c8 | |||
| 28756ef4cd | |||
| 7f27ae49c3 | |||
| 59f3423c00 | |||
| c48aa33592 | |||
| 8f3aa4b891 | |||
| 32bfb00970 | |||
| 7ce2901c2a | |||
| 8bbc713093 |
@@ -187,59 +187,6 @@ impl FollowRepository for PgFollowRepository {
|
||||
.into_domain()?;
|
||||
Ok(ids.into_iter().map(UserId::from_uuid).collect())
|
||||
}
|
||||
|
||||
async fn list_mutual(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
page: &PageParams,
|
||||
) -> Result<Paginated<User>, DomainError> {
|
||||
let total: i64 = sqlx::query_scalar(
|
||||
"SELECT COUNT(*) FROM follows f1
|
||||
WHERE f1.follower_id = $1 AND f1.state = 'accepted'
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM follows f2
|
||||
WHERE f2.follower_id = f1.following_id
|
||||
AND f2.following_id = f1.follower_id
|
||||
AND f2.state = 'accepted'
|
||||
)",
|
||||
)
|
||||
.bind(user_id.as_uuid())
|
||||
.fetch_one(&self.pool)
|
||||
.await
|
||||
.into_domain()?;
|
||||
|
||||
let rows = sqlx::query_as::<_, crate::user::UserRow>(
|
||||
"SELECT u.id, u.username, u.email, u.password_hash, u.display_name, u.bio,
|
||||
u.avatar_url, u.header_url, u.custom_css, u.local,
|
||||
u.created_at, u.updated_at
|
||||
FROM users u
|
||||
JOIN follows f1
|
||||
ON f1.follower_id = $1
|
||||
AND f1.following_id = u.id
|
||||
AND f1.state = 'accepted'
|
||||
WHERE EXISTS (
|
||||
SELECT 1 FROM follows f2
|
||||
WHERE f2.follower_id = u.id
|
||||
AND f2.following_id = $1
|
||||
AND f2.state = 'accepted'
|
||||
)
|
||||
ORDER BY f1.created_at DESC
|
||||
LIMIT $2 OFFSET $3",
|
||||
)
|
||||
.bind(user_id.as_uuid())
|
||||
.bind(page.limit())
|
||||
.bind(page.offset())
|
||||
.fetch_all(&self.pool)
|
||||
.await
|
||||
.into_domain()?;
|
||||
|
||||
Ok(Paginated {
|
||||
items: rows.into_iter().map(User::from).collect(),
|
||||
total,
|
||||
page: page.page,
|
||||
per_page: page.per_page,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -56,86 +56,3 @@ async fn get_accepted_following_ids(pool: sqlx::PgPool) {
|
||||
let ids = repo.get_accepted_following_ids(&alice.id).await.unwrap();
|
||||
assert_eq!(ids, vec![bob.id]);
|
||||
}
|
||||
|
||||
#[sqlx::test(migrations = "./migrations")]
|
||||
async fn list_mutual_returns_only_mutual_accepted_follows(pool: sqlx::PgPool) {
|
||||
let alice = seed_user(&pool, "alice", "alice@ex.com").await;
|
||||
let bob = seed_user(&pool, "bob", "bob@ex.com").await;
|
||||
let carol = seed_user(&pool, "carol", "carol@ex.com").await;
|
||||
let repo = PgFollowRepository::new(pool);
|
||||
let page = domain::models::feed::PageParams {
|
||||
page: 1,
|
||||
per_page: 20,
|
||||
};
|
||||
|
||||
// alice → bob (accepted), bob → alice (accepted) = friends
|
||||
repo.save(&Follow {
|
||||
follower_id: alice.id.clone(),
|
||||
following_id: bob.id.clone(),
|
||||
state: FollowState::Accepted,
|
||||
ap_id: None,
|
||||
created_at: Utc::now(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
repo.save(&Follow {
|
||||
follower_id: bob.id.clone(),
|
||||
following_id: alice.id.clone(),
|
||||
state: FollowState::Accepted,
|
||||
ap_id: None,
|
||||
created_at: Utc::now(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// alice → carol (accepted), carol does NOT follow back = not a friend
|
||||
repo.save(&Follow {
|
||||
follower_id: alice.id.clone(),
|
||||
following_id: carol.id.clone(),
|
||||
state: FollowState::Accepted,
|
||||
ap_id: None,
|
||||
created_at: Utc::now(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let result = repo.list_mutual(&alice.id, &page).await.unwrap();
|
||||
assert_eq!(result.total, 1);
|
||||
assert_eq!(result.items.len(), 1);
|
||||
assert_eq!(result.items[0].id, bob.id);
|
||||
}
|
||||
|
||||
#[sqlx::test(migrations = "./migrations")]
|
||||
async fn list_mutual_excludes_pending_follows(pool: sqlx::PgPool) {
|
||||
let alice = seed_user(&pool, "alice", "alice@ex.com").await;
|
||||
let bob = seed_user(&pool, "bob", "bob@ex.com").await;
|
||||
let repo = PgFollowRepository::new(pool);
|
||||
let page = domain::models::feed::PageParams {
|
||||
page: 1,
|
||||
per_page: 20,
|
||||
};
|
||||
|
||||
// alice → bob (accepted), bob → alice (PENDING) = NOT a friend
|
||||
repo.save(&Follow {
|
||||
follower_id: alice.id.clone(),
|
||||
following_id: bob.id.clone(),
|
||||
state: FollowState::Accepted,
|
||||
ap_id: None,
|
||||
created_at: Utc::now(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
repo.save(&Follow {
|
||||
follower_id: bob.id.clone(),
|
||||
following_id: alice.id.clone(),
|
||||
state: FollowState::Pending,
|
||||
ap_id: None,
|
||||
created_at: Utc::now(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let result = repo.list_mutual(&alice.id, &page).await.unwrap();
|
||||
assert_eq!(result.total, 0);
|
||||
assert!(result.items.is_empty());
|
||||
}
|
||||
|
||||
@@ -96,20 +96,6 @@ pub async fn list_remote_following(
|
||||
federation.get_remote_following(user_id).await
|
||||
}
|
||||
|
||||
pub async fn get_remote_friends(
|
||||
federation: &dyn FederationActionPort,
|
||||
user_id: &UserId,
|
||||
) -> Result<Vec<RemoteActor>, DomainError> {
|
||||
use std::collections::HashSet;
|
||||
let following = federation.get_remote_following(user_id).await?;
|
||||
let followers = federation.get_remote_followers(user_id).await?;
|
||||
let follower_urls: HashSet<&str> = followers.iter().map(|a| a.url.as_str()).collect();
|
||||
Ok(following
|
||||
.into_iter()
|
||||
.filter(|a| follower_urls.contains(a.url.as_str()))
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub async fn remove_remote_following(
|
||||
follows: &dyn FollowRepository,
|
||||
users: &dyn UserReader,
|
||||
|
||||
@@ -1,25 +1,6 @@
|
||||
use super::*;
|
||||
use chrono::Utc;
|
||||
use domain::models::remote_actor::RemoteActor;
|
||||
use domain::testing::TestStore;
|
||||
|
||||
fn remote_actor(url: &str, handle: &str) -> RemoteActor {
|
||||
RemoteActor {
|
||||
url: url.to_string(),
|
||||
handle: handle.to_string(),
|
||||
display_name: None,
|
||||
avatar_url: None,
|
||||
bio: None,
|
||||
banner_url: None,
|
||||
also_known_as: None,
|
||||
outbox_url: None,
|
||||
followers_url: None,
|
||||
following_url: None,
|
||||
attachment: vec![],
|
||||
last_fetched_at: Utc::now(),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn list_pending_returns_empty_by_default() {
|
||||
let store = TestStore::default();
|
||||
@@ -70,41 +51,3 @@ async fn list_remote_following_returns_empty_by_default() {
|
||||
let result = list_remote_following(&store, &uid).await.unwrap();
|
||||
assert!(result.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn get_remote_friends_returns_intersection() {
|
||||
let store = TestStore::default();
|
||||
let uid = UserId::new();
|
||||
|
||||
let bob = remote_actor("https://bob.example.com/users/bob", "bob@bob.example.com");
|
||||
let carol = remote_actor(
|
||||
"https://carol.example.com/users/carol",
|
||||
"carol@carol.example.com",
|
||||
);
|
||||
|
||||
// uid follows bob and carol
|
||||
store
|
||||
.remote_following
|
||||
.lock()
|
||||
.unwrap()
|
||||
.extend([bob.clone(), carol.clone()]);
|
||||
// only bob follows back
|
||||
store.remote_followers.lock().unwrap().push(bob.clone());
|
||||
|
||||
let friends = get_remote_friends(&store, &uid).await.unwrap();
|
||||
assert_eq!(friends.len(), 1);
|
||||
assert_eq!(friends[0].url, bob.url);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn get_remote_friends_empty_when_no_mutual() {
|
||||
let store = TestStore::default();
|
||||
let uid = UserId::new();
|
||||
|
||||
let bob = remote_actor("https://bob.example.com/users/bob", "bob@bob.example.com");
|
||||
store.remote_following.lock().unwrap().push(bob.clone());
|
||||
// bob does NOT follow back
|
||||
|
||||
let friends = get_remote_friends(&store, &uid).await.unwrap();
|
||||
assert!(friends.is_empty());
|
||||
}
|
||||
|
||||
@@ -2,11 +2,7 @@ use chrono::Utc;
|
||||
use domain::{
|
||||
errors::DomainError,
|
||||
events::DomainEvent,
|
||||
models::{
|
||||
feed::{PageParams, Paginated},
|
||||
social::{Block, Boost, Follow, FollowState, Like},
|
||||
user::User,
|
||||
},
|
||||
models::social::{Block, Boost, Follow, FollowState, Like},
|
||||
ports::{
|
||||
BlockRepository, BoostRepository, EventPublisher, FederationFollowPort, FollowRepository,
|
||||
LikeRepository, UserReader,
|
||||
@@ -284,13 +280,5 @@ pub async fn unblock_user(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_local_friends(
|
||||
follows: &dyn FollowRepository,
|
||||
user_id: &UserId,
|
||||
page: &PageParams,
|
||||
) -> Result<Paginated<User>, DomainError> {
|
||||
follows.list_mutual(user_id, page).await
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
@@ -204,51 +204,3 @@ async fn boost_and_unboost() {
|
||||
.iter()
|
||||
.any(|e| matches!(e, DomainEvent::BoostRemoved { .. })));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn get_local_friends_returns_mutual_follows() {
|
||||
use domain::models::feed::PageParams;
|
||||
let store = TestStore::default();
|
||||
let alice = user("alice");
|
||||
let bob = user("bob");
|
||||
let carol = user("carol");
|
||||
|
||||
store
|
||||
.users
|
||||
.lock()
|
||||
.unwrap()
|
||||
.extend([alice.clone(), bob.clone(), carol.clone()]);
|
||||
|
||||
// alice ↔ bob = friends; alice → carol but not back
|
||||
store.follows.lock().unwrap().extend([
|
||||
domain::models::social::Follow {
|
||||
follower_id: alice.id.clone(),
|
||||
following_id: bob.id.clone(),
|
||||
state: domain::models::social::FollowState::Accepted,
|
||||
ap_id: None,
|
||||
created_at: chrono::Utc::now(),
|
||||
},
|
||||
domain::models::social::Follow {
|
||||
follower_id: bob.id.clone(),
|
||||
following_id: alice.id.clone(),
|
||||
state: domain::models::social::FollowState::Accepted,
|
||||
ap_id: None,
|
||||
created_at: chrono::Utc::now(),
|
||||
},
|
||||
domain::models::social::Follow {
|
||||
follower_id: alice.id.clone(),
|
||||
following_id: carol.id.clone(),
|
||||
state: domain::models::social::FollowState::Accepted,
|
||||
ap_id: None,
|
||||
created_at: chrono::Utc::now(),
|
||||
},
|
||||
]);
|
||||
|
||||
let page = PageParams {
|
||||
page: 1,
|
||||
per_page: 20,
|
||||
};
|
||||
let result = get_local_friends(&store, &alice.id, &page).await.unwrap();
|
||||
assert_eq!(result.total, 1);
|
||||
assert_eq!(result.items[0].id, bob.id);
|
||||
}
|
||||
|
||||
@@ -171,11 +171,6 @@ pub trait FollowRepository: Send + Sync {
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
) -> Result<Vec<UserId>, DomainError>;
|
||||
async fn list_mutual(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
page: &PageParams,
|
||||
) -> Result<Paginated<User>, DomainError>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
|
||||
@@ -37,8 +37,6 @@ pub struct TestStore {
|
||||
pub actor_ap_ids: Arc<Mutex<HashMap<String, UserId>>>,
|
||||
/// ThoughtId → AP object URL (used by get_thought_ap_id)
|
||||
pub thought_ap_ids: Arc<Mutex<HashMap<ThoughtId, String>>>,
|
||||
pub remote_following: Arc<Mutex<Vec<RemoteActor>>>,
|
||||
pub remote_followers: Arc<Mutex<Vec<RemoteActor>>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -454,46 +452,6 @@ impl FollowRepository for TestStore {
|
||||
.map(|f| f.following_id.clone())
|
||||
.collect())
|
||||
}
|
||||
async fn list_mutual(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
page: &PageParams,
|
||||
) -> Result<Paginated<User>, DomainError> {
|
||||
use std::collections::HashSet;
|
||||
let follows = self.follows.lock().unwrap();
|
||||
let following_ids: HashSet<UserId> = follows
|
||||
.iter()
|
||||
.filter(|f| &f.follower_id == user_id && f.state == FollowState::Accepted)
|
||||
.map(|f| f.following_id.clone())
|
||||
.collect();
|
||||
let follower_ids: HashSet<UserId> = follows
|
||||
.iter()
|
||||
.filter(|f| &f.following_id == user_id && f.state == FollowState::Accepted)
|
||||
.map(|f| f.follower_id.clone())
|
||||
.collect();
|
||||
let mutual_ids: HashSet<UserId> =
|
||||
following_ids.intersection(&follower_ids).cloned().collect();
|
||||
drop(follows);
|
||||
let users = self.users.lock().unwrap();
|
||||
let all_items: Vec<User> = users
|
||||
.iter()
|
||||
.filter(|u| mutual_ids.contains(&u.id))
|
||||
.cloned()
|
||||
.collect();
|
||||
let total = all_items.len() as i64;
|
||||
let offset = page.offset() as usize;
|
||||
let items: Vec<User> = all_items
|
||||
.into_iter()
|
||||
.skip(offset)
|
||||
.take(page.limit() as usize)
|
||||
.collect();
|
||||
Ok(Paginated {
|
||||
items,
|
||||
total,
|
||||
page: page.page,
|
||||
per_page: page.per_page,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -752,7 +710,7 @@ impl FederationFollowPort for TestStore {
|
||||
&self,
|
||||
_user_id: &UserId,
|
||||
) -> Result<Vec<RemoteActor>, DomainError> {
|
||||
Ok(self.remote_following.lock().unwrap().clone())
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
async fn broadcast_move(
|
||||
@@ -793,7 +751,7 @@ impl FederationFollowRequestPort for TestStore {
|
||||
&self,
|
||||
_user_id: &UserId,
|
||||
) -> Result<Vec<RemoteActor>, DomainError> {
|
||||
Ok(self.remote_followers.lock().unwrap().clone())
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
async fn remove_remote_follower(
|
||||
|
||||
@@ -5,8 +5,8 @@ use crate::{
|
||||
};
|
||||
use api_types::responses::{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,
|
||||
accept_follow_request, initiate_actor_move, list_pending_requests, list_remote_followers,
|
||||
list_remote_following, reject_follow_request, remove_remote_following,
|
||||
};
|
||||
use axum::{http::StatusCode, Json};
|
||||
use domain::ports::{EventPublisher, FederationActionPort, FollowRepository, UserRepository};
|
||||
@@ -96,14 +96,6 @@ pub async fn get_remote_following(
|
||||
Ok(Json(actors.into_iter().map(to_response).collect()))
|
||||
}
|
||||
|
||||
pub async fn get_remote_friends_handler(
|
||||
Deps(d): Deps<FederationManagementDeps>,
|
||||
AuthUser(uid): AuthUser,
|
||||
) -> Result<Json<Vec<RemoteActorResponse>>, ApiError> {
|
||||
let actors = get_remote_friends(&*d.federation, &uid).await?;
|
||||
Ok(Json(actors.into_iter().map(to_response).collect()))
|
||||
}
|
||||
|
||||
pub async fn delete_following(
|
||||
Deps(d): Deps<FederationManagementDeps>,
|
||||
AuthUser(uid): AuthUser,
|
||||
|
||||
@@ -4,15 +4,11 @@ use crate::{
|
||||
errors::ApiError,
|
||||
extractors::{AuthUser, Deps},
|
||||
};
|
||||
use api_types::requests::{PaginationQuery, SetTopFriendsRequest};
|
||||
use api_types::responses::{PagedResponse, TopFriendsResponse, UserResponse};
|
||||
use api_types::requests::SetTopFriendsRequest;
|
||||
use api_types::responses::TopFriendsResponse;
|
||||
use application::use_cases::profile::{get_top_friends, get_user_by_username, set_top_friends};
|
||||
use application::use_cases::social::*;
|
||||
use axum::{
|
||||
extract::{Path, Query},
|
||||
http::StatusCode,
|
||||
Json,
|
||||
};
|
||||
use axum::{extract::Path, http::StatusCode, Json};
|
||||
use domain::{
|
||||
ports::{
|
||||
BlockRepository, BoostRepository, EventPublisher, FederationActionPort, FollowRepository,
|
||||
@@ -154,33 +150,5 @@ pub async fn get_top_friends_handler(
|
||||
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)]
|
||||
mod tests;
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
use super::get_friends_handler;
|
||||
use super::*;
|
||||
use crate::testing::make_state;
|
||||
use axum::{
|
||||
body::Body,
|
||||
http::Request,
|
||||
routing::{delete, get, post},
|
||||
routing::{delete, post},
|
||||
Router,
|
||||
};
|
||||
use tower::ServiceExt;
|
||||
@@ -33,24 +32,6 @@ async fn follow_without_auth_returns_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]
|
||||
async fn unfollow_remote_without_auth_returns_401() {
|
||||
let resp = app()
|
||||
|
||||
@@ -14,7 +14,6 @@ use utoipa::OpenApi;
|
||||
crate::handlers::social::delete_block,
|
||||
crate::handlers::social::put_top_friends,
|
||||
crate::handlers::social::get_top_friends_handler,
|
||||
crate::handlers::social::get_friends_handler,
|
||||
),
|
||||
components(schemas(SetTopFriendsRequest))
|
||||
)]
|
||||
|
||||
@@ -26,7 +26,6 @@ pub fn router() -> Router<AppState> {
|
||||
put(users::upload_banner).layer(DefaultBodyLimit::max(10 * 1024 * 1024)),
|
||||
)
|
||||
.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/{username}", get(users::get_user))
|
||||
.route(
|
||||
@@ -105,10 +104,6 @@ pub fn router() -> Router<AppState> {
|
||||
get(federation_management::get_remote_following)
|
||||
.delete(federation_management::delete_following),
|
||||
)
|
||||
.route(
|
||||
"/federation/me/friends",
|
||||
get(federation_management::get_remote_friends_handler),
|
||||
)
|
||||
.route(
|
||||
"/federation/me/move",
|
||||
post(federation_management::post_move_account),
|
||||
|
||||
Reference in New Issue
Block a user