diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2025-11-19 17:01:36 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2025-11-19 17:01:36 +0000 |
| commit | bf7f4d5381203d5c27b2811d62c5b1781533aa2b (patch) | |
| tree | 26903bbf535d83abd7242370d8b6932eb80e3389 /src | |
| parent | fa065ad128882755f2a988d6203b59a2ab5e38ff (diff) | |
fix some clippy fmt warnings
Diffstat (limited to 'src')
| -rw-r--r-- | src/config.rs | 6 | ||||
| -rw-r--r-- | src/http/landing.rs | 15 | ||||
| -rw-r--r-- | src/http/mod.rs | 11 | ||||
| -rw-r--r-- | src/http/websocket.rs | 24 | ||||
| -rw-r--r-- | src/nostr/events.rs | 74 |
5 files changed, 57 insertions, 73 deletions
diff --git a/src/config.rs b/src/config.rs index 252873d..b4dd853 100644 --- a/src/config.rs +++ b/src/config.rs | |||
| @@ -19,10 +19,8 @@ impl Config { | |||
| 19 | dotenvy::dotenv().ok(); | 19 | dotenvy::dotenv().ok(); |
| 20 | 20 | ||
| 21 | Ok(Config { | 21 | Ok(Config { |
| 22 | domain: env::var("NGIT_DOMAIN") | 22 | domain: env::var("NGIT_DOMAIN").unwrap_or_else(|_| "localhost:8080".to_string()), |
| 23 | .unwrap_or_else(|_| "localhost:8080".to_string()), | 23 | owner_npub: env::var("NGIT_OWNER_NPUB").context("NGIT_OWNER_NPUB must be set")?, |
| 24 | owner_npub: env::var("NGIT_OWNER_NPUB") | ||
| 25 | .context("NGIT_OWNER_NPUB must be set")?, | ||
| 26 | relay_name: env::var("NGIT_RELAY_NAME") | 24 | relay_name: env::var("NGIT_RELAY_NAME") |
| 27 | .unwrap_or_else(|_| "ngit-grasp relay".to_string()), | 25 | .unwrap_or_else(|_| "ngit-grasp relay".to_string()), |
| 28 | relay_description: env::var("NGIT_RELAY_DESCRIPTION") | 26 | relay_description: env::var("NGIT_RELAY_DESCRIPTION") |
diff --git a/src/http/landing.rs b/src/http/landing.rs index 35e49e5..976ec50 100644 --- a/src/http/landing.rs +++ b/src/http/landing.rs | |||
| @@ -1,7 +1,6 @@ | |||
| 1 | /// Landing Page Handler | 1 | /// Landing Page Handler |
| 2 | /// | 2 | /// |
| 3 | /// Serves the HTML landing page or upgrades to WebSocket for Nostr relay connections. | 3 | /// Serves the HTML landing page or upgrades to WebSocket for Nostr relay connections. |
| 4 | |||
| 5 | use actix_web::{web, HttpRequest, HttpResponse, Result}; | 4 | use actix_web::{web, HttpRequest, HttpResponse, Result}; |
| 6 | use nostr_relay_builder::LocalRelay; | 5 | use nostr_relay_builder::LocalRelay; |
| 7 | 6 | ||
| @@ -16,12 +15,16 @@ pub async fn handle( | |||
| 16 | ) -> Result<HttpResponse> { | 15 | ) -> Result<HttpResponse> { |
| 17 | // Check if this is a WebSocket upgrade request | 16 | // Check if this is a WebSocket upgrade request |
| 18 | if let Some(upgrade) = req.headers().get("upgrade") { | 17 | if let Some(upgrade) = req.headers().get("upgrade") { |
| 19 | if upgrade.to_str().unwrap_or("").eq_ignore_ascii_case("websocket") { | 18 | if upgrade |
| 19 | .to_str() | ||
| 20 | .unwrap_or("") | ||
| 21 | .eq_ignore_ascii_case("websocket") | ||
| 22 | { | ||
| 20 | // Delegate to WebSocket handler | 23 | // Delegate to WebSocket handler |
| 21 | return crate::http::websocket::handle(req, stream, relay).await; | 24 | return crate::http::websocket::handle(req, stream, relay).await; |
| 22 | } | 25 | } |
| 23 | } | 26 | } |
| 24 | 27 | ||
| 25 | // Otherwise, serve the landing page | 28 | // Otherwise, serve the landing page |
| 26 | let html = format!( | 29 | let html = format!( |
| 27 | include_str!("../../templates/landing.html"), | 30 | include_str!("../../templates/landing.html"), |
| @@ -30,8 +33,8 @@ pub async fn handle( | |||
| 30 | domain = config.domain, | 33 | domain = config.domain, |
| 31 | bind_address = config.bind_address, | 34 | bind_address = config.bind_address, |
| 32 | ); | 35 | ); |
| 33 | 36 | ||
| 34 | Ok(HttpResponse::Ok() | 37 | Ok(HttpResponse::Ok() |
| 35 | .content_type("text/html; charset=utf-8") | 38 | .content_type("text/html; charset=utf-8") |
| 36 | .body(html)) | 39 | .body(html)) |
| 37 | } \ No newline at end of file | 40 | } |
diff --git a/src/http/mod.rs b/src/http/mod.rs index 286e8ff..b434c69 100644 --- a/src/http/mod.rs +++ b/src/http/mod.rs | |||
| @@ -1,7 +1,6 @@ | |||
| 1 | /// HTTP Server Module | 1 | /// HTTP Server Module |
| 2 | /// | 2 | /// |
| 3 | /// Provides actix-web HTTP server with WebSocket upgrade support for the Nostr relay. | 3 | /// Provides actix-web HTTP server with WebSocket upgrade support for the Nostr relay. |
| 4 | |||
| 5 | pub mod landing; | 4 | pub mod landing; |
| 6 | pub mod websocket; | 5 | pub mod websocket; |
| 7 | 6 | ||
| @@ -13,9 +12,9 @@ use crate::config::Config; | |||
| 13 | /// Start the HTTP server with integrated Nostr relay | 12 | /// Start the HTTP server with integrated Nostr relay |
| 14 | pub async fn run_server(config: Config, relay: LocalRelay) -> anyhow::Result<()> { | 13 | pub async fn run_server(config: Config, relay: LocalRelay) -> anyhow::Result<()> { |
| 15 | let bind_addr = config.bind_address.clone(); | 14 | let bind_addr = config.bind_address.clone(); |
| 16 | 15 | ||
| 17 | tracing::info!("Starting HTTP server on {}", bind_addr); | 16 | tracing::info!("Starting HTTP server on {}", bind_addr); |
| 18 | 17 | ||
| 19 | HttpServer::new(move || { | 18 | HttpServer::new(move || { |
| 20 | App::new() | 19 | App::new() |
| 21 | .app_data(web::Data::new(config.clone())) | 20 | .app_data(web::Data::new(config.clone())) |
| @@ -26,6 +25,6 @@ pub async fn run_server(config: Config, relay: LocalRelay) -> anyhow::Result<()> | |||
| 26 | .bind(&bind_addr)? | 25 | .bind(&bind_addr)? |
| 27 | .run() | 26 | .run() |
| 28 | .await?; | 27 | .await?; |
| 29 | 28 | ||
| 30 | Ok(()) | 29 | Ok(()) |
| 31 | } \ No newline at end of file | 30 | } |
diff --git a/src/http/websocket.rs b/src/http/websocket.rs index 7af847a..0171013 100644 --- a/src/http/websocket.rs +++ b/src/http/websocket.rs | |||
| @@ -1,8 +1,7 @@ | |||
| 1 | /// WebSocket Handler | 1 | /// WebSocket Handler |
| 2 | /// | 2 | /// |
| 3 | /// Handles WebSocket upgrade requests and passes connections to the Nostr relay. | 3 | /// Handles WebSocket upgrade requests and passes connections to the Nostr relay. |
| 4 | 4 | use actix_web::{web, Error, HttpRequest, HttpResponse, Result}; | |
| 5 | use actix_web::{web, HttpRequest, HttpResponse, Result, Error}; | ||
| 6 | use actix_ws::Message; | 5 | use actix_ws::Message; |
| 7 | use futures_util::StreamExt; | 6 | use futures_util::StreamExt; |
| 8 | use nostr_relay_builder::LocalRelay; | 7 | use nostr_relay_builder::LocalRelay; |
| @@ -14,19 +13,20 @@ pub async fn handle( | |||
| 14 | relay: web::Data<LocalRelay>, | 13 | relay: web::Data<LocalRelay>, |
| 15 | ) -> Result<HttpResponse, Error> { | 14 | ) -> Result<HttpResponse, Error> { |
| 16 | let (response, mut session, mut msg_stream) = actix_ws::handle(&req, stream)?; | 15 | let (response, mut session, mut msg_stream) = actix_ws::handle(&req, stream)?; |
| 17 | 16 | ||
| 18 | let peer_addr = req.peer_addr() | 17 | let peer_addr = req |
| 18 | .peer_addr() | ||
| 19 | .unwrap_or_else(|| "0.0.0.0:0".parse().unwrap()); | 19 | .unwrap_or_else(|| "0.0.0.0:0".parse().unwrap()); |
| 20 | 20 | ||
| 21 | tracing::debug!("WebSocket connection from {}", peer_addr); | 21 | tracing::debug!("WebSocket connection from {}", peer_addr); |
| 22 | 22 | ||
| 23 | // Spawn task to handle the WebSocket connection | 23 | // Spawn task to handle the WebSocket connection |
| 24 | // TODO: Will use relay.take_connection() for full Nostr relay integration | 24 | // TODO: Will use relay.take_connection() for full Nostr relay integration |
| 25 | let _relay = relay.get_ref().clone(); | 25 | let _relay = relay.get_ref().clone(); |
| 26 | actix_web::rt::spawn(async move { | 26 | actix_web::rt::spawn(async move { |
| 27 | // Create a channel to communicate between actix-ws and relay | 27 | // Create a channel to communicate between actix-ws and relay |
| 28 | let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel(); | 28 | let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel(); |
| 29 | 29 | ||
| 30 | // Spawn task to send messages from relay to client | 30 | // Spawn task to send messages from relay to client |
| 31 | let mut session_clone = session.clone(); | 31 | let mut session_clone = session.clone(); |
| 32 | actix_web::rt::spawn(async move { | 32 | actix_web::rt::spawn(async move { |
| @@ -36,7 +36,7 @@ pub async fn handle( | |||
| 36 | } | 36 | } |
| 37 | } | 37 | } |
| 38 | }); | 38 | }); |
| 39 | 39 | ||
| 40 | // Handle incoming messages from client | 40 | // Handle incoming messages from client |
| 41 | while let Some(Ok(msg)) = msg_stream.next().await { | 41 | while let Some(Ok(msg)) = msg_stream.next().await { |
| 42 | match msg { | 42 | match msg { |
| @@ -65,9 +65,9 @@ pub async fn handle( | |||
| 65 | Message::Nop => {} | 65 | Message::Nop => {} |
| 66 | } | 66 | } |
| 67 | } | 67 | } |
| 68 | 68 | ||
| 69 | tracing::debug!("WebSocket connection closed for {}", peer_addr); | 69 | tracing::debug!("WebSocket connection closed for {}", peer_addr); |
| 70 | }); | 70 | }); |
| 71 | 71 | ||
| 72 | Ok(response) | 72 | Ok(response) |
| 73 | } \ No newline at end of file | 73 | } |
diff --git a/src/nostr/events.rs b/src/nostr/events.rs index 88aefed..21dd2dd 100644 --- a/src/nostr/events.rs +++ b/src/nostr/events.rs | |||
| @@ -1,12 +1,11 @@ | |||
| 1 | /// NIP-34 Git Repository Event Handling | 1 | /// NIP-34 Git Repository Event Handling |
| 2 | /// | 2 | /// |
| 3 | /// This module handles Git repository announcements (kind 30617) and | 3 | /// This module handles Git repository announcements (kind 30617) and |
| 4 | /// repository state announcements (kind 30618) according to NIP-34 and GRASP-01. | 4 | /// repository state announcements (kind 30618) according to NIP-34 and GRASP-01. |
| 5 | /// | 5 | /// |
| 6 | /// Reference: | 6 | /// Reference: |
| 7 | /// - NIP-34: https://nips.nostr.com/34 | 7 | /// - NIP-34: https://nips.nostr.com/34 |
| 8 | /// - GRASP-01: https://gitworkshop.dev/danconwaydev.com/grasp/01.md | 8 | /// - GRASP-01: https://gitworkshop.dev/danconwaydev.com/grasp/01.md |
| 9 | |||
| 10 | use anyhow::{anyhow, Result}; | 9 | use anyhow::{anyhow, Result}; |
| 11 | use nostr_sdk::{Event, Kind, TagKind, ToBech32}; | 10 | use nostr_sdk::{Event, Kind, TagKind, ToBech32}; |
| 12 | 11 | ||
| @@ -138,8 +137,8 @@ impl RepositoryAnnouncement { | |||
| 138 | } | 137 | } |
| 139 | 138 | ||
| 140 | /// Check if this announcement lists the service (both clone and relay) | 139 | /// Check if this announcement lists the service (both clone and relay) |
| 141 | /// | 140 | /// |
| 142 | /// GRASP-01 requirement: MUST reject announcements that do not list | 141 | /// GRASP-01 requirement: MUST reject announcements that do not list |
| 143 | /// the service in both `clone` and `relays` tags unless implementing GRASP-05. | 142 | /// the service in both `clone` and `relays` tags unless implementing GRASP-05. |
| 144 | pub fn lists_service(&self, domain: &str) -> bool { | 143 | pub fn lists_service(&self, domain: &str) -> bool { |
| 145 | self.has_clone_url(domain) && self.has_relay(domain) | 144 | self.has_clone_url(domain) && self.has_relay(domain) |
| @@ -278,19 +277,19 @@ impl RepositoryState { | |||
| 278 | } | 277 | } |
| 279 | 278 | ||
| 280 | /// Validate a repository announcement according to GRASP-01 | 279 | /// Validate a repository announcement according to GRASP-01 |
| 281 | /// | 280 | /// |
| 282 | /// Returns Ok(()) if valid, Err with reason if invalid. | 281 | /// Returns Ok(()) if valid, Err with reason if invalid. |
| 283 | pub fn validate_announcement(event: &Event, domain: &str) -> Result<()> { | 282 | pub fn validate_announcement(event: &Event, domain: &str) -> Result<()> { |
| 284 | // Must be kind 30617 | 283 | // Must be kind 30617 |
| 285 | if event.kind != Kind::from(KIND_REPOSITORY_ANNOUNCEMENT) { | 284 | if event.kind != Kind::from(KIND_REPOSITORY_ANNOUNCEMENT) { |
| 286 | return Err(anyhow!("Invalid kind: expected {}", KIND_REPOSITORY_ANNOUNCEMENT)); | 285 | return Err(anyhow!( |
| 286 | "Invalid kind: expected {}", | ||
| 287 | KIND_REPOSITORY_ANNOUNCEMENT | ||
| 288 | )); | ||
| 287 | } | 289 | } |
| 288 | 290 | ||
| 289 | // Must have identifier | 291 | // Must have identifier |
| 290 | let has_identifier = event | 292 | let has_identifier = event.tags.iter().any(|t| t.kind() == TagKind::d()); |
| 291 | .tags | ||
| 292 | .iter() | ||
| 293 | .any(|t| t.kind() == TagKind::d()); | ||
| 294 | if !has_identifier { | 293 | if !has_identifier { |
| 295 | return Err(anyhow!("Missing required 'd' tag (identifier)")); | 294 | return Err(anyhow!("Missing required 'd' tag (identifier)")); |
| 296 | } | 295 | } |
| @@ -298,7 +297,7 @@ pub fn validate_announcement(event: &Event, domain: &str) -> Result<()> { | |||
| 298 | // Parse full announcement to validate structure | 297 | // Parse full announcement to validate structure |
| 299 | let announcement = RepositoryAnnouncement::from_event(event.clone())?; | 298 | let announcement = RepositoryAnnouncement::from_event(event.clone())?; |
| 300 | 299 | ||
| 301 | // GRASP-01: MUST reject announcements that do not list the service | 300 | // GRASP-01: MUST reject announcements that do not list the service |
| 302 | // in both `clone` and `relays` tags unless implementing GRASP-05 | 301 | // in both `clone` and `relays` tags unless implementing GRASP-05 |
| 303 | if !announcement.lists_service(domain) { | 302 | if !announcement.lists_service(domain) { |
| 304 | return Err(anyhow!( | 303 | return Err(anyhow!( |
| @@ -313,7 +312,7 @@ pub fn validate_announcement(event: &Event, domain: &str) -> Result<()> { | |||
| 313 | } | 312 | } |
| 314 | 313 | ||
| 315 | /// Validate a repository state announcement according to GRASP-01 | 314 | /// Validate a repository state announcement according to GRASP-01 |
| 316 | /// | 315 | /// |
| 317 | /// Returns Ok(()) if valid, Err with reason if invalid. | 316 | /// Returns Ok(()) if valid, Err with reason if invalid. |
| 318 | pub fn validate_state(event: &Event) -> Result<()> { | 317 | pub fn validate_state(event: &Event) -> Result<()> { |
| 319 | // Must be kind 30618 | 318 | // Must be kind 30618 |
| @@ -322,10 +321,7 @@ pub fn validate_state(event: &Event) -> Result<()> { | |||
| 322 | } | 321 | } |
| 323 | 322 | ||
| 324 | // Must have identifier | 323 | // Must have identifier |
| 325 | let has_identifier = event | 324 | let has_identifier = event.tags.iter().any(|t| t.kind() == TagKind::d()); |
| 326 | .tags | ||
| 327 | .iter() | ||
| 328 | .any(|t| t.kind() == TagKind::d()); | ||
| 329 | if !has_identifier { | 325 | if !has_identifier { |
| 330 | return Err(anyhow!("Missing required 'd' tag (identifier)")); | 326 | return Err(anyhow!("Missing required 'd' tag (identifier)")); |
| 331 | } | 327 | } |
| @@ -352,7 +348,7 @@ mod tests { | |||
| 352 | relays: Vec<&str>, | 348 | relays: Vec<&str>, |
| 353 | ) -> Event { | 349 | ) -> Event { |
| 354 | use nostr_sdk::Tag; | 350 | use nostr_sdk::Tag; |
| 355 | 351 | ||
| 356 | let mut tags = vec![Tag::custom( | 352 | let mut tags = vec![Tag::custom( |
| 357 | nostr_sdk::TagKind::d(), | 353 | nostr_sdk::TagKind::d(), |
| 358 | vec![identifier.to_string()], | 354 | vec![identifier.to_string()], |
| @@ -372,18 +368,15 @@ mod tests { | |||
| 372 | )); | 368 | )); |
| 373 | } | 369 | } |
| 374 | 370 | ||
| 375 | EventBuilder::new( | 371 | EventBuilder::new(Kind::from(KIND_REPOSITORY_ANNOUNCEMENT), "Test repository") |
| 376 | Kind::from(KIND_REPOSITORY_ANNOUNCEMENT), | 372 | .tags(tags) |
| 377 | "Test repository", | 373 | .sign_with_keys(keys) |
| 378 | ) | 374 | .unwrap() |
| 379 | .tags(tags) | ||
| 380 | .sign_with_keys(keys) | ||
| 381 | .unwrap() | ||
| 382 | } | 375 | } |
| 383 | 376 | ||
| 384 | fn create_state_event(keys: &Keys, identifier: &str, branches: Vec<(&str, &str)>) -> Event { | 377 | fn create_state_event(keys: &Keys, identifier: &str, branches: Vec<(&str, &str)>) -> Event { |
| 385 | use nostr_sdk::Tag; | 378 | use nostr_sdk::Tag; |
| 386 | 379 | ||
| 387 | let mut tags = vec![Tag::custom( | 380 | let mut tags = vec![Tag::custom( |
| 388 | nostr_sdk::TagKind::d(), | 381 | nostr_sdk::TagKind::d(), |
| 389 | vec![identifier.to_string()], | 382 | vec![identifier.to_string()], |
| @@ -392,10 +385,7 @@ mod tests { | |||
| 392 | for (branch, commit) in branches { | 385 | for (branch, commit) in branches { |
| 393 | tags.push(Tag::custom( | 386 | tags.push(Tag::custom( |
| 394 | nostr_sdk::TagKind::Custom("ref".into()), | 387 | nostr_sdk::TagKind::Custom("ref".into()), |
| 395 | vec![ | 388 | vec![format!("refs/heads/{}", branch), commit.to_string()], |
| 396 | format!("refs/heads/{}", branch), | ||
| 397 | commit.to_string(), | ||
| 398 | ], | ||
| 399 | )); | 389 | )); |
| 400 | } | 390 | } |
| 401 | 391 | ||
| @@ -428,12 +418,9 @@ mod tests { | |||
| 428 | #[test] | 418 | #[test] |
| 429 | fn test_parse_announcement_missing_identifier() { | 419 | fn test_parse_announcement_missing_identifier() { |
| 430 | let keys = create_test_keys(); | 420 | let keys = create_test_keys(); |
| 431 | let event = EventBuilder::new( | 421 | let event = EventBuilder::new(Kind::from(KIND_REPOSITORY_ANNOUNCEMENT), "Test repository") |
| 432 | Kind::from(KIND_REPOSITORY_ANNOUNCEMENT), | 422 | .sign_with_keys(&keys) |
| 433 | "Test repository", | 423 | .unwrap(); |
| 434 | ) | ||
| 435 | .sign_with_keys(&keys) | ||
| 436 | .unwrap(); | ||
| 437 | 424 | ||
| 438 | let result = RepositoryAnnouncement::from_event(event); | 425 | let result = RepositoryAnnouncement::from_event(event); |
| 439 | assert!(result.is_err()); | 426 | assert!(result.is_err()); |
| @@ -539,7 +526,7 @@ mod tests { | |||
| 539 | #[test] | 526 | #[test] |
| 540 | fn test_announcement_maintainers() { | 527 | fn test_announcement_maintainers() { |
| 541 | use nostr_sdk::Tag; | 528 | use nostr_sdk::Tag; |
| 542 | 529 | ||
| 543 | let keys = create_test_keys(); | 530 | let keys = create_test_keys(); |
| 544 | let maintainer_keys = create_test_keys(); | 531 | let maintainer_keys = create_test_keys(); |
| 545 | 532 | ||
| @@ -558,13 +545,10 @@ mod tests { | |||
| 558 | // Add maintainer | 545 | // Add maintainer |
| 559 | tags.push(Tag::public_key(maintainer_keys.public_key())); | 546 | tags.push(Tag::public_key(maintainer_keys.public_key())); |
| 560 | 547 | ||
| 561 | let event = EventBuilder::new( | 548 | let event = EventBuilder::new(Kind::from(KIND_REPOSITORY_ANNOUNCEMENT), "Test repository") |
| 562 | Kind::from(KIND_REPOSITORY_ANNOUNCEMENT), | 549 | .tags(tags) |
| 563 | "Test repository", | 550 | .sign_with_keys(&keys) |
| 564 | ) | 551 | .unwrap(); |
| 565 | .tags(tags) | ||
| 566 | .sign_with_keys(&keys) | ||
| 567 | .unwrap(); | ||
| 568 | 552 | ||
| 569 | let announcement = RepositoryAnnouncement::from_event(event).unwrap(); | 553 | let announcement = RepositoryAnnouncement::from_event(event).unwrap(); |
| 570 | assert_eq!(announcement.maintainers.len(), 1); | 554 | assert_eq!(announcement.maintainers.len(), 1); |
| @@ -573,7 +557,7 @@ mod tests { | |||
| 573 | #[test] | 557 | #[test] |
| 574 | fn test_state_with_tags() { | 558 | fn test_state_with_tags() { |
| 575 | use nostr_sdk::Tag; | 559 | use nostr_sdk::Tag; |
| 576 | 560 | ||
| 577 | let keys = create_test_keys(); | 561 | let keys = create_test_keys(); |
| 578 | let mut tags = vec![Tag::custom( | 562 | let mut tags = vec![Tag::custom( |
| 579 | nostr_sdk::TagKind::d(), | 563 | nostr_sdk::TagKind::d(), |