upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/nostr/builder.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/nostr/builder.rs')
-rw-r--r--src/nostr/builder.rs135
1 files changed, 120 insertions, 15 deletions
diff --git a/src/nostr/builder.rs b/src/nostr/builder.rs
index 34014db..629c111 100644
--- a/src/nostr/builder.rs
+++ b/src/nostr/builder.rs
@@ -98,6 +98,62 @@ impl Nip34WritePolicy {
98 self.ctx.set_local_relay(relay); 98 self.ctx.set_local_relay(relay);
99 } 99 }
100 100
101 /// Extract repository identifier from event's 'd' tag.
102 ///
103 /// Used for structured logging when parsing fails - we try to extract
104 /// the identifier even if full parsing failed.
105 fn extract_identifier_from_event(event: &Event) -> String {
106 use nostr_relay_builder::prelude::TagKind;
107 event
108 .tags
109 .iter()
110 .find(|t| t.kind() == TagKind::d())
111 .and_then(|t| t.content())
112 .map(|s| s.to_string())
113 .unwrap_or_else(|| "unknown".to_string())
114 }
115
116 /// Extract ALL repository identifiers from PR event's 'a' tags.
117 ///
118 /// PR events can reference multiple repositories via multiple 'a' tags
119 /// (e.g., when there are multiple maintainers). Each tag has format
120 /// `30617:<owner_pubkey>:<identifier>`.
121 ///
122 /// Returns a vector of unique identifiers, or `["unknown"]` if none found.
123 fn extract_repos_from_pr_event(event: &Event) -> Vec<String> {
124 let repos: Vec<String> = event
125 .tags
126 .iter()
127 .filter_map(|tag| {
128 let tag_vec = tag.clone().to_vec();
129 if tag_vec.len() >= 2 && tag_vec[0] == "a" && tag_vec[1].starts_with("30617:") {
130 // Format: 30617:<owner_pubkey>:<identifier>
131 let parts: Vec<&str> = tag_vec[1].split(':').collect();
132 if parts.len() >= 3 {
133 Some(parts[2].to_string())
134 } else {
135 None
136 }
137 } else {
138 None
139 }
140 })
141 .collect();
142
143 // Deduplicate while preserving order
144 let mut seen = std::collections::HashSet::new();
145 let unique_repos: Vec<String> = repos
146 .into_iter()
147 .filter(|r| seen.insert(r.clone()))
148 .collect();
149
150 if unique_repos.is_empty() {
151 vec!["unknown".to_string()]
152 } else {
153 unique_repos
154 }
155 }
156
101 /// Handle repository announcement event 157 /// Handle repository announcement event
102 async fn handle_announcement(&self, event: &Event) -> WritePolicyResult { 158 async fn handle_announcement(&self, event: &Event) -> WritePolicyResult {
103 let event_id_str = event.id.to_bech32().unwrap_or_else(|_| event.id.to_hex()); 159 let event_id_str = event.id.to_bech32().unwrap_or_else(|_| event.id.to_hex());
@@ -129,10 +185,18 @@ impl Nip34WritePolicy {
129 WritePolicyResult::Accept 185 WritePolicyResult::Accept
130 } 186 }
131 Err(e) => { 187 Err(e) => {
188 let npub = event.pubkey.to_bech32().unwrap_or_else(|_| event.pubkey.to_hex());
189 let event_id_short = &event.id.to_hex()[..12];
190 // Try to extract repo identifier from 'd' tag even if parsing failed
191 let repo = Self::extract_identifier_from_event(event);
192 // Structured log for migration scripts
132 tracing::warn!( 193 tracing::warn!(
133 "Failed to parse repository announcement {}: {}", 194 "[PARSE_FAIL] kind={} event_id={}... reason=\"{}\" repo={} npub={}",
134 event_id_str, 195 event.kind.as_u16(),
135 e 196 event_id_short,
197 e,
198 repo,
199 npub
136 ); 200 );
137 WritePolicyResult::reject(format!("Failed to parse announcement: {}", e)) 201 WritePolicyResult::reject(format!("Failed to parse announcement: {}", e))
138 } 202 }
@@ -157,10 +221,18 @@ impl Nip34WritePolicy {
157 WritePolicyResult::Accept 221 WritePolicyResult::Accept
158 } 222 }
159 Err(e) => { 223 Err(e) => {
224 let npub = event.pubkey.to_bech32().unwrap_or_else(|_| event.pubkey.to_hex());
225 let event_id_short = &event.id.to_hex()[..12];
226 // Try to extract repo identifier from 'd' tag even if parsing failed
227 let repo = Self::extract_identifier_from_event(event);
228 // Structured log for migration scripts
160 tracing::warn!( 229 tracing::warn!(
161 "Failed to parse maintainer announcement {}: {}", 230 "[PARSE_FAIL] kind={} event_id={}... reason=\"{}\" repo={} npub={}",
162 event_id_str, 231 event.kind.as_u16(),
163 e 232 event_id_short,
233 e,
234 repo,
235 npub
164 ); 236 );
165 WritePolicyResult::reject(format!("Failed to parse announcement: {}", e)) 237 WritePolicyResult::reject(format!("Failed to parse announcement: {}", e))
166 } 238 }
@@ -183,8 +255,6 @@ impl Nip34WritePolicy {
183 /// * `event` - The state event to validate 255 /// * `event` - The state event to validate
184 /// * `is_synced` - True if this event came from proactive sync (vs user-submitted) 256 /// * `is_synced` - True if this event came from proactive sync (vs user-submitted)
185 async fn handle_state(&self, event: &Event, is_synced: bool) -> WritePolicyResult { 257 async fn handle_state(&self, event: &Event, is_synced: bool) -> WritePolicyResult {
186 let event_id_str = event.id.to_bech32().unwrap_or_else(|_| event.id.to_hex());
187
188 match self.state_policy.validate(event) { 258 match self.state_policy.validate(event) {
189 StateResult::Accept => { 259 StateResult::Accept => {
190 // Process state alignment asynchronously 260 // Process state alignment asynchronously
@@ -195,7 +265,19 @@ impl Nip34WritePolicy {
195 { 265 {
196 Ok(poilicy_result) => poilicy_result, 266 Ok(poilicy_result) => poilicy_result,
197 Err(e) => { 267 Err(e) => {
198 tracing::warn!("Failed to process state event {}: {}", event_id_str, e); 268 let npub = event.pubkey.to_bech32().unwrap_or_else(|_| event.pubkey.to_hex());
269 let event_id_short = &event.id.to_hex()[..12];
270 // Try to extract repo identifier from 'd' tag even if parsing failed
271 let repo = Self::extract_identifier_from_event(event);
272 // Structured log for migration scripts
273 tracing::warn!(
274 "[PARSE_FAIL] kind={} event_id={}... reason=\"{}\" repo={} npub={}",
275 event.kind.as_u16(),
276 event_id_short,
277 e,
278 repo,
279 npub
280 );
199 // reject if processing failed 281 // reject if processing failed
200 WritePolicyResult::Reject { 282 WritePolicyResult::Reject {
201 status: false, 283 status: false,
@@ -205,7 +287,19 @@ impl Nip34WritePolicy {
205 } 287 }
206 } 288 }
207 StateResult::Reject(reason) => { 289 StateResult::Reject(reason) => {
208 tracing::warn!("Rejected repository state {}: {}", event_id_str, reason); 290 let npub = event.pubkey.to_bech32().unwrap_or_else(|_| event.pubkey.to_hex());
291 let event_id_short = &event.id.to_hex()[..12];
292 // Try to extract repo identifier from 'd' tag even if parsing failed
293 let repo = Self::extract_identifier_from_event(event);
294 // Structured log for migration scripts
295 tracing::warn!(
296 "[PARSE_FAIL] kind={} event_id={}... reason=\"{}\" repo={} npub={}",
297 event.kind.as_u16(),
298 event_id_short,
299 reason,
300 repo,
301 npub
302 );
209 WritePolicyResult::reject(reason) 303 WritePolicyResult::reject(reason)
210 } 304 }
211 } 305 }
@@ -323,11 +417,22 @@ impl Nip34WritePolicy {
323 } 417 }
324 Err(e) => { 418 Err(e) => {
325 // Error checking git data - reject event 419 // Error checking git data - reject event
326 tracing::warn!( 420 let npub = event.pubkey.to_bech32().unwrap_or_else(|_| event.pubkey.to_hex());
327 "Failed to check git data for PR event {}: {}", 421 let event_id_short = &event.id.to_hex()[..12];
328 event_id_str, 422 // Extract ALL repo identifiers from 'a' tags for PR events
329 e 423 // (PR events can reference multiple repos when there are multiple maintainers)
330 ); 424 let repos = Self::extract_repos_from_pr_event(event);
425 // Structured log for migration scripts - log once per repo
426 for repo in &repos {
427 tracing::warn!(
428 "[PARSE_FAIL] kind={} event_id={}... reason=\"git data check failed: {}\" repo={} npub={}",
429 event.kind.as_u16(),
430 event_id_short,
431 e,
432 repo,
433 npub
434 );
435 }
331 WritePolicyResult::reject(format!("Failed to check git data: {}", e)) 436 WritePolicyResult::reject(format!("Failed to check git data: {}", e))
332 } 437 }
333 } 438 }