106 lines
3.4 KiB
Rust
106 lines
3.4 KiB
Rust
//! 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<Option<Self>, 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<Option<Self>, 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
|
|
}
|
|
|
|
/// 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<bool, sqlx::Error> {
|
|
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<Option<Role>, sqlx::Error> {
|
|
if owner_id == user_id {
|
|
return Ok(Some(Role::Owner));
|
|
}
|
|
let role_str: Option<String> = 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::<Role>().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<bool, sqlx::Error> {
|
|
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,
|
|
})
|
|
}
|
|
}
|