use db::sqlx; use model::users::UserModel; use serde::{Deserialize, Serialize}; use crate::{AppService, error::AppError}; #[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)] pub struct UserSummaryResponse { pub username: String, pub display_name: String, pub avatar_url: String, pub website_url: String, #[schema(value_type = String)] pub created_at: chrono::DateTime, } impl AppService { pub async fn users_summary_by_username( &self, username: &str, ) -> Result { let user = self.users_find_active_user_by_username(username).await?; Ok(user.into()) } /// Get a user's avatar URL by username. pub async fn users_get_avatar_url( &self, username: &str, ) -> Result { let user = self.users_find_active_user_by_username(username).await?; if user.avatar_url.is_empty() { return Err(AppError::NotFound("avatar not found".to_string())); } Ok(user.avatar_url) } pub(crate) async fn users_find_active_user_by_username( &self, username: &str, ) -> Result { let user = sqlx::query_as::<_, UserModel>( "SELECT id, username, display_name, avatar_url, website_url, allow_use, can_search, \ last_sign_in_at, created_at, updated_at \ FROM \"user\" WHERE username = $1", ) .bind(username) .fetch_optional(self.db.reader()) .await .map_err(|e| AppError::DatabaseError(e.to_string()))? .ok_or(AppError::UserNotFound)?; if !user.allow_use { return Err(AppError::UserNotFound); } Ok(user) } } impl From for UserSummaryResponse { fn from(value: UserModel) -> Self { Self { username: value.username, display_name: value.display_name, avatar_url: value.avatar_url, website_url: value.website_url, created_at: value.created_at, } } }