upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src/lib/client.rs
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2026-03-04 13:54:32 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2026-03-04 14:50:01 +0000
commit6d9b0cc8fff65447849d0d55db177dcdff315c48 (patch)
tree604587b7e06149d89a36383eb2e4227043e6955d /src/lib/client.rs
parent3908abbbfc5e748dd168d22bf5e3ea6aae17de61 (diff)
feat: fetch and display NIP-22 comment counts on issues and proposals
Download kind-1111 NIP-22 comments from relays and show a comment count in the detail view of `ngit list <id>` and `ngit issue list <id>`. - git_events: add KIND_COMMENT constant (kind 1111) - client: import KIND_COMMENT; add `comments` field to FetchReport; route kind-1111 events into report.comments in process_fetched_events; consolidate comments across relay reports; display "N comment(s)" in FetchReport; add a #E-tagged kind-1111 filter in get_fetch_filters covering all known issue and proposal root IDs - issue_list: add get_comment_counts() to query the local cache for kind-1111 events by #E tag and count per issue; extend the filtered tuple with comment_count; show a CMTS column in the table, a "Comments: N" line in the detail view, and a "comments" field in JSON - list: add KIND_COMMENT import; add resolve_event_id() helper and get_comment_count_for_proposal() to look up the count for a single proposal from the local cache; pass comment_count into show_proposal_details(); display "Comments: N" in plain text and "comments" in JSON; align detail-view labels
Diffstat (limited to 'src/lib/client.rs')
-rw-r--r--src/lib/client.rs67
1 files changed, 51 insertions, 16 deletions
diff --git a/src/lib/client.rs b/src/lib/client.rs
index 1f46e3c..8501a1f 100644
--- a/src/lib/client.rs
+++ b/src/lib/client.rs
@@ -56,9 +56,9 @@ use crate::{
56 get_dirs, 56 get_dirs,
57 git::{Repo, RepoActions, get_git_config_item}, 57 git::{Repo, RepoActions, get_git_config_item},
58 git_events::{ 58 git_events::{
59 KIND_PULL_REQUEST, KIND_PULL_REQUEST_UPDATE, KIND_USER_GRASP_LIST, event_is_cover_letter, 59 KIND_COMMENT, KIND_PULL_REQUEST, KIND_PULL_REQUEST_UPDATE, KIND_USER_GRASP_LIST,
60 event_is_patch_set_root, event_is_revision_root, event_is_valid_pr_or_pr_update, 60 event_is_cover_letter, event_is_patch_set_root, event_is_revision_root,
61 status_kinds, 61 event_is_valid_pr_or_pr_update, status_kinds,
62 }, 62 },
63 login::{get_likely_logged_in_user, user::get_user_ref_from_cache}, 63 login::{get_likely_logged_in_user, user::get_user_ref_from_cache},
64 repo_ref::{RepoRef, normalize_grasp_server_url}, 64 repo_ref::{RepoRef, normalize_grasp_server_url},
@@ -1877,7 +1877,7 @@ async fn create_relays_request(
1877 }) 1877 })
1878} 1878}
1879 1879
1880#[allow(clippy::too_many_lines)] 1880#[allow(clippy::too_many_lines, clippy::too_many_arguments)]
1881async fn process_fetched_events( 1881async fn process_fetched_events(
1882 events: Vec<nostr::Event>, 1882 events: Vec<nostr::Event>,
1883 request: &FetchRequest, 1883 request: &FetchRequest,
@@ -1996,6 +1996,8 @@ async fn process_fetched_events(
1996 { 1996 {
1997 fresh_profiles.insert(event.pubkey); 1997 fresh_profiles.insert(event.pubkey);
1998 } 1998 }
1999 } else if event.kind.eq(&KIND_COMMENT) {
2000 report.comments.insert(event.id);
1999 } else if [Kind::RelayList, Kind::Metadata, KIND_USER_GRASP_LIST].contains(&event.kind) 2001 } else if [Kind::RelayList, Kind::Metadata, KIND_USER_GRASP_LIST].contains(&event.kind)
2000 { 2002 {
2001 if request.missing_contributor_profiles.contains(&event.pubkey) { 2003 if request.missing_contributor_profiles.contains(&event.pubkey) {
@@ -2023,9 +2025,7 @@ async fn process_fetched_events(
2023 for event in &events { 2025 for event in &events {
2024 if !request.existing_events.contains(&event.id) { 2026 if !request.existing_events.contains(&event.id) {
2025 let tagged_root_id = event.tags.iter().find_map(|t| { 2027 let tagged_root_id = event.tags.iter().find_map(|t| {
2026 if t.as_slice().len() > 1 2028 if t.as_slice().len() > 1 && (t.as_slice()[0].eq("E") || t.as_slice()[0].eq("e")) {
2027 && (t.as_slice()[0].eq("E") || t.as_slice()[0].eq("e"))
2028 {
2029 EventId::parse(&t.as_slice()[1]).ok() 2029 EventId::parse(&t.as_slice()[1]).ok()
2030 } else { 2030 } else {
2031 None 2031 None
@@ -2038,9 +2038,11 @@ async fn process_fetched_events(
2038 // as their parent (new issues/proposals already inflate the count). 2038 // as their parent (new issues/proposals already inflate the count).
2039 if let Some(root_id) = &tagged_root_id { 2039 if let Some(root_id) = &tagged_root_id {
2040 if report.issues.contains(root_id) { 2040 if report.issues.contains(root_id) {
2041 // status for a new issue in this batch — skip (counted via issues) 2041 // status for a new issue in this batch — skip (counted
2042 // via issues)
2042 } else if report.proposals.contains(root_id) { 2043 } else if report.proposals.contains(root_id) {
2043 // status for a new proposal in this batch — skip (counted via proposals) 2044 // status for a new proposal in this batch — skip
2045 // (counted via proposals)
2044 } else if request.issue_ids.contains(root_id) { 2046 } else if request.issue_ids.contains(root_id) {
2045 report.issue_statuses.insert(event.id); 2047 report.issue_statuses.insert(event.id);
2046 } else { 2048 } else {
@@ -2052,12 +2054,11 @@ async fn process_fetched_events(
2052 let not_tagged_with_new_proposal = tagged_root_id 2054 let not_tagged_with_new_proposal = tagged_root_id
2053 .as_ref() 2055 .as_ref()
2054 .is_none_or(|id| !report.proposals.contains(id)); 2056 .is_none_or(|id| !report.proposals.contains(id));
2055 if not_tagged_with_new_proposal { 2057 if not_tagged_with_new_proposal
2056 if (event.kind.eq(&Kind::GitPatch) && !event_is_patch_set_root(event)) 2058 && ((event.kind.eq(&Kind::GitPatch) && !event_is_patch_set_root(event))
2057 || event.kind.eq(&KIND_PULL_REQUEST_UPDATE) 2059 || event.kind.eq(&KIND_PULL_REQUEST_UPDATE))
2058 { 2060 {
2059 report.commits.insert(event.id); 2061 report.commits.insert(event.id);
2060 }
2061 } 2062 }
2062 } 2063 }
2063 } 2064 }
@@ -2117,6 +2118,9 @@ pub fn consolidate_fetch_reports(reports: Vec<Result<FetchReport>>) -> FetchRepo
2117 for c in relay_report.issue_statuses { 2118 for c in relay_report.issue_statuses {
2118 report.issue_statuses.insert(c); 2119 report.issue_statuses.insert(c);
2119 } 2120 }
2121 for c in relay_report.comments {
2122 report.comments.insert(c);
2123 }
2120 report.deletions += relay_report.deletions; 2124 report.deletions += relay_report.deletions;
2121 for c in relay_report.contributor_profiles { 2125 for c in relay_report.contributor_profiles {
2122 report.contributor_profiles.insert(c); 2126 report.contributor_profiles.insert(c);
@@ -2223,6 +2227,24 @@ pub fn get_fetch_filters(
2223 .kinds(status_kinds()), 2227 .kinds(status_kinds()),
2224 ] 2228 ]
2225 }, 2229 },
2230 // Fetch NIP-22 kind-1111 comments for issues and proposals (patches/PRs).
2231 // Comments use an uppercase `E` tag pointing to the root event ID.
2232 {
2233 let all_root_ids: HashSet<EventId> = issue_ids
2234 .iter()
2235 .chain(proposal_ids.iter())
2236 .copied()
2237 .collect();
2238 if all_root_ids.is_empty() {
2239 vec![]
2240 } else {
2241 vec![
2242 nostr::Filter::default()
2243 .custom_tags(SingleLetterTag::uppercase(Alphabet::E), all_root_ids)
2244 .kind(KIND_COMMENT),
2245 ]
2246 }
2247 },
2226 // Request kind-5 deletions for state events and repo announcements by 2248 // Request kind-5 deletions for state events and repo announcements by
2227 // their event ID (#e tag), as per NIP-09. The #a-tagged filter above 2249 // their event ID (#e tag), as per NIP-09. The #a-tagged filter above
2228 // covers addressable-event deletions; this covers the specific event IDs 2250 // covers addressable-event deletions; this covers the specific event IDs
@@ -2309,6 +2331,8 @@ pub struct FetchReport {
2309 statuses: HashSet<EventId>, 2331 statuses: HashSet<EventId>,
2310 issues: HashSet<EventId>, 2332 issues: HashSet<EventId>,
2311 issue_statuses: HashSet<EventId>, 2333 issue_statuses: HashSet<EventId>,
2334 /// NIP-22 kind-1111 comments against issues, patches, and PRs.
2335 comments: HashSet<EventId>,
2312 /// Count of kind-5 deletion events received (for display purposes). 2336 /// Count of kind-5 deletion events received (for display purposes).
2313 deletions: u32, 2337 deletions: u32,
2314 contributor_profiles: HashSet<PublicKey>, 2338 contributor_profiles: HashSet<PublicKey>,
@@ -2383,7 +2407,18 @@ impl Display for FetchReport {
2383 display_items.push(format!( 2407 display_items.push(format!(
2384 "{} issue status{}", 2408 "{} issue status{}",
2385 self.issue_statuses.len(), 2409 self.issue_statuses.len(),
2386 if self.issue_statuses.len() > 1 { "es" } else { "" }, 2410 if self.issue_statuses.len() > 1 {
2411 "es"
2412 } else {
2413 ""
2414 },
2415 ));
2416 }
2417 if !self.comments.is_empty() {
2418 display_items.push(format!(
2419 "{} comment{}",
2420 self.comments.len(),
2421 if self.comments.len() > 1 { "s" } else { "" },
2387 )); 2422 ));
2388 } 2423 }
2389 if self.deletions > 0 { 2424 if self.deletions > 0 {