diff options
Diffstat (limited to 'src/nostr/builder.rs')
| -rw-r--r-- | src/nostr/builder.rs | 116 |
1 files changed, 102 insertions, 14 deletions
diff --git a/src/nostr/builder.rs b/src/nostr/builder.rs index 8dd6291..2b4d524 100644 --- a/src/nostr/builder.rs +++ b/src/nostr/builder.rs | |||
| @@ -13,7 +13,7 @@ use nostr_relay_builder::prelude::*; | |||
| 13 | 13 | ||
| 14 | use crate::config::{Config, DatabaseBackend}; | 14 | use crate::config::{Config, DatabaseBackend}; |
| 15 | use crate::nostr::events::{ | 15 | use crate::nostr::events::{ |
| 16 | RepositoryAnnouncement, RepositoryState, KIND_PR, KIND_PR_UPDATE, KIND_REPOSITORY_ANNOUNCEMENT, | 16 | RepositoryAnnouncement, KIND_PR, KIND_PR_UPDATE, KIND_REPOSITORY_ANNOUNCEMENT, |
| 17 | KIND_REPOSITORY_STATE, KIND_USER_GRASP_LIST, | 17 | KIND_REPOSITORY_STATE, KIND_USER_GRASP_LIST, |
| 18 | }; | 18 | }; |
| 19 | use crate::nostr::policy::{ | 19 | use crate::nostr::policy::{ |
| @@ -57,8 +57,9 @@ impl Nip34WritePolicy { | |||
| 57 | domain: impl Into<String>, | 57 | domain: impl Into<String>, |
| 58 | database: SharedDatabase, | 58 | database: SharedDatabase, |
| 59 | git_data_path: impl Into<std::path::PathBuf>, | 59 | git_data_path: impl Into<std::path::PathBuf>, |
| 60 | purgatory: std::sync::Arc<crate::purgatory::Purgatory>, | ||
| 60 | ) -> Self { | 61 | ) -> Self { |
| 61 | let ctx = PolicyContext::new(domain, database, git_data_path); | 62 | let ctx = PolicyContext::new(domain, database, git_data_path, purgatory); |
| 62 | Self { | 63 | Self { |
| 63 | announcement_policy: AnnouncementPolicy::new(ctx.clone()), | 64 | announcement_policy: AnnouncementPolicy::new(ctx.clone()), |
| 64 | state_policy: StatePolicy::new(ctx.clone()), | 65 | state_policy: StatePolicy::new(ctx.clone()), |
| @@ -143,21 +144,50 @@ impl Nip34WritePolicy { | |||
| 143 | 144 | ||
| 144 | match self.state_policy.validate(event) { | 145 | match self.state_policy.validate(event) { |
| 145 | StateResult::Accept => { | 146 | StateResult::Accept => { |
| 146 | // Parse state to get HEAD and branch info | 147 | // Parse state to get identifier for purgatory message |
| 147 | match RepositoryState::from_event(event.clone()) { | 148 | let identifier = event |
| 148 | Ok(_state) => { | 149 | .tags |
| 149 | // Process state alignment asynchronously | 150 | .iter() |
| 150 | if let Err(e) = self.state_policy.process_state_event(event).await { | 151 | .find_map(|tag| { |
| 151 | tracing::warn!("Failed to process state event {}: {}", event_id_str, e); | 152 | let tag_vec = tag.clone().to_vec(); |
| 153 | if tag_vec.len() >= 2 && tag_vec[0] == "d" { | ||
| 154 | Some(tag_vec[1].clone()) | ||
| 155 | } else { | ||
| 156 | None | ||
| 152 | } | 157 | } |
| 158 | }) | ||
| 159 | .unwrap_or_else(|| "unknown".to_string()); | ||
| 153 | 160 | ||
| 154 | tracing::debug!("Accepted repository state: {}", event_id_str); | 161 | // Process state alignment asynchronously |
| 162 | match self.state_policy.process_state_event(event).await { | ||
| 163 | Ok(0) => { | ||
| 164 | // No repos aligned - event was added to purgatory | ||
| 165 | tracing::info!( | ||
| 166 | "State event {} added to purgatory: waiting for git data for identifier {}", | ||
| 167 | event_id_str, | ||
| 168 | identifier | ||
| 169 | ); | ||
| 170 | WritePolicyResult::Reject { | ||
| 171 | status: true, // Client sees OK | ||
| 172 | message: format!( | ||
| 173 | "purgatory: state event stored, waiting for git push for {}", | ||
| 174 | identifier | ||
| 175 | ) | ||
| 176 | .into(), | ||
| 177 | } | ||
| 178 | } | ||
| 179 | Ok(count) => { | ||
| 180 | // Successfully aligned repos | ||
| 181 | tracing::debug!( | ||
| 182 | "Accepted repository state {}: aligned {} repo(s)", | ||
| 183 | event_id_str, | ||
| 184 | count | ||
| 185 | ); | ||
| 155 | WritePolicyResult::Accept | 186 | WritePolicyResult::Accept |
| 156 | } | 187 | } |
| 157 | Err(e) => { | 188 | Err(e) => { |
| 158 | tracing::warn!("Failed to parse repository state {}: {}", event_id_str, e); | 189 | tracing::warn!("Failed to process state event {}: {}", event_id_str, e); |
| 159 | // Still accept the event even if we can't parse it | 190 | // Still accept the event even if processing failed |
| 160 | // The validation passed, so it's structurally valid | ||
| 161 | WritePolicyResult::Accept | 191 | WritePolicyResult::Accept |
| 162 | } | 192 | } |
| 163 | } | 193 | } |
| @@ -173,6 +203,58 @@ impl Nip34WritePolicy { | |||
| 173 | async fn handle_pr_event(&self, event: &Event) -> WritePolicyResult { | 203 | async fn handle_pr_event(&self, event: &Event) -> WritePolicyResult { |
| 174 | let event_id_str = event.id.to_bech32().unwrap_or_else(|_| event.id.to_hex()); | 204 | let event_id_str = event.id.to_bech32().unwrap_or_else(|_| event.id.to_hex()); |
| 175 | 205 | ||
| 206 | // Check if git data exists (checks placeholders and commit existence) | ||
| 207 | match self.pr_event_policy.check_git_data_exists(event).await { | ||
| 208 | Ok(false) => { | ||
| 209 | // No git data exists - add to purgatory | ||
| 210 | let commit = event | ||
| 211 | .tags | ||
| 212 | .iter() | ||
| 213 | .find_map(|tag| { | ||
| 214 | let tag_vec = tag.clone().to_vec(); | ||
| 215 | if tag_vec.len() >= 2 && tag_vec[0] == "c" { | ||
| 216 | Some(tag_vec[1].clone()) | ||
| 217 | } else { | ||
| 218 | None | ||
| 219 | } | ||
| 220 | }) | ||
| 221 | .unwrap_or_else(|| "unknown".to_string()); | ||
| 222 | |||
| 223 | tracing::info!( | ||
| 224 | "PR event {} added to purgatory: waiting for git push with commit {}", | ||
| 225 | event_id_str, | ||
| 226 | commit | ||
| 227 | ); | ||
| 228 | |||
| 229 | // Add to purgatory | ||
| 230 | self.ctx | ||
| 231 | .purgatory | ||
| 232 | .add_pr(event.clone(), event.id.to_hex(), commit.clone()); | ||
| 233 | |||
| 234 | return WritePolicyResult::Reject { | ||
| 235 | status: true, // Client sees OK | ||
| 236 | message: format!( | ||
| 237 | "purgatory: PR event stored, waiting for git push with commit {}", | ||
| 238 | commit | ||
| 239 | ) | ||
| 240 | .into(), | ||
| 241 | }; | ||
| 242 | } | ||
| 243 | Ok(true) => { | ||
| 244 | // Git data exists - proceed with normal validation | ||
| 245 | tracing::debug!("Git data exists for PR event {}", event_id_str); | ||
| 246 | } | ||
| 247 | Err(e) => { | ||
| 248 | // Error checking git data - reject event | ||
| 249 | tracing::warn!( | ||
| 250 | "Failed to check git data for PR event {}: {}", | ||
| 251 | event_id_str, | ||
| 252 | e | ||
| 253 | ); | ||
| 254 | return WritePolicyResult::reject(format!("Failed to check git data: {}", e)); | ||
| 255 | } | ||
| 256 | } | ||
| 257 | |||
| 176 | // Validate refs/nostr refs for this PR event | 258 | // Validate refs/nostr refs for this PR event |
| 177 | // This deletes any refs/nostr/<event-id> that points to wrong commit | 259 | // This deletes any refs/nostr/<event-id> that points to wrong commit |
| 178 | if let Err(e) = self.pr_event_policy.validate_nostr_ref(event).await { | 260 | if let Err(e) = self.pr_event_policy.validate_nostr_ref(event).await { |
| @@ -289,7 +371,10 @@ pub struct RelayWithDatabase { | |||
| 289 | /// Returns a `RelayWithDatabase` struct containing: | 371 | /// Returns a `RelayWithDatabase` struct containing: |
| 290 | /// - The `LocalRelay` for handling WebSocket connections | 372 | /// - The `LocalRelay` for handling WebSocket connections |
| 291 | /// - The `SharedDatabase` for direct database queries (e.g., push authorization) | 373 | /// - The `SharedDatabase` for direct database queries (e.g., push authorization) |
| 292 | pub async fn create_relay(config: &Config) -> Result<RelayWithDatabase> { | 374 | pub async fn create_relay( |
| 375 | config: &Config, | ||
| 376 | purgatory: Arc<crate::purgatory::Purgatory>, | ||
| 377 | ) -> Result<RelayWithDatabase> { | ||
| 293 | tracing::info!("Configuring nostr relay with GRASP-01 validation..."); | 378 | tracing::info!("Configuring nostr relay with GRASP-01 validation..."); |
| 294 | 379 | ||
| 295 | // Determine database path | 380 | // Determine database path |
| @@ -337,7 +422,10 @@ pub async fn create_relay(config: &Config) -> Result<RelayWithDatabase> { | |||
| 337 | // Build relay with GRASP-01 validation | 422 | // Build relay with GRASP-01 validation |
| 338 | // Clone Arc for the write policy so both relay and policy can access the database | 423 | // Clone Arc for the write policy so both relay and policy can access the database |
| 339 | let git_data_path = config.effective_git_data_path(); | 424 | let git_data_path = config.effective_git_data_path(); |
| 340 | let write_policy = Nip34WritePolicy::new(&config.domain, database.clone(), &git_data_path); | 425 | |
| 426 | // Create write policy with purgatory integration | ||
| 427 | let write_policy = | ||
| 428 | Nip34WritePolicy::new(&config.domain, database.clone(), &git_data_path, purgatory); | ||
| 341 | 429 | ||
| 342 | let relay = LocalRelayBuilder::default() | 430 | let relay = LocalRelayBuilder::default() |
| 343 | .database(database.clone()) | 431 | .database(database.clone()) |