diff --git a/backend/modules/archiving/Cargo.toml b/backend/modules/archiving/Cargo.toml index a990ccae..38f2fb71 100644 --- a/backend/modules/archiving/Cargo.toml +++ b/backend/modules/archiving/Cargo.toml @@ -15,7 +15,7 @@ chrono = { version = "0.4", features = ["serde"] } tokio = { version = "1", features = ["full"] } log = "0.4" env_logger = "0.11" -reqwest = { version = "0.11", features = ["json"] } +reqwest = { version = "0.11", features = ["json", "multipart"] } base64 = "0.21" sha2 = "0.10" hex = "0.4" diff --git a/backend/modules/archiving/src/lib.rs b/backend/modules/archiving/src/lib.rs index 47b2abf1..b3e91783 100644 --- a/backend/modules/archiving/src/lib.rs +++ b/backend/modules/archiving/src/lib.rs @@ -263,11 +263,11 @@ impl PGNArchiver { if arweave_cost > ipfs_cost { transaction_id = arweave_tx; gas_used = arweave_gas; - cost_usd = arweave_cost; + cost_usd = Some(arweave_cost); } else { transaction_id = ipfs_tx; gas_used = ipfs_gas; - cost_usd = ipfs_cost; + cost_usd = Some(ipfs_cost); } } } @@ -362,14 +362,12 @@ impl PGNArchiver { let url = format!("{}/tx", self.arweave_gateway); let mut form_data = HashMap::new(); - form_data.insert("data", general_purpose::STANDARD.encode(&upload_data)); - form_data.insert("content-type", "application/json".to_string()); + form_data.insert("data".to_string(), general_purpose::STANDARD.encode(&upload_data)); + form_data.insert("content-type".to_string(), "application/json".to_string()); - if let Some(tags) = &metadata.tags { - for (i, tag) in tags.iter().enumerate() { - form_data.insert(format!("tag-{}-name", i), "xlmate-tag".to_string()); - form_data.insert(format!("tag-{}-value", i), tag.clone()); - } + for (i, tag) in metadata.tags.iter().enumerate() { + form_data.insert(format!("tag-{}-name", i), "xlmate-tag".to_string()); + form_data.insert(format!("tag-{}-value", i), tag.clone()); } let response = self.http_client @@ -466,7 +464,7 @@ impl PGNArchiver { // Add annotations if present if let Some(annotations) = &pgn.annotations { for annotation in annotations { - pgn_string.push_str(&format!("\n{{{",)); + pgn_string.push_str(&format!("\n{{",)); if let Some(evaluation) = annotation.evaluation { pgn_string.push_str(&format!("[%eval {:.2}]", evaluation)); } diff --git a/backend/modules/validation/src/lib.rs b/backend/modules/validation/src/lib.rs index 9dd1c20a..475502f6 100644 --- a/backend/modules/validation/src/lib.rs +++ b/backend/modules/validation/src/lib.rs @@ -1,9 +1,10 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; +use std::sync::{Arc, Mutex}; use uuid::Uuid; use chrono::{DateTime, Utc}; use async_trait::async_trait; -use redis::Client as RedisClient; +use redis::{AsyncCommands, Client as RedisClient}; use futures_util::StreamExt; #[derive(Debug, Clone, Serialize, Deserialize)] @@ -120,7 +121,7 @@ pub struct RealTimeMoveValidator { cache_ttl_seconds: u64, max_batch_size: usize, rate_limit_per_minute: u32, - position_cache: HashMap, + position_cache: Mutex>, } impl RealTimeMoveValidator { @@ -133,7 +134,7 @@ impl RealTimeMoveValidator { cache_ttl_seconds: 300, // 5 minutes max_batch_size: 100, rate_limit_per_minute: 60, - position_cache: HashMap::new(), + position_cache: Mutex::new(HashMap::new()), } } @@ -151,7 +152,7 @@ impl RealTimeMoveValidator { cache_ttl_seconds, max_batch_size, rate_limit_per_minute, - position_cache: HashMap::new(), + position_cache: Mutex::new(HashMap::new()), } } @@ -179,7 +180,8 @@ impl RealTimeMoveValidator { async fn get_cached_validation(&self, fen: &str, move_san: &str) -> Option { let cache_key = format!("{}:{}", fen, move_san); - if let Some(cached) = self.position_cache.get(&cache_key) { + let cached_opt = self.position_cache.lock().unwrap().get(&cache_key).cloned(); + if let Some(cached) = cached_opt { let now = Utc::now(); let age = (now - cached.cached_at).num_seconds() as u64; @@ -187,7 +189,7 @@ impl RealTimeMoveValidator { return Some(cached.clone()); } else { // Remove expired cache entry - self.position_cache.remove(&cache_key); + self.position_cache.lock().unwrap().remove(&cache_key); } } @@ -200,7 +202,7 @@ impl RealTimeMoveValidator { if age < cached.ttl_seconds { // Update local cache - self.position_cache.insert(cache_key, cached.clone()); + self.position_cache.lock().unwrap().insert(cache_key, cached.clone()); return Some(cached); } } @@ -214,7 +216,7 @@ impl RealTimeMoveValidator { let cache_key = format!("{}:{}", fen, move_san); // Update local cache - self.position_cache.insert(cache_key.clone(), validation.clone()); + self.position_cache.lock().unwrap().insert(cache_key.clone(), validation.clone()); // Update Redis cache if let Ok(mut conn) = self.redis_client.get_async_connection().await { @@ -250,6 +252,12 @@ impl RealTimeMoveValidator { None }; + // Compute this before moving from_square/to_square into the struct. + let is_castling = (from_square.chars().nth(1) == Some('1') + && to_square.chars().nth(1) == Some('1')) + || (from_square.chars().nth(1) == Some('8') + && to_square.chars().nth(1) == Some('8')); + Ok(ProcessedMove { from_square, to_square, @@ -257,8 +265,7 @@ impl RealTimeMoveValidator { is_capture: false, // Will be determined by position is_check: false, // Will be determined by position is_checkmate: false, // Will be determined by position - is_castling: from_square.chars().nth(1) == Some('1') && to_square.chars().nth(1) == Some('1') || - from_square.chars().nth(1) == Some('8') && to_square.chars().nth(1) == Some('8'), + is_castling, is_en_passant: false, // Will be determined by position promotion_piece, san: uci.to_string(),