diff --git a/warzone/crates/warzone-server/src/routes/messages.rs b/warzone/crates/warzone-server/src/routes/messages.rs index f026d71..fdf1906 100644 --- a/warzone/crates/warzone-server/src/routes/messages.rs +++ b/warzone/crates/warzone-server/src/routes/messages.rs @@ -22,37 +22,59 @@ struct SendRequest { } fn normalize_fp(fp: &str) -> String { - fp.chars().filter(|c| c.is_ascii_hexdigit()).collect::().to_lowercase() + fp.chars() + .filter(|c| c.is_ascii_hexdigit()) + .collect::() + .to_lowercase() } async fn send_message( State(state): State, Json(req): Json, ) -> AppResult> { - let key = format!("queue:{}", normalize_fp(&req.to)); - state.db.messages.insert( - format!("{}:{}", key, uuid::Uuid::new_v4()).as_bytes(), - req.message, - )?; + let to = normalize_fp(&req.to); + let key = format!("queue:{}:{}", to, uuid::Uuid::new_v4()); + tracing::info!("Queuing message for {} ({} bytes)", to, req.message.len()); + state.db.messages.insert(key.as_bytes(), req.message)?; Ok(Json(serde_json::json!({ "ok": true }))) } +/// Poll fetches all queued messages and deletes them from the server. +/// This is store-and-forward: once delivered, the server drops them. async fn poll_messages( State(state): State, Path(fingerprint): Path, ) -> AppResult>> { let prefix = format!("queue:{}", normalize_fp(&fingerprint)); let mut messages = Vec::new(); + let mut keys_to_delete = Vec::new(); + for item in state.db.messages.scan_prefix(prefix.as_bytes()) { - let (_, value) = item?; + let (key, value) = item?; messages.push(base64::Engine::encode( &base64::engine::general_purpose::STANDARD, &value, )); + keys_to_delete.push(key); } + + // Delete after collecting (fetch-and-delete) + for key in &keys_to_delete { + state.db.messages.remove(key)?; + } + + if !messages.is_empty() { + tracing::info!( + "Delivered {} message(s) to {}, deleted from queue", + messages.len(), + normalize_fp(&fingerprint) + ); + } + Ok(Json(messages)) } +/// Explicit ack endpoint (for future use with selective delivery). async fn ack_message( State(state): State, Path(id): Path,