use std::collections::HashMap;
use std::sync::RwLock;
use axum::Json;
use axum::extract::State;
use axum::http::StatusCode;
use did_hosting_common::StatsSyncPayload;
use did_hosting_common::server::acl;
use did_hosting_common::server::auth::ServiceAuth;
use tracing::{debug, warn};
use crate::server::AppState;
static LAST_SEQ: std::sync::LazyLock<RwLock<HashMap<String, u64>>> =
std::sync::LazyLock::new(|| RwLock::new(HashMap::new()));
pub fn accept_seq(server_did: &str, seq: u64) -> bool {
if seq > 0
&& let Ok(map) = LAST_SEQ.read()
&& let Some(&last) = map.get(server_did)
&& seq <= last
{
return false;
}
if let Ok(mut map) = LAST_SEQ.write() {
map.insert(server_did.to_string(), seq);
}
true
}
pub async fn receive_stats(
auth: ServiceAuth,
State(state): State<AppState>,
Json(payload): Json<StatsSyncPayload>,
) -> StatusCode {
if auth.0.did != payload.server_did {
warn!(
authenticated = %auth.0.did,
claimed = %payload.server_did,
"stats sync rejected: payload server_did does not match authenticated DID",
);
return StatusCode::FORBIDDEN;
}
match acl::get_acl_entry(&state.acl_ks, &payload.server_did).await {
Ok(Some(_)) => {}
Ok(None) => {
warn!(server_did = %payload.server_did, "stats sync rejected: DID not in ACL");
return StatusCode::FORBIDDEN;
}
Err(e) => {
warn!(error = %e, "stats sync: ACL lookup failed");
return StatusCode::INTERNAL_SERVER_ERROR;
}
}
if !accept_seq(&payload.server_did, payload.seq) {
debug!(
server_did = %payload.server_did,
seq = payload.seq,
"stats sync: stale sequence (skipped)"
);
return StatusCode::NO_CONTENT;
}
for delta in &payload.did_deltas {
state.stats_collector.record_deltas(
&delta.mnemonic,
delta.resolve_delta,
delta.update_delta,
delta.last_resolved_at,
delta.last_updated_at,
);
}
#[cfg(feature = "metrics")]
did_hosting_common::server::metrics::inc_stats_sync();
debug!(
server_did = %payload.server_did,
seq = payload.seq,
delta_count = payload.did_deltas.len(),
"stats sync accepted"
);
StatusCode::NO_CONTENT
}