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.rs165
1 files changed, 145 insertions, 20 deletions
diff --git a/src/nostr/builder.rs b/src/nostr/builder.rs
index d056e46..7a05348 100644
--- a/src/nostr/builder.rs
+++ b/src/nostr/builder.rs
@@ -102,6 +102,62 @@ impl Nip34WritePolicy {
102 self.ctx.set_local_relay(relay); 102 self.ctx.set_local_relay(relay);
103 } 103 }
104 104
105 /// Extract repository identifier from event's 'd' tag.
106 ///
107 /// Used for structured logging when parsing fails - we try to extract
108 /// the identifier even if full parsing failed.
109 fn extract_identifier_from_event(event: &Event) -> String {
110 use nostr_relay_builder::prelude::TagKind;
111 event
112 .tags
113 .iter()
114 .find(|t| t.kind() == TagKind::d())
115 .and_then(|t| t.content())
116 .map(|s| s.to_string())
117 .unwrap_or_else(|| "unknown".to_string())
118 }
119
120 /// Extract ALL repository identifiers from PR event's 'a' tags.
121 ///
122 /// PR events can reference multiple repositories via multiple 'a' tags
123 /// (e.g., when there are multiple maintainers). Each tag has format
124 /// `30617:<owner_pubkey>:<identifier>`.
125 ///
126 /// Returns a vector of unique identifiers, or `["unknown"]` if none found.
127 fn extract_repos_from_pr_event(event: &Event) -> Vec<String> {
128 let repos: Vec<String> = event
129 .tags
130 .iter()
131 .filter_map(|tag| {
132 let tag_vec = tag.clone().to_vec();
133 if tag_vec.len() >= 2 && tag_vec[0] == "a" && tag_vec[1].starts_with("30617:") {
134 // Format: 30617:<owner_pubkey>:<identifier>
135 let parts: Vec<&str> = tag_vec[1].split(':').collect();
136 if parts.len() >= 3 {
137 Some(parts[2].to_string())
138 } else {
139 None
140 }
141 } else {
142 None
143 }
144 })
145 .collect();
146
147 // Deduplicate while preserving order
148 let mut seen = std::collections::HashSet::new();
149 let unique_repos: Vec<String> = repos
150 .into_iter()
151 .filter(|r| seen.insert(r.clone()))
152 .collect();
153
154 if unique_repos.is_empty() {
155 vec!["unknown".to_string()]
156 } else {
157 unique_repos
158 }
159 }
160
105 /// Handle repository announcement event 161 /// Handle repository announcement event
106 async fn handle_announcement(&self, event: &Event) -> WritePolicyResult { 162 async fn handle_announcement(&self, event: &Event) -> WritePolicyResult {
107 let event_id_str = event.id.to_bech32().unwrap_or_else(|_| event.id.to_hex()); 163 let event_id_str = event.id.to_bech32().unwrap_or_else(|_| event.id.to_hex());
@@ -133,10 +189,21 @@ impl Nip34WritePolicy {
133 WritePolicyResult::Accept 189 WritePolicyResult::Accept
134 } 190 }
135 Err(e) => { 191 Err(e) => {
192 let npub = event
193 .pubkey
194 .to_bech32()
195 .unwrap_or_else(|_| event.pubkey.to_hex());
196 let event_id_short = &event.id.to_hex()[..12];
197 // Try to extract repo identifier from 'd' tag even if parsing failed
198 let repo = Self::extract_identifier_from_event(event);
199 // Structured log for migration scripts
136 tracing::warn!( 200 tracing::warn!(
137 "Failed to parse repository announcement {}: {}", 201 "[PARSE_FAIL] kind={} event_id={}... reason=\"{}\" repo={} npub={}",
138 event_id_str, 202 event.kind.as_u16(),
139 e 203 event_id_short,
204 e,
205 repo,
206 npub
140 ); 207 );
141 WritePolicyResult::reject(format!("Failed to parse announcement: {}", e)) 208 WritePolicyResult::reject(format!("Failed to parse announcement: {}", e))
142 } 209 }
@@ -185,10 +252,21 @@ impl Nip34WritePolicy {
185 WritePolicyResult::Accept 252 WritePolicyResult::Accept
186 } 253 }
187 Err(e) => { 254 Err(e) => {
255 let npub = event
256 .pubkey
257 .to_bech32()
258 .unwrap_or_else(|_| event.pubkey.to_hex());
259 let event_id_short = &event.id.to_hex()[..12];
260 // Try to extract repo identifier from 'd' tag even if parsing failed
261 let repo = Self::extract_identifier_from_event(event);
262 // Structured log for migration scripts
188 tracing::warn!( 263 tracing::warn!(
189 "Failed to parse maintainer announcement {}: {}", 264 "[PARSE_FAIL] kind={} event_id={}... reason=\"{}\" repo={} npub={}",
190 event_id_str, 265 event.kind.as_u16(),
191 e 266 event_id_short,
267 e,
268 repo,
269 npub
192 ); 270 );
193 WritePolicyResult::reject(format!("Failed to parse announcement: {}", e)) 271 WritePolicyResult::reject(format!("Failed to parse announcement: {}", e))
194 } 272 }
@@ -211,8 +289,6 @@ impl Nip34WritePolicy {
211 /// * `event` - The state event to validate 289 /// * `event` - The state event to validate
212 /// * `is_synced` - True if this event came from proactive sync (vs user-submitted) 290 /// * `is_synced` - True if this event came from proactive sync (vs user-submitted)
213 async fn handle_state(&self, event: &Event, is_synced: bool) -> WritePolicyResult { 291 async fn handle_state(&self, event: &Event, is_synced: bool) -> WritePolicyResult {
214 let event_id_str = event.id.to_bech32().unwrap_or_else(|_| event.id.to_hex());
215
216 match self.state_policy.validate(event) { 292 match self.state_policy.validate(event) {
217 StateResult::Accept => { 293 StateResult::Accept => {
218 // Process state alignment asynchronously 294 // Process state alignment asynchronously
@@ -223,7 +299,22 @@ impl Nip34WritePolicy {
223 { 299 {
224 Ok(poilicy_result) => poilicy_result, 300 Ok(poilicy_result) => poilicy_result,
225 Err(e) => { 301 Err(e) => {
226 tracing::warn!("Failed to process state event {}: {}", event_id_str, e); 302 let npub = event
303 .pubkey
304 .to_bech32()
305 .unwrap_or_else(|_| event.pubkey.to_hex());
306 let event_id_short = &event.id.to_hex()[..12];
307 // Try to extract repo identifier from 'd' tag even if parsing failed
308 let repo = Self::extract_identifier_from_event(event);
309 // Structured log for migration scripts
310 tracing::warn!(
311 "[PARSE_FAIL] kind={} event_id={}... reason=\"{}\" repo={} npub={}",
312 event.kind.as_u16(),
313 event_id_short,
314 e,
315 repo,
316 npub
317 );
227 // reject if processing failed 318 // reject if processing failed
228 WritePolicyResult::Reject { 319 WritePolicyResult::Reject {
229 status: false, 320 status: false,
@@ -233,7 +324,22 @@ impl Nip34WritePolicy {
233 } 324 }
234 } 325 }
235 StateResult::Reject(reason) => { 326 StateResult::Reject(reason) => {
236 tracing::warn!("Rejected repository state {}: {}", event_id_str, reason); 327 let npub = event
328 .pubkey
329 .to_bech32()
330 .unwrap_or_else(|_| event.pubkey.to_hex());
331 let event_id_short = &event.id.to_hex()[..12];
332 // Try to extract repo identifier from 'd' tag even if parsing failed
333 let repo = Self::extract_identifier_from_event(event);
334 // Structured log for migration scripts
335 tracing::warn!(
336 "[PARSE_FAIL] kind={} event_id={}... reason=\"{}\" repo={} npub={}",
337 event.kind.as_u16(),
338 event_id_short,
339 reason,
340 repo,
341 npub
342 );
237 WritePolicyResult::reject(reason) 343 WritePolicyResult::reject(reason)
238 } 344 }
239 } 345 }
@@ -331,9 +437,12 @@ impl Nip34WritePolicy {
331 ); 437 );
332 438
333 // Add to purgatory 439 // Add to purgatory
334 self.ctx 440 self.ctx.purgatory.add_pr(
335 .purgatory 441 event.clone(),
336 .add_pr(event.clone(), event.id.to_hex(), commit.clone()); 442 event.id.to_hex(),
443 commit.clone(),
444 is_synced,
445 );
337 446
338 WritePolicyResult::Reject { 447 WritePolicyResult::Reject {
339 status: true, // Client sees OK 448 status: true, // Client sees OK
@@ -351,11 +460,25 @@ impl Nip34WritePolicy {
351 } 460 }
352 Err(e) => { 461 Err(e) => {
353 // Error checking git data - reject event 462 // Error checking git data - reject event
354 tracing::warn!( 463 let npub = event
355 "Failed to check git data for PR event {}: {}", 464 .pubkey
356 event_id_str, 465 .to_bech32()
357 e 466 .unwrap_or_else(|_| event.pubkey.to_hex());
358 ); 467 let event_id_short = &event.id.to_hex()[..12];
468 // Extract ALL repo identifiers from 'a' tags for PR events
469 // (PR events can reference multiple repos when there are multiple maintainers)
470 let repos = Self::extract_repos_from_pr_event(event);
471 // Structured log for migration scripts - log once per repo
472 for repo in &repos {
473 tracing::warn!(
474 "[PARSE_FAIL] kind={} event_id={}... reason=\"git data check failed: {}\" repo={} npub={}",
475 event.kind.as_u16(),
476 event_id_short,
477 e,
478 repo,
479 npub
480 );
481 }
359 WritePolicyResult::reject(format!("Failed to check git data: {}", e)) 482 WritePolicyResult::reject(format!("Failed to check git data: {}", e))
360 } 483 }
361 } 484 }
@@ -462,9 +585,11 @@ impl Nip34WritePolicy {
462 let (addressable_refs, event_refs) = 585 let (addressable_refs, event_refs) =
463 RelatedEventPolicy::extract_reference_tags(event); 586 RelatedEventPolicy::extract_reference_tags(event);
464 tracing::info!( 587 tracing::info!(
465 "Rejected orphan {} event {}: no references to accepted repos or events (checked {} addressable, {} event refs)", 588 "Rejected orphan {} event {} (kind={}, pubkey={}): no references to accepted repos or events (checked {} addressable, {} event refs)",
466 event_type, 589 event_type,
467 event_id_str, 590 event.id.to_hex(),
591 event.kind.as_u16(),
592 event.pubkey.to_hex(),
468 addressable_refs.len(), 593 addressable_refs.len(),
469 event_refs.len() 594 event_refs.len()
470 ); 595 );