| 1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
use crate::db::{self, accounts::Account, repos::RepoRow, Pool}; |
| 6 |
use hub_api::Visibility; |
| 7 |
|
| 8 |
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] |
| 9 |
#[repr(u8)] |
| 10 |
pub enum Role { |
| 11 |
Read = 1, |
| 12 |
Triage = 2, |
| 13 |
Write = 3, |
| 14 |
Maintain = 4, |
| 15 |
Admin = 5, |
| 16 |
} |
| 17 |
|
| 18 |
impl Role { |
| 19 |
pub fn parse(s: &str) -> Option<Role> { |
| 20 |
Some(match s { |
| 21 |
"read" => Role::Read, |
| 22 |
"triage" => Role::Triage, |
| 23 |
"write" => Role::Write, |
| 24 |
"maintain" => Role::Maintain, |
| 25 |
"admin" => Role::Admin, |
| 26 |
_ => return None, |
| 27 |
}) |
| 28 |
} |
| 29 |
|
| 30 |
pub fn as_str(self) -> &'static str { |
| 31 |
match self { |
| 32 |
Role::Read => "read", |
| 33 |
Role::Triage => "triage", |
| 34 |
Role::Write => "write", |
| 35 |
Role::Maintain => "maintain", |
| 36 |
Role::Admin => "admin", |
| 37 |
} |
| 38 |
} |
| 39 |
|
| 40 |
pub const ALL: [Role; 5] = [Role::Read, Role::Triage, Role::Write, Role::Maintain, Role::Admin]; |
| 41 |
} |
| 42 |
|
| 43 |
|
| 44 |
#[derive(Clone, Copy, Debug, PartialEq, Eq)] |
| 45 |
pub enum Access { |
| 46 |
Read, |
| 47 |
Write, |
| 48 |
Maintain, |
| 49 |
Admin, |
| 50 |
} |
| 51 |
|
| 52 |
impl Access { |
| 53 |
fn needs(self) -> Role { |
| 54 |
match self { |
| 55 |
Access::Read => Role::Read, |
| 56 |
Access::Write => Role::Write, |
| 57 |
Access::Maintain => Role::Maintain, |
| 58 |
Access::Admin => Role::Admin, |
| 59 |
} |
| 60 |
} |
| 61 |
} |
| 62 |
|
| 63 |
|
| 64 |
|
| 65 |
pub async fn role_for(pool: &Pool, account: Option<&Account>, repo: &RepoRow) -> sqlx::Result<Option<Role>> { |
| 66 |
let visibility = repo.visibility(); |
| 67 |
let Some(account) = account else { |
| 68 |
return Ok(if visibility == Visibility::Public { Some(Role::Read) } else { None }); |
| 69 |
}; |
| 70 |
if account.banned { |
| 71 |
return Ok(None); |
| 72 |
} |
| 73 |
if account.is_admin || account.username.eq_ignore_ascii_case(&repo.owner) { |
| 74 |
return Ok(Some(Role::Admin)); |
| 75 |
} |
| 76 |
let mut best: Option<Role> = None; |
| 77 |
if let Some(org) = db::accounts::org_by_name(pool, &repo.owner).await? { |
| 78 |
match db::accounts::org_role(pool, org.id, account.id).await?.as_deref() { |
| 79 |
Some("owner") => return Ok(Some(Role::Admin)), |
| 80 |
Some(_) => best = Role::parse(&org.default_role), |
| 81 |
None => {} |
| 82 |
} |
| 83 |
} |
| 84 |
if let Some(r) = db::accounts::collaborator_role(pool, repo.id, account.id).await?.as_deref().and_then(Role::parse) { |
| 85 |
best = Some(best.map_or(r, |b| b.max(r))); |
| 86 |
} |
| 87 |
if best.is_none() && visibility != Visibility::Private { |
| 88 |
best = Some(Role::Read); |
| 89 |
} |
| 90 |
Ok(best) |
| 91 |
} |
| 92 |
|
| 93 |
|
| 94 |
pub fn allows(role: Option<Role>, access: Access) -> bool { |
| 95 |
role.is_some_and(|r| r >= access.needs()) |
| 96 |
} |
| 97 |
|
| 98 |
|
| 99 |
pub async fn may_create_under(pool: &Pool, account: &Account, owner: &str) -> sqlx::Result<bool> { |
| 100 |
if account.banned { |
| 101 |
return Ok(false); |
| 102 |
} |
| 103 |
if account.username.eq_ignore_ascii_case(owner) || account.is_admin { |
| 104 |
return Ok(true); |
| 105 |
} |
| 106 |
match db::accounts::org_by_name(pool, owner).await? { |
| 107 |
Some(org) => Ok(db::accounts::org_role(pool, org.id, account.id).await?.is_some()), |
| 108 |
None => Ok(false), |
| 109 |
} |
| 110 |
} |
| 111 |
|
| 112 |
|
| 113 |
pub async fn is_org_owner(pool: &Pool, account: &Account, org_id: i64) -> sqlx::Result<bool> { |
| 114 |
if account.is_admin { |
| 115 |
return Ok(true); |
| 116 |
} |
| 117 |
Ok(db::accounts::org_role(pool, org_id, account.id).await?.as_deref() == Some("owner")) |
| 118 |
} |