use db::sqlx; use model::users::{UserPrivacyModel, UserProfileModel}; use serde::{Deserialize, Serialize}; use crate::{AppService, error::AppError}; #[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)] pub struct PublicUserResponse { pub username: String, pub display_name: String, pub avatar_url: String, pub website_url: String, pub language: String, pub timezone: String, pub allow_direct_messages: bool, pub show_online_status: bool, } impl AppService { pub async fn users_public_by_username( &self, username: &str, ) -> Result { let user = self.users_find_active_user_by_username(username).await?; let privacy = sqlx::query_as::<_, UserPrivacyModel>( "SELECT \"user\", profile_visibility, email_visibility, activity_visibility, allow_search_indexing, allow_direct_messages, show_online_status, created_at, updated_at \ FROM user_privacy WHERE \"user\" = $1", ) .bind(user.id) .fetch_optional(self.db.reader()) .await .map_err(|e| AppError::DatabaseError(e.to_string()))?; if privacy .as_ref() .is_some_and(|privacy| privacy.profile_visibility == "private") { return Err(AppError::Forbidden("profile is private".to_string())); } let profile = sqlx::query_as::<_, UserProfileModel>( "SELECT \"user\", language, theme, timezone, created_at, updated_at \ FROM user_profile WHERE \"user\" = $1", ) .bind(user.id) .fetch_optional(self.db.reader()) .await .map_err(|e| AppError::DatabaseError(e.to_string()))?; Ok(PublicUserResponse { username: user.username, display_name: user.display_name, avatar_url: user.avatar_url, website_url: user.website_url, language: profile .as_ref() .map(|profile| profile.language.clone()) .unwrap_or_else(|| "en".to_string()), timezone: profile .map(|profile| profile.timezone) .unwrap_or_else(|| "UTC".to_string()), allow_direct_messages: privacy .as_ref() .map(|privacy| privacy.allow_direct_messages) .unwrap_or(true), show_online_status: privacy .map(|privacy| privacy.show_online_status) .unwrap_or(true), }) } }