enigma_node_registry/
routes.rs

1use std::sync::Arc;
2
3use axum::extract::{Path, State};
4use axum::http::StatusCode;
5use axum::response::{IntoResponse, Response};
6use axum::routing::{get, post};
7use axum::Json;
8use enigma_node_types::{
9    CheckUserResponse, NodesPayload, Presence, RegisterRequest, RegisterResponse, ResolveResponse,
10    SyncRequest, SyncResponse, UserId,
11};
12use serde::{Deserialize, Serialize};
13
14use crate::error::{EnigmaNodeRegistryError, Result};
15use crate::store::Store;
16
17#[derive(Clone)]
18pub struct AppState {
19    pub store: Arc<Store>,
20}
21
22#[derive(Debug, Serialize, Deserialize)]
23#[serde(deny_unknown_fields)]
24struct OkResponse {
25    ok: bool,
26}
27
28#[derive(Debug, Serialize, Deserialize)]
29#[serde(deny_unknown_fields)]
30struct MergedResponse {
31    merged: usize,
32}
33
34#[derive(Debug, Serialize)]
35struct ErrorBody {
36    error: String,
37}
38
39pub fn build_router(state: AppState) -> axum::Router {
40    axum::Router::new()
41        .route("/register", post(register))
42        .route("/resolve/:user_id_hex", get(resolve))
43        .route("/check_user/:user_id_hex", get(check_user))
44        .route("/announce", post(announce))
45        .route("/sync", post(sync))
46        .route("/nodes", get(list_nodes).post(add_nodes))
47        .with_state(state)
48}
49
50async fn register(
51    State(state): State<AppState>,
52    payload: std::result::Result<Json<RegisterRequest>, axum::extract::rejection::JsonRejection>,
53) -> std::result::Result<impl IntoResponse, AppError> {
54    let Json(body) = payload?;
55    state.store.register(body.identity).await?;
56    Ok((StatusCode::OK, Json(RegisterResponse { ok: true })))
57}
58
59async fn resolve(
60    State(state): State<AppState>,
61    Path(user_id_hex): Path<String>,
62) -> std::result::Result<impl IntoResponse, AppError> {
63    let user_id = parse_user_id(&user_id_hex)?;
64    let identity = state.store.resolve(&user_id).await?;
65    Ok((StatusCode::OK, Json(ResolveResponse { identity })))
66}
67
68async fn check_user(
69    State(state): State<AppState>,
70    Path(user_id_hex): Path<String>,
71) -> std::result::Result<impl IntoResponse, AppError> {
72    let user_id = parse_user_id(&user_id_hex)?;
73    let exists = state.store.check_user(&user_id).await?;
74    Ok((StatusCode::OK, Json(CheckUserResponse { exists })))
75}
76
77async fn announce(
78    State(state): State<AppState>,
79    payload: std::result::Result<Json<Presence>, axum::extract::rejection::JsonRejection>,
80) -> std::result::Result<impl IntoResponse, AppError> {
81    let Json(body) = payload?;
82    state.store.announce(body).await?;
83    Ok((StatusCode::OK, Json(OkResponse { ok: true })))
84}
85
86async fn sync(
87    State(state): State<AppState>,
88    payload: std::result::Result<Json<SyncRequest>, axum::extract::rejection::JsonRejection>,
89) -> std::result::Result<impl IntoResponse, AppError> {
90    let Json(body) = payload?;
91    let merged = state.store.sync_identities(body.identities).await?;
92    Ok((StatusCode::OK, Json(SyncResponse { merged })))
93}
94
95async fn list_nodes(
96    State(state): State<AppState>,
97) -> std::result::Result<impl IntoResponse, AppError> {
98    let nodes = state.store.list_nodes().await?;
99    Ok((StatusCode::OK, Json(NodesPayload { nodes })))
100}
101
102async fn add_nodes(
103    State(state): State<AppState>,
104    payload: std::result::Result<Json<NodesPayload>, axum::extract::rejection::JsonRejection>,
105) -> std::result::Result<impl IntoResponse, AppError> {
106    let Json(body) = payload?;
107    let merged = state.store.add_nodes(body.nodes).await?;
108    Ok((StatusCode::OK, Json(MergedResponse { merged })))
109}
110
111fn parse_user_id(user_id_hex: &str) -> Result<UserId> {
112    UserId::from_hex(user_id_hex).map_err(|_| EnigmaNodeRegistryError::InvalidInput("user_id"))
113}
114
115#[derive(Debug)]
116pub struct AppError(pub EnigmaNodeRegistryError);
117
118impl IntoResponse for AppError {
119    fn into_response(self) -> Response {
120        let status = match self.0 {
121            EnigmaNodeRegistryError::InvalidInput(_) => StatusCode::BAD_REQUEST,
122            EnigmaNodeRegistryError::JsonError => StatusCode::BAD_REQUEST,
123            EnigmaNodeRegistryError::Conflict => StatusCode::CONFLICT,
124            EnigmaNodeRegistryError::NotFound => StatusCode::NOT_FOUND,
125            EnigmaNodeRegistryError::Transport => StatusCode::BAD_GATEWAY,
126            EnigmaNodeRegistryError::Internal => StatusCode::INTERNAL_SERVER_ERROR,
127        };
128        let body = Json(ErrorBody {
129            error: self.0.to_string(),
130        });
131        (status, body).into_response()
132    }
133}
134
135impl From<EnigmaNodeRegistryError> for AppError {
136    fn from(err: EnigmaNodeRegistryError) -> Self {
137        AppError(err)
138    }
139}
140
141impl From<axum::extract::rejection::JsonRejection> for AppError {
142    fn from(_: axum::extract::rejection::JsonRejection) -> Self {
143        AppError(EnigmaNodeRegistryError::JsonError)
144    }
145}