upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/lib/git_events.rs
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2026-03-05 11:28:36 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2026-03-05 11:28:36 +0000
commit0e493c455a0345c206dd1c5b0dfb5322b8a4e3e9 (patch)
tree105517fc7a592e469a0f72667f9b364895052287 /src/lib/git_events.rs
parent2f2819cc2365be07fedfd35ab3654b3607e29e76 (diff)
feat(labels): fetch and apply NIP-32 kind-1985 label events
- Add KIND_LABEL (kind 1985) constant to git_events.rs - Add get_labels() merging inline t-tags with external kind-1985 events, gating each on author-or-maintainer permission - Extend get_fetch_filters() to request kind-1985 events for all known issue and proposal IDs - Track label event counts in FetchReport (field + Display + consolidation) - Update issue_list.rs and list.rs to fetch label events from cache and pass them through get_labels() instead of reading t-tags inline
Diffstat (limited to 'src/lib/git_events.rs')
-rw-r--r--src/lib/git_events.rs88
1 files changed, 88 insertions, 0 deletions
diff --git a/src/lib/git_events.rs b/src/lib/git_events.rs
index 7c5dda2..a5aef12 100644
--- a/src/lib/git_events.rs
+++ b/src/lib/git_events.rs
@@ -91,6 +91,9 @@ pub const KIND_PULL_REQUEST_UPDATE: Kind = Kind::Custom(1619);
91pub const KIND_USER_GRASP_LIST: Kind = Kind::Custom(10317); 91pub const KIND_USER_GRASP_LIST: Kind = Kind::Custom(10317);
92/// NIP-22 comment (kind 1111) — threaded comments on any event. 92/// NIP-22 comment (kind 1111) — threaded comments on any event.
93pub const KIND_COMMENT: Kind = Kind::Custom(1111); 93pub const KIND_COMMENT: Kind = Kind::Custom(1111);
94/// NIP-32 label event (kind 1985) — applies hashtag labels to an existing
95/// event. Used to add labels to issues, patches and PRs after the fact.
96pub const KIND_LABEL: Kind = Kind::Custom(1985);
94 97
95pub fn event_is_patch_set_root(event: &Event) -> bool { 98pub fn event_is_patch_set_root(event: &Event) -> bool {
96 event.kind.eq(&Kind::GitPatch) 99 event.kind.eq(&Kind::GitPatch)
@@ -975,6 +978,91 @@ pub fn is_event_proposal_root_for_branch(
975 )) 978 ))
976} 979}
977 980
981/// Compute the effective set of labels for `event`.
982///
983/// Labels come from two sources, both subject to the same permission check:
984///
985/// 1. `t` tags on the event itself (self-reported by the event author).
986/// 2. NIP-32 kind-1985 label events in `all_label_events` that reference
987/// `event` via a lowercase `e` tag and carry `["L", "#t"]` +
988/// `["l", "<value>", "#t"]` tags.
989///
990/// A label is only applied when the author of the source event (the original
991/// event for inline `t` tags, or the kind-1985 event for external labels) is
992/// either the author of `event` itself or one of the repository maintainers.
993pub fn get_labels(
994 event: &Event,
995 repo_ref: &RepoRef,
996 all_label_events: &[Event],
997) -> Vec<String> {
998 let is_permitted = |pubkey: &PublicKey| -> bool {
999 pubkey.eq(&event.pubkey) || repo_ref.maintainers.contains(pubkey)
1000 };
1001
1002 // 1. Inline `t` tags on the event itself — only if the event author is
1003 // permitted (they always are, since they authored the event, but we
1004 // keep the check symmetric with the external-label path).
1005 let mut labels: Vec<String> = if is_permitted(&event.pubkey) {
1006 event
1007 .tags
1008 .iter()
1009 .filter(|t| {
1010 let s = t.as_slice();
1011 s.len() >= 2 && s[0].eq("t")
1012 })
1013 .map(|t| t.as_slice()[1].clone())
1014 .collect()
1015 } else {
1016 vec![]
1017 };
1018
1019 // 2. External NIP-32 kind-1985 label events.
1020 //
1021 // A valid label event must:
1022 // - be kind 1985
1023 // - reference `event` via a lowercase `e` tag
1024 // - have `["L", "#t"]` (namespace declaration)
1025 // - have at least one `["l", "<value>", "#t"]` tag
1026 // - be authored by a permitted pubkey
1027 let event_id_str = event.id.to_string();
1028 for label_event in all_label_events {
1029 if !label_event.kind.eq(&KIND_LABEL) {
1030 continue;
1031 }
1032 if !is_permitted(&label_event.pubkey) {
1033 continue;
1034 }
1035 // Must reference our event via a lowercase `e` tag.
1036 let references_event = label_event.tags.iter().any(|t| {
1037 let s = t.as_slice();
1038 s.len() >= 2 && s[0].eq("e") && s[1].eq(&event_id_str)
1039 });
1040 if !references_event {
1041 continue;
1042 }
1043 // Must declare the `#t` namespace.
1044 let has_namespace = label_event.tags.iter().any(|t| {
1045 let s = t.as_slice();
1046 s.len() >= 2 && s[0].eq("L") && s[1].eq("#t")
1047 });
1048 if !has_namespace {
1049 continue;
1050 }
1051 // Collect all `["l", "<value>", "#t"]` labels from this event.
1052 for tag in label_event.tags.iter() {
1053 let s = tag.as_slice();
1054 if s.len() >= 3 && s[0].eq("l") && s[2].eq("#t") && !s[1].is_empty() {
1055 let label = s[1].clone();
1056 if !labels.contains(&label) {
1057 labels.push(label);
1058 }
1059 }
1060 }
1061 }
1062
1063 labels
1064}
1065
978pub fn get_status( 1066pub fn get_status(
979 proposal: &Event, 1067 proposal: &Event,
980 repo_ref: &RepoRef, 1068 repo_ref: &RepoRef,