Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
218 changes: 218 additions & 0 deletions crates/buzz-cli/src/commands/issues.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
use crate::client::BuzzClient;
use crate::error::CliError;
use crate::validate::{read_or_stdin, sdk_err, validate_hex64, validate_repo_id};
use buzz_sdk::{GitIssueMeta, GitRepoCoord, GitStatusMeta};

// ---------------------------------------------------------------------------
// Create issue — publish kind:1621
// ---------------------------------------------------------------------------

pub async fn cmd_create_issue(
client: &BuzzClient,
repo_owner: &str,
repo_id: &str,
subject: &str,
content: &str,
labels: &[String],
to: &[String],
) -> Result<(), CliError> {
validate_hex64(repo_owner)?;
validate_repo_id(repo_id)?;
let body = read_or_stdin(content)?;

let meta = GitIssueMeta {
labels: labels.to_vec(),
recipients: to.to_vec(),
};

let repo = GitRepoCoord {
owner: repo_owner.to_string(),
id: repo_id.to_string(),
};

let builder = buzz_sdk::build_git_issue(&repo, subject, &body, &meta).map_err(sdk_err)?;
let event = client.sign_event(builder)?;
let resp = client.submit_event(event).await?;
println!("{resp}");
Ok(())
}

// ---------------------------------------------------------------------------
// Get issue — query kind:1621 by event id
// ---------------------------------------------------------------------------

pub async fn cmd_get_issue(client: &BuzzClient, event: &str) -> Result<(), CliError> {
validate_hex64(event)?;
let filter = serde_json::json!({
"kinds": [1621],
"ids": [event]
});
let resp = client.query(&filter).await?;
println!("{resp}");
Ok(())
}

// ---------------------------------------------------------------------------
// List issues — query kind:1621 by repo coordinate, with optional filters
// ---------------------------------------------------------------------------

pub async fn cmd_list_issues(
client: &BuzzClient,
repo_owner: &str,
repo_id: &str,
author: Option<&str>,
label: Option<&str>,
limit: Option<u32>,
) -> Result<(), CliError> {
validate_hex64(repo_owner)?;
validate_repo_id(repo_id)?;

let a_value = format!("30617:{repo_owner}:{repo_id}");
let mut filter = serde_json::json!({
"kinds": [1621],
"#a": [a_value]
});

if let Some(pk) = author {
validate_hex64(pk)?;
filter["authors"] = serde_json::json!([pk]);
}
if let Some(l) = label {
filter["#t"] = serde_json::json!([l]);
}
if let Some(n) = limit {
filter["limit"] = serde_json::json!(n);
}

let resp = client.query(&filter).await?;
println!("{resp}");
Ok(())
}

// ---------------------------------------------------------------------------
// Status — publish kind:1630/1631/1632/1633 against an issue
// ---------------------------------------------------------------------------

#[allow(clippy::too_many_arguments)]
pub async fn cmd_issue_status(
client: &BuzzClient,
issue: &str,
status: &str,
content: Option<&str>,
repo_owner: Option<&str>,
repo_id: Option<&str>,
euc: Option<&str>,
to: &[String],
) -> Result<(), CliError> {
validate_hex64(issue)?;
let status = crate::commands::patches::parse_status(status)?;
let body = match content {
Some(c) => read_or_stdin(c)?,
None => String::new(),
};

let repo = match (repo_owner, repo_id) {
(Some(owner), Some(id)) => {
validate_hex64(owner)?;
validate_repo_id(id)?;
Some(GitRepoCoord {
owner: owner.to_string(),
id: id.to_string(),
})
}
(None, None) => None,
_ => {
return Err(CliError::Usage(
"--repo-owner and --repo-id must be given together".into(),
))
}
};

// Mirrors `buzz patches status`: default a `p` tag to the repo owner
// for discoverability, plus a `--to` escape hatch for the issue author
// or anyone else who should be notified of the status change.
let mut recipients = Vec::new();
if let Some(ref repo) = repo {
recipients.push(repo.owner.clone());
}
for recipient in to {
validate_hex64(recipient)?;
if !recipients.contains(recipient) {
recipients.push(recipient.clone());
}
}

let meta = GitStatusMeta {
root_event: issue.to_string(),
accepted_revision_root: None,
repo,
euc: euc.map(str::to_string),
recipients,
applied_patches: vec![],
merge_commit: None,
applied_as_commits: vec![],
};

let builder = buzz_sdk::build_git_status(status, &body, &meta).map_err(sdk_err)?;
let event = client.sign_event(builder)?;
let resp = client.submit_event(event).await?;
println!("{resp}");
Ok(())
}

// ---------------------------------------------------------------------------
// Dispatch
// ---------------------------------------------------------------------------

pub async fn dispatch(cmd: crate::IssuesCmd, client: &BuzzClient) -> Result<(), CliError> {
use crate::IssuesCmd;
match cmd {
IssuesCmd::Create {
repo_owner,
repo_id,
title,
content,
label,
to,
} => cmd_create_issue(client, &repo_owner, &repo_id, &title, &content, &label, &to).await,
IssuesCmd::Get { event } => cmd_get_issue(client, &event).await,
IssuesCmd::List {
repo_owner,
repo_id,
author,
label,
limit,
} => {
cmd_list_issues(
client,
&repo_owner,
&repo_id,
author.as_deref(),
label.as_deref(),
limit,
)
.await
}
IssuesCmd::Status {
issue,
status,
content,
repo_owner,
repo_id,
euc,
to,
} => {
cmd_issue_status(
client,
&issue,
&status,
content.as_deref(),
repo_owner.as_deref(),
repo_id.as_deref(),
euc.as_deref(),
&to,
)
.await
}
}
}
2 changes: 2 additions & 0 deletions crates/buzz-cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ pub mod channels;
pub mod dms;
pub mod emoji;
pub mod feed;
pub mod issues;
pub mod mem;
pub mod messages;
pub mod notes;
pub mod pack;
pub mod patches;
pub mod reactions;
pub mod repos;
pub mod social;
Expand Down
Loading