use std::{
net::SocketAddr,
str::FromStr,
sync::{Arc, LazyLock},
};
use axum::{
Json, Router,
extract::{Path, State},
http::StatusCode,
routing::put,
};
use axum_macros::debug_handler;
use directories::ProjectDirs;
use dwn::{Dwn, core::message::Message};
use tokio::net::TcpListener;
use tracing::{debug, error, info};
use xdid::core::did::Did;
pub use dwn::core::reply::Reply;
pub static DIRS: LazyLock<ProjectDirs> = LazyLock::new(|| {
let dirs = ProjectDirs::from("", "UNAVI", "dwn-server").expect("project dirs");
std::fs::create_dir_all(dirs.data_dir()).expect("data dir");
dirs
});
const DB_FILE: &str = "data.db";
pub async fn run_server(addr: SocketAddr) -> anyhow::Result<()> {
let path = {
let mut dir = DIRS.data_dir().to_path_buf();
dir.push(DB_FILE);
dir
};
let store = Arc::new(dwn_native_db::NativeDbStore::new(path)?);
let dwn = Dwn::new(store.clone(), store);
let listener = TcpListener::bind(addr).await?;
let router = create_router(dwn);
info!("DWN server running on {addr}");
axum::serve(listener, router).await?;
Ok(())
}
pub fn create_router(dwn: Dwn) -> Router {
Router::new()
.route("/{target}", put(handle_put))
.with_state(dwn)
}
#[debug_handler]
async fn handle_put(
Path(mut target): Path<String>,
State(dwn): State<Dwn>,
Json(msg): Json<Message>,
) -> Result<Json<serde_json::Value>, StatusCode> {
if target.starts_with("did:web:") {
let (_, rest) = target
.split_once("did:web:")
.ok_or(StatusCode::INTERNAL_SERVER_ERROR)?;
let mut parts = rest.split(':');
if let Some(first) = parts.next()
&& let Some(second) = parts.next()
&& second.len() <= 5
&& second.chars().all(|c| c.is_numeric())
{
let parts_vec = parts.collect::<Vec<_>>();
let parts_str = if parts_vec.is_empty() {
String::new()
} else {
format!(":{}", parts_vec.join(":"))
};
target = format!("did:web:{first}%3A{second}{parts_str}");
}
}
let target = Did::from_str(&target).map_err(|e| {
debug!("Failed to parse DID: {:?}", e);
StatusCode::BAD_REQUEST
})?;
let reply = dwn.process_message(&target, msg).await?;
let res = serde_json::to_value(reply).map_err(|e| {
error!("Error serializing response: {e:?}");
StatusCode::INTERNAL_SERVER_ERROR
})?;
Ok(Json(res))
}