feat(cli/org): create-collection, grant, revoke commands
This commit is contained in:
@@ -224,6 +224,99 @@ pub fn run_set_role(dir: &Path, member_id_prefix: &str, role: OrgRole) -> Result
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run_create_collection(dir: &Path, slug: &str, display_name: &str) -> Result<()> {
|
||||
let vault = crate::org_session::open_org_vault(Some(dir))?;
|
||||
let caller = vault.current_member()?;
|
||||
if !caller.role.can_manage_members() {
|
||||
anyhow::bail!("only owners and admins can create collections");
|
||||
}
|
||||
|
||||
let mut collections = vault.load_collections()?;
|
||||
if collections.contains_slug(slug) {
|
||||
anyhow::bail!("collection `{slug}` already exists");
|
||||
}
|
||||
if slug.is_empty() || slug.contains('/') || slug.contains('.') {
|
||||
anyhow::bail!("invalid slug `{slug}` — no slashes or dots, no empty string");
|
||||
}
|
||||
|
||||
collections.collections.push(CollectionDef {
|
||||
slug: slug.to_string(),
|
||||
display_name: display_name.to_string(),
|
||||
created_by: caller.member_id.clone(),
|
||||
created_at: relicario_core::now_unix(),
|
||||
});
|
||||
vault.save_collections(&collections)?;
|
||||
|
||||
let commit_msg = format!(
|
||||
"org: create collection \"{slug}\"\n\nRelicario-Actor: {} {}\nRelicario-Action: collection-create\nRelicario-Collection: {slug}",
|
||||
caller.display_name, caller.member_id.as_str()
|
||||
);
|
||||
crate::org_session::org_git_run(&vault.root, &["add", "collections.json"], "git add")?;
|
||||
crate::org_session::org_git_run(&vault.root, &["commit", "-m", &commit_msg], "git commit")?;
|
||||
|
||||
println!("Created collection `{slug}`");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run_grant(dir: &Path, member_id_prefix: &str, slug: &str) -> Result<()> {
|
||||
let vault = crate::org_session::open_org_vault(Some(dir))?;
|
||||
let caller = vault.current_member()?;
|
||||
if !caller.role.can_manage_members() {
|
||||
anyhow::bail!("only owners and admins can grant collection access");
|
||||
}
|
||||
|
||||
let collections = vault.load_collections()?;
|
||||
if !collections.contains_slug(slug) {
|
||||
anyhow::bail!("collection `{slug}` does not exist — create it first");
|
||||
}
|
||||
|
||||
let mut members = vault.load_members()?;
|
||||
let target_id = resolve_member_id(&members, member_id_prefix)?;
|
||||
let target = members.find_by_id_mut(&target_id).unwrap();
|
||||
if target.collections.contains(&slug.to_string()) {
|
||||
anyhow::bail!("member already has access to `{slug}`");
|
||||
}
|
||||
target.collections.push(slug.to_string());
|
||||
vault.save_members(&members)?;
|
||||
|
||||
let commit_msg = format!(
|
||||
"org: grant {slug} to {}\n\nRelicario-Actor: {} {}\nRelicario-Action: collection-grant\nRelicario-Collection: {slug}\nRelicario-Member: {}",
|
||||
target_id.as_str(), caller.display_name, caller.member_id.as_str(), target_id.as_str()
|
||||
);
|
||||
crate::org_session::org_git_run(&vault.root, &["add", "members.json"], "git add")?;
|
||||
crate::org_session::org_git_run(&vault.root, &["commit", "-m", &commit_msg], "git commit")?;
|
||||
|
||||
println!("Granted `{slug}` to {}", target_id.as_str());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run_revoke(dir: &Path, member_id_prefix: &str, slug: &str) -> Result<()> {
|
||||
let vault = crate::org_session::open_org_vault(Some(dir))?;
|
||||
let caller = vault.current_member()?;
|
||||
if !caller.role.can_manage_members() {
|
||||
anyhow::bail!("only owners and admins can revoke collection access");
|
||||
}
|
||||
|
||||
let mut members = vault.load_members()?;
|
||||
let target_id = resolve_member_id(&members, member_id_prefix)?;
|
||||
let target = members.find_by_id_mut(&target_id).unwrap();
|
||||
if !target.collections.contains(&slug.to_string()) {
|
||||
anyhow::bail!("member does not have access to `{slug}`");
|
||||
}
|
||||
target.collections.retain(|s| s != slug);
|
||||
vault.save_members(&members)?;
|
||||
|
||||
let commit_msg = format!(
|
||||
"org: revoke {slug} from {}\n\nRelicario-Actor: {} {}\nRelicario-Action: collection-revoke\nRelicario-Collection: {slug}\nRelicario-Member: {}",
|
||||
target_id.as_str(), caller.display_name, caller.member_id.as_str(), target_id.as_str()
|
||||
);
|
||||
crate::org_session::org_git_run(&vault.root, &["add", "members.json"], "git add")?;
|
||||
crate::org_session::org_git_run(&vault.root, &["commit", "-m", &commit_msg], "git commit")?;
|
||||
|
||||
println!("Revoked `{slug}` from {}", target_id.as_str());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Resolve a member_id prefix (or full ID) to a MemberId.
|
||||
fn resolve_member_id(members: &OrgMembers, prefix: &str) -> Result<MemberId> {
|
||||
let hits: Vec<_> = members.members.iter()
|
||||
@@ -264,4 +357,32 @@ mod tests {
|
||||
}
|
||||
assert_eq!(members.find_by_id(&id).unwrap().role, OrgRole::Admin);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn grant_adds_slug_to_member_collections() {
|
||||
let mut members = OrgMembers::new();
|
||||
let a = alice();
|
||||
let id = a.member_id.clone();
|
||||
members.members.push(a);
|
||||
|
||||
let m = members.find_by_id_mut(&id).unwrap();
|
||||
if !m.collections.contains(&"prod".to_string()) {
|
||||
m.collections.push("prod".to_string());
|
||||
}
|
||||
assert!(members.find_by_id(&id).unwrap().collections.contains(&"prod".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn revoke_removes_slug_from_member_collections() {
|
||||
let mut members = OrgMembers::new();
|
||||
let mut a = alice();
|
||||
a.collections = vec!["prod".into(), "dev".into()];
|
||||
let id = a.member_id.clone();
|
||||
members.members.push(a);
|
||||
|
||||
let m = members.find_by_id_mut(&id).unwrap();
|
||||
m.collections.retain(|s| s != "prod");
|
||||
assert!(!members.find_by_id(&id).unwrap().collections.contains(&"prod".to_string()));
|
||||
assert!(members.find_by_id(&id).unwrap().collections.contains(&"dev".to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user