Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backend/modules/archiving/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
18 changes: 8 additions & 10 deletions backend/modules/archiving/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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));
}
Expand Down
27 changes: 17 additions & 10 deletions backend/modules/validation/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand Down Expand Up @@ -120,7 +121,7 @@ pub struct RealTimeMoveValidator {
cache_ttl_seconds: u64,
max_batch_size: usize,
rate_limit_per_minute: u32,
position_cache: HashMap<String, ValidationCache>,
position_cache: Mutex<HashMap<String, ValidationCache>>,
}

impl RealTimeMoveValidator {
Expand All @@ -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()),
}
}

Expand All @@ -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()),
}
}

Expand Down Expand Up @@ -179,15 +180,16 @@ impl RealTimeMoveValidator {
async fn get_cached_validation(&self, fen: &str, move_san: &str) -> Option<ValidationCache> {
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;

if age < cached.ttl_seconds {
return Some(cached.clone());
} else {
// Remove expired cache entry
self.position_cache.remove(&cache_key);
self.position_cache.lock().unwrap().remove(&cache_key);
}
}

Expand All @@ -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);
}
}
Expand All @@ -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 {
Expand Down Expand Up @@ -250,15 +252,20 @@ 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,
piece: "P".to_string(), // Will be determined by position
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(),
Expand Down
Loading