use crate::AppService; use crate::error::AppError; use chrono::Utc; use models::projects::{project, project_members}; use models::users::user; use sea_orm::prelude::*; use sea_orm::*; use serde::{Deserialize, Serialize}; use session::Session; use utoipa::{IntoParams, ToSchema}; use uuid::Uuid; #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub struct UserProjectInfo { pub uid: Uuid, pub name: String, pub display_name: String, pub avatar_url: Option, pub description: Option, pub is_public: bool, pub created_at: chrono::DateTime, pub updated_at: chrono::DateTime, pub member_count: i64, pub is_member: bool, } #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub struct UserProjectsResponse { pub username: String, pub projects: Vec, pub total_count: u64, } #[derive(Debug, Clone, Serialize, Deserialize, ToSchema, IntoParams)] pub struct UserProjectsQuery { pub page: Option, pub per_page: Option, } impl AppService { pub async fn get_user_projects( &self, context: Session, username: String, query: UserProjectsQuery, ) -> Result { let target_user = user::Entity::find() .filter(user::Column::Username.eq(&username)) .one(&self.db) .await? .ok_or(AppError::UserNotFound)?; let current_user_uid = context.user(); let is_owner = current_user_uid .map(|uid| uid == target_user.uid) .unwrap_or(false); let has_admin_privilege = false; let page = std::cmp::Ord::max(query.page.unwrap_or(1), 1); let per_page = std::cmp::Ord::min(std::cmp::Ord::max(query.per_page.unwrap_or(20), 1), 100); let offset = (page - 1) * per_page; let mut condition = Condition::all().add(project::Column::CreatedBy.eq(target_user.uid)); if !is_owner && !has_admin_privilege { condition = condition.add(project::Column::IsPublic.eq(true)); } let total_count = project::Entity::find() .filter(condition.clone()) .count(&self.db) .await?; let project_list = project::Entity::find() .filter(condition) .order_by_desc(project::Column::CreatedAt) .limit(per_page) .offset(offset) .all(&self.db) .await?; let user_project_memberships: std::collections::HashSet = if let Some(uid) = current_user_uid { project_members::Entity::find() .filter(project_members::Column::User.eq(uid)) .select_only() .column(project_members::Column::Project) .into_tuple::() .all(&self.db) .await? .into_iter() .collect() } else { std::collections::HashSet::new() }; let mut project_infos: Vec = Vec::new(); for project in project_list { let member_count = project_members::Entity::find() .filter(project_members::Column::Project.eq(project.id)) .count(&self.db) .await?; let is_member = user_project_memberships.contains(&project.id); project_infos.push(UserProjectInfo { uid: project.id, name: project.name, display_name: project.display_name, avatar_url: project.avatar_url, description: project.description, is_public: project.is_public, created_at: project.created_at, updated_at: project.updated_at, member_count: member_count as i64, is_member, }); } Ok(UserProjectsResponse { username: target_user.username, projects: project_infos, total_count, }) } pub async fn get_current_user_projects( &self, context: Session, query: UserProjectsQuery, ) -> Result { let user_uid = context.user().ok_or(AppError::Unauthorized)?; let user = user::Entity::find() .filter(user::Column::Uid.eq(user_uid)) .one(&self.db) .await? .ok_or(AppError::UserNotFound)?; self.get_user_projects(context, user.username, query).await } }