learn and share notes on atproto (wip) 馃
malfestio.stormlightlabs.org/
readability
solid
axum
atproto
srs
1use crate::state::SharedState;
2
3use axum::{
4 Json,
5 extract::{Path, State},
6 http::StatusCode,
7 response::IntoResponse,
8};
9use serde_json::json;
10
11/// GET /api/users/:did/profile
12/// Get aggregated profile information for a user
13pub async fn get_profile(State(state): State<SharedState>, Path(did): Path<String>) -> impl IntoResponse {
14 match state.social_repo.get_user_profile(&did).await {
15 Ok(profile) => Json(profile).into_response(),
16 Err(e) => {
17 tracing::error!("Failed to get user profile: {:?}", e);
18 (
19 StatusCode::INTERNAL_SERVER_ERROR,
20 Json(json!({"error": "Failed to get user profile"})),
21 )
22 .into_response()
23 }
24 }
25}
26
27#[cfg(test)]
28mod tests {
29 use super::*;
30 use crate::repository::card::mock::MockCardRepository;
31 use crate::repository::deck::mock::MockDeckRepository;
32 use crate::repository::note::mock::MockNoteRepository;
33 use crate::repository::oauth::mock::MockOAuthRepository;
34 use crate::repository::preferences::mock::MockPreferencesRepository;
35 use crate::repository::review::mock::MockReviewRepository;
36 use crate::repository::search::mock::MockSearchRepository;
37 use crate::repository::social::mock::MockSocialRepository;
38 use crate::repository::social::{SocialRepository, UserProfile};
39 use crate::state::AppState;
40 use std::sync::Arc;
41
42 fn create_test_state(social_repo: Arc<MockSocialRepository>) -> SharedState {
43 let pool = crate::db::create_mock_pool();
44 Arc::new(AppState {
45 pool,
46 card_repo: Arc::new(MockCardRepository::new()) as Arc<dyn crate::repository::card::CardRepository>,
47 note_repo: Arc::new(MockNoteRepository::new()) as Arc<dyn crate::repository::note::NoteRepository>,
48 oauth_repo: Arc::new(MockOAuthRepository::new()) as Arc<dyn crate::repository::oauth::OAuthRepository>,
49 prefs_repo: Arc::new(MockPreferencesRepository::new())
50 as Arc<dyn crate::repository::preferences::PreferencesRepository>,
51 review_repo: Arc::new(MockReviewRepository::new()) as Arc<dyn crate::repository::review::ReviewRepository>,
52 social_repo: social_repo.clone() as Arc<dyn SocialRepository>,
53 deck_repo: Arc::new(MockDeckRepository::new()) as Arc<dyn crate::repository::deck::DeckRepository>,
54 search_repo: Arc::new(MockSearchRepository::new()) as Arc<dyn crate::repository::search::SearchRepository>,
55 sync_repo: Arc::new(crate::repository::sync::mock::MockSyncRepository::new())
56 as Arc<dyn crate::repository::sync::SyncRepository>,
57 config: crate::state::AppConfig { pds_url: "https://bsky.social".to_string() },
58 auth_cache: Arc::new(tokio::sync::RwLock::new(std::collections::HashMap::new())),
59 dpop_nonces: Arc::new(tokio::sync::RwLock::new(std::collections::HashMap::new())),
60 identity_resolver: crate::oauth::resolver::IdentityResolver::new(),
61 })
62 }
63
64 #[tokio::test]
65 async fn test_get_profile_returns_counts() {
66 let social_repo = Arc::new(MockSocialRepository::new());
67 social_repo.follow("did:follower1", "did:user").await.unwrap();
68 social_repo.follow("did:follower2", "did:user").await.unwrap();
69 social_repo.follow("did:user", "did:following").await.unwrap();
70
71 let state = create_test_state(social_repo);
72 let response = get_profile(State(state), Path("did:user".to_string()))
73 .await
74 .into_response();
75
76 assert_eq!(response.status(), StatusCode::OK);
77
78 let body = axum::body::to_bytes(response.into_body(), usize::MAX).await.unwrap();
79 let profile: UserProfile = serde_json::from_slice(&body).unwrap();
80
81 assert_eq!(profile.did, "did:user");
82 assert_eq!(profile.follower_count, 2);
83 assert_eq!(profile.following_count, 1);
84 }
85
86 #[tokio::test]
87 async fn test_get_profile_unknown_user_returns_zero() {
88 let social_repo = Arc::new(MockSocialRepository::new());
89 let state = create_test_state(social_repo);
90
91 let response = get_profile(State(state), Path("did:unknown".to_string()))
92 .await
93 .into_response();
94
95 assert_eq!(response.status(), StatusCode::OK);
96
97 let body = axum::body::to_bytes(response.into_body(), usize::MAX).await.unwrap();
98 let profile: UserProfile = serde_json::from_slice(&body).unwrap();
99
100 assert_eq!(profile.follower_count, 0);
101 assert_eq!(profile.following_count, 0);
102 }
103}