use uuid::Uuid; use crate::error::AppError; use crate::models::common::Role; use crate::models::repos::RepoStats; use crate::service::RepoService; use crate::session::Session; impl RepoService { pub async fn repo_stats( &self, ctx: &Session, wk_name: &str, repo_name: &str, ) -> Result { let user_uid = ctx.user().ok_or(AppError::Unauthorized)?; let repo = self.resolve_repo(wk_name, repo_name).await?; let repo_id = repo.id; self.ensure_repo_readable(user_uid, &repo).await?; self.ensure_repo_stats(repo_id).await } pub async fn repo_refresh_stats( &self, ctx: &Session, wk_name: &str, repo_name: &str, ) -> Result { let user_uid = ctx.user().ok_or(AppError::Unauthorized)?; let repo = self.resolve_repo(wk_name, repo_name).await?; let repo_id = repo.id; self.ensure_repo_role_at_least(user_uid, &repo, Role::Admin) .await?; let branches_count = sqlx::query_scalar::<_, i64>("SELECT COUNT(*) FROM repo_branch WHERE repo_id = $1") .bind(repo_id) .fetch_one(self.ctx.db.reader()) .await .map_err(AppError::Database)?; let tags_count = sqlx::query_scalar::<_, i64>("SELECT COUNT(*) FROM repo_tag WHERE repo_id = $1") .bind(repo_id) .fetch_one(self.ctx.db.reader()) .await .map_err(AppError::Database)?; let releases_count = sqlx::query_scalar::<_, i64>( "SELECT COUNT(*) FROM repo_release WHERE repo_id = $1 AND deleted_at IS NULL", ) .bind(repo_id) .fetch_one(self.ctx.db.reader()) .await .map_err(AppError::Database)?; let stars_count = sqlx::query_scalar::<_, i64>("SELECT COUNT(*) FROM repo_star WHERE repo_id = $1") .bind(repo_id) .fetch_one(self.ctx.db.reader()) .await .map_err(AppError::Database)?; let watchers_count = sqlx::query_scalar::<_, i64>("SELECT COUNT(*) FROM repo_watch WHERE repo_id = $1") .bind(repo_id) .fetch_one(self.ctx.db.reader()) .await .map_err(AppError::Database)?; let forks_count = sqlx::query_scalar::<_, i64>( "SELECT COUNT(*) FROM repo WHERE forked_from_repo_id = $1 AND deleted_at IS NULL", ) .bind(repo_id) .fetch_one(self.ctx.db.reader()) .await .map_err(AppError::Database)?; let open_issues_count: i64 = sqlx::query_scalar( "SELECT COUNT(*) FROM issue i \ JOIN issue_repo_relation irr ON irr.issue_id = i.id \ WHERE irr.repo_id = $1 AND i.deleted_at IS NULL AND i.state = 'open'", ) .bind(repo_id) .fetch_one(self.ctx.db.reader()) .await .map_err(AppError::Database)?; let open_prs_count = sqlx::query_scalar::<_, i64>( "SELECT COUNT(*) FROM pull_request WHERE repo_id = $1 AND deleted_at IS NULL AND state = 'open'", ) .bind(repo_id) .fetch_one(self.ctx.db.reader()) .await .map_err(AppError::Database)?; let now = chrono::Utc::now(); let result = sqlx::query_as::<_, RepoStats>( "UPDATE repo_stats SET stars_count = $1, watchers_count = $2, forks_count = $3, \ branches_count = $4, tags_count = $5, releases_count = $6, \ open_issues_count = $7, open_pull_requests_count = $8, updated_at = $9 \ WHERE repo_id = $10 RETURNING repo_id, stars_count, watchers_count, forks_count, branches_count, tags_count, commits_count, releases_count, open_issues_count, open_pull_requests_count, size_bytes, last_push_at, updated_at", ) .bind(stars_count) .bind(watchers_count) .bind(forks_count) .bind(branches_count) .bind(tags_count) .bind(releases_count) .bind(open_issues_count) .bind(open_prs_count) .bind(now) .bind(repo_id) .fetch_one(self.ctx.db.writer()) .await .map_err(AppError::Database)?; Ok(result) } async fn ensure_repo_stats(&self, repo_id: Uuid) -> Result { if let Some(stats) = sqlx::query_as::<_, RepoStats>( "SELECT repo_id, stars_count, watchers_count, forks_count, branches_count, tags_count, commits_count, releases_count, open_issues_count, open_pull_requests_count, size_bytes, last_push_at, updated_at FROM repo_stats WHERE repo_id = $1", ) .bind(repo_id) .fetch_optional(self.ctx.db.reader()) .await .map_err(AppError::Database)? { return Ok(stats); } sqlx::query( "INSERT INTO repo_stats (repo_id, stars_count, watchers_count, forks_count, branches_count, \ tags_count, commits_count, releases_count, open_issues_count, open_pull_requests_count, \ size_bytes, updated_at) \ VALUES ($1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, $2) ON CONFLICT (repo_id) DO NOTHING", ) .bind(repo_id) .bind(chrono::Utc::now()) .execute(self.ctx.db.writer()) .await .map_err(AppError::Database)?; sqlx::query_as::<_, RepoStats>( "SELECT repo_id, stars_count, watchers_count, forks_count, branches_count, tags_count, commits_count, releases_count, open_issues_count, open_pull_requests_count, size_bytes, last_push_at, updated_at FROM repo_stats WHERE repo_id = $1", ) .bind(repo_id) .fetch_one(self.ctx.db.reader()) .await .map_err(AppError::Database) } }