use serde::Serialize; use uuid::Uuid; use crate::error::AppError; use crate::models::base_info::UserBaseInfo; use crate::service::RepoService; use crate::session::Session; use super::util::clamp_limit_offset; #[derive(Serialize, utoipa::ToSchema)] pub struct Contributor { pub user: Option, pub commits: i64, } impl RepoService { pub async fn repo_contributors( &self, ctx: &Session, wk_name: &str, repo_name: &str, limit: i64, offset: i64, ) -> Result, AppError> { let user_uid = ctx.user().ok_or(AppError::Unauthorized)?; let repo = self.resolve_repo(wk_name, repo_name).await?; self.ensure_repo_readable(user_uid, &repo).await?; let (limit, offset) = clamp_limit_offset(limit, offset); #[derive(sqlx::FromRow)] struct ContributorRow { pusher_id: Uuid, commits: i64, } let rows = sqlx::query_as::<_, ContributorRow>( "SELECT pusher_id, COUNT(*) as commits FROM repo_push_commit \ WHERE repo_id = $1 AND branch_name = $2 \ GROUP BY pusher_id ORDER BY commits DESC LIMIT $3 OFFSET $4", ) .bind(repo.id) .bind(&repo.default_branch) .bind(limit) .bind(offset) .fetch_all(self.ctx.db.reader()) .await .map_err(AppError::Database)?; let user_ids: Vec = rows.iter().map(|r| r.pusher_id).collect(); let users = crate::models::base_info::resolve_users(&self.ctx.db, &user_ids).await?; Ok(rows .into_iter() .map(|r| Contributor { user: users.get(&r.pusher_id).cloned(), commits: r.commits, }) .collect()) } }