//! SQL query methods for the `Repo` entity. use sqlx::PgPool; use uuid::Uuid; use super::repo::Repo; use crate::models::common::{Role, Visibility}; impl Repo { /// Find a non-deleted repo by primary key. pub async fn find_by_name( pool: &PgPool, workspace_id: Uuid, name: &str, ) -> Result, sqlx::Error> { sqlx::query_as::<_, Repo>( "SELECT id, workspace_id, owner_id, name, description, default_branch, visibility, \ status, is_fork, forked_from_repo_id, storage_node_ids, \ primary_storage_node_id, storage_path, git_service, \ archived_at, created_at, updated_at, deleted_at \ FROM repo WHERE workspace_id = $1 AND name = $2 AND deleted_at IS NULL", ) .bind(workspace_id) .bind(name) .fetch_optional(pool) .await } pub async fn find_by_id(pool: &PgPool, id: Uuid) -> Result, sqlx::Error> { sqlx::query_as::<_, Repo>( r#"SELECT id, workspace_id, owner_id, name, description, default_branch, visibility, status, is_fork, forked_from_repo_id, storage_node_ids, primary_storage_node_id, storage_path, git_service, archived_at, created_at, updated_at, deleted_at FROM repo WHERE id = $1 AND deleted_at IS NULL"#, ) .bind(id) .fetch_optional(pool) .await } /// Find multiple non-deleted repos by their IDs in a single query. pub async fn find_by_ids( pool: &PgPool, ids: &[Uuid], ) -> Result, sqlx::Error> { if ids.is_empty() { return Ok(Vec::new()); } sqlx::query_as::<_, Repo>( r#"SELECT id, workspace_id, owner_id, name, description, default_branch, visibility, status, is_fork, forked_from_repo_id, storage_node_ids, primary_storage_node_id, storage_path, git_service, archived_at, created_at, updated_at, deleted_at FROM repo WHERE id = ANY($1) AND deleted_at IS NULL"#, ) .bind(ids) .fetch_all(pool) .await } /// Check if a user is an active member of a repo. pub async fn is_member( pool: &PgPool, repo_id: Uuid, user_id: Uuid, ) -> Result { sqlx::query_scalar::<_, bool>( r#"SELECT EXISTS( SELECT 1 FROM repo_member WHERE repo_id = $1 AND user_id = $2 AND status = 'active' )"#, ) .bind(repo_id) .bind(user_id) .fetch_one(pool) .await } /// Get the role of a user in a repo. /// Returns `Role::Owner` if the user is the repo owner but has no explicit member row. pub async fn user_role( pool: &PgPool, repo_id: Uuid, user_id: Uuid, owner_id: Uuid, ) -> Result, sqlx::Error> { if owner_id == user_id { return Ok(Some(Role::Owner)); } let role_str: Option = sqlx::query_scalar( r#"SELECT role FROM repo_member WHERE repo_id = $1 AND user_id = $2 AND status = 'active'"#, ) .bind(repo_id) .bind(user_id) .fetch_optional(pool) .await?; Ok(role_str.and_then(|r| r.parse::().ok())) } /// Check if the user can read the repo. /// Readable based on visibility + workspace membership + repo membership. pub async fn is_readable( pool: &PgPool, repo: &Repo, user_id: Uuid, ) -> Result { use crate::models::workspaces::Workspace; if repo.owner_id == user_id { return Ok(true); } let is_ws_member = Workspace::is_member(pool, repo.workspace_id, user_id).await?; let is_repo_member = Self::is_member(pool, repo.id, user_id).await?; Ok(match repo.visibility { Visibility::Public => true, Visibility::Internal => is_ws_member, Visibility::Private => is_ws_member && is_repo_member, _ => false, }) } }