diff --git a/crates/tangled-api/src/client.rs b/crates/tangled-api/src/client.rs index e291e99..82ec51f 100644 --- a/crates/tangled-api/src/client.rs +++ b/crates/tangled-api/src/client.rs @@ -926,6 +926,35 @@ impl TangledClient { Self::uri_rkey(&res.uri).ok_or_else(|| anyhow!("missing rkey in issue state uri")) } + pub async fn list_issue_states( + &self, + author_did: &str, + bearer: Option<&str>, + ) -> Result> { + #[derive(Deserialize)] + struct Item { + #[allow(dead_code)] + uri: String, + #[allow(dead_code)] + cid: Option, + value: IssueState, + } + #[derive(Deserialize)] + struct ListRes { + #[serde(default)] + records: Vec, + } + let params = vec![ + ("repo", author_did.to_string()), + ("collection", "sh.tangled.repo.issue.state".to_string()), + ("limit", "100".to_string()), + ]; + let res: ListRes = self + .get_json("com.atproto.repo.listRecords", ¶ms, bearer) + .await?; + Ok(res.records.into_iter().map(|it| it.value).collect()) + } + pub async fn get_pull_record( &self, author_did: &str, @@ -1365,6 +1394,12 @@ pub struct IssueRecord { pub issue: Issue, } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct IssueState { + pub issue: String, + pub state: String, +} + // Pull record value (subset) #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PullTarget { diff --git a/crates/tangled-api/src/lib.rs b/crates/tangled-api/src/lib.rs index 661c21a..4c85466 100644 --- a/crates/tangled-api/src/lib.rs +++ b/crates/tangled-api/src/lib.rs @@ -2,6 +2,6 @@ pub mod client; pub use client::TangledClient; pub use client::{ - CreateRepoOptions, DefaultBranch, Issue, IssueRecord, Language, Languages, Pull, PullRecord, - RepoRecord, Repository, Secret, + CreateRepoOptions, DefaultBranch, Issue, IssueRecord, IssueState, Language, Languages, Pull, + PullRecord, RepoRecord, Repository, Secret, }; diff --git a/crates/tangled-cli/src/commands/issue.rs b/crates/tangled-cli/src/commands/issue.rs index 6f2a377..28cc54c 100644 --- a/crates/tangled-cli/src/commands/issue.rs +++ b/crates/tangled-cli/src/commands/issue.rs @@ -34,13 +34,51 @@ async fn list(args: IssueListArgs) -> Result<()> { None }; - let items = client + let mut items = client .list_issues( &session.did, repo_filter_at.as_deref(), Some(session.access_jwt.as_str()), ) .await?; + + // Filter by state if requested + if let Some(state_filter) = &args.state { + let state_nsid = match state_filter.as_str() { + "open" => "sh.tangled.repo.issue.state.open", + "closed" => "sh.tangled.repo.issue.state.closed", + other => { + return Err(anyhow!(format!( + "unknown state '{}', expected 'open' or 'closed'", + other + ))) + } + }; + + // Fetch issue states + let states = client + .list_issue_states(&session.did, Some(session.access_jwt.as_str())) + .await?; + + // Build map of issue AT-URI to current state + let mut issue_states = std::collections::HashMap::new(); + for state in states { + issue_states.insert(state.issue, state.state); + } + + // Filter issues by state + items.retain(|it| { + let issue_at = format!( + "at://{}/sh.tangled.repo.issue/{}", + it.author_did, it.rkey + ); + match issue_states.get(&issue_at) { + Some(state) => state == state_nsid, + None => state_nsid == "sh.tangled.repo.issue.state.open", // default to open + } + }); + } + if items.is_empty() { println!("No issues found (showing only issues you created)"); } else {