100 lines
3.4 KiB
Rust
100 lines
3.4 KiB
Rust
//! # Zesdex REST API — Axum HTTP server
|
|||
|
|
//!
|
||
|
|
//! Provides RESTful endpoints for the Zesdex application, enabling
|
||
|
|
//! web clients, mobile apps, and third-party integrations.
|
||
|
|
//!
|
||
|
|
//! ## Architecture
|
||
|
|
//!
|
||
|
|
//! ```text
|
||
|
|
//! src/
|
||
|
|
//! ├── lib.rs — Module declarations, re-exports, router builder
|
||
|
|
//! ├── state.rs — ApiState with concrete service implementations
|
||
|
|
//! ├── error.rs — ApiError enum + IntoResponse
|
||
|
|
//! ├── dto/ — Request/response DTOs (serde)
|
||
|
|
//! ├── handlers/ — Axum route handlers
|
||
|
|
//! └── middleware/ — Tower layers (JWT auth, etc.)
|
||
|
|
//! ```
|
||
|
|
//!
|
||
|
|
//! ## Flow
|
||
|
|
//!
|
||
|
|
//! 1. `build_router()` constructs an Axum `Router` with all routes nested.
|
||
|
|
//! 2. Each handler receives `State<Arc<ApiState>>` or direct extractors.
|
||
|
|
//! 3. Handlers delegate to application-layer service implementations.
|
||
|
|
//! 4. Domain/infrastructure errors are mapped to `ApiError` → HTTP status codes.
|
||
|
|
|
||
|
|
pub mod dto;
|
||
|
|
pub mod error;
|
||
|
|
pub mod handlers;
|
||
|
|
pub mod middleware;
|
||
|
|
pub mod state;
|
||
|
|
|
||
|
|
pub use error::ApiError;
|
||
|
|
pub use state::ApiState;
|
||
|
|
|
||
|
|
use std::sync::Arc;
|
||
|
|
use axum::Router;
|
||
|
|
use tower_http::cors::CorsLayer;
|
||
|
|
|
||
|
|
/// Build the API router with all routes registered.
|
||
|
|
///
|
||
|
|
/// Flow: create CORS layer → build sub-routers for each resource → nest
|
||
|
|
/// them under `/api/v1` → attach shared state → return.
|
||
|
|
///
|
||
|
|
/// ## Arguments
|
||
|
|
/// * `state` — shared application state (wrapped in `Arc` for clone-free sharing)
|
||
|
|
///
|
||
|
|
/// ## Example
|
||
|
|
/// ```ignore
|
||
|
|
/// let state = ApiState::new("/path/to/data");
|
||
|
|
/// let app = build_router(state);
|
||
|
|
/// let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
||
|
|
/// axum::serve(listener, app).await.unwrap();
|
||
|
|
/// ```
|
||
|
|
pub fn build_router(state: ApiState) -> Router {
|
||
|
|
let shared_state: Arc<ApiState> = Arc::new(state);
|
||
|
|
|
||
|
|
// CORS layer — permissive for local daemon / development use
|
||
|
|
let cors = CorsLayer::permissive();
|
||
|
|
|
||
|
|
// Combine all sub-routers under a versioned prefix
|
||
|
|
Router::new()
|
||
|
|
.nest("/api/v1", api_v1_router())
|
||
|
|
.layer(cors)
|
||
|
|
.with_state(shared_state)
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Version 1 API sub-router.
|
||
|
|
///
|
||
|
|
/// Groups all resource routes under `/api/v1/*`.
|
||
|
|
fn api_v1_router() -> Router<Arc<ApiState>> {
|
||
|
|
use handlers::{auth, chat, conversations, health, sessions};
|
||
|
|
|
||
|
|
// Sessions router combines session CRUD + nested conversations
|
||
|
|
let sessions_router = Router::new()
|
||
|
|
.route("/", axum::routing::get(sessions::list_sessions_handler))
|
||
|
|
.route("/", axum::routing::post(sessions::create_session_handler))
|
||
|
|
.route("/{id}", axum::routing::delete(sessions::delete_session_handler))
|
||
|
|
// Conversations are sub-resources of sessions
|
||
|
|
.route(
|
||
|
|
"/{id}/conversations",
|
||
|
|
axum::routing::get(conversations::get_conversation_handler),
|
||
|
|
)
|
||
|
|
.route(
|
||
|
|
"/{id}/conversations",
|
||
|
|
axum::routing::post(conversations::add_message_handler),
|
||
|
|
)
|
||
|
|
.route(
|
||
|
|
"/{id}/conversations/{cid}",
|
||
|
|
axum::routing::delete(conversations::delete_message_handler),
|
||
|
|
);
|
||
|
|
|
||
|
|
Router::new()
|
||
|
|
.route("/health", axum::routing::get(health::health))
|
||
|
|
.nest("/auth", auth::router())
|
||
|
|
.nest("/sessions", sessions_router)
|
||
|
|
.nest("/chat", chat::router())
|
||
|
|
}
|
||
|
|
|
||
|
|
// Re-export commonly used types at the crate root for ergonomic access.
|
||
|
|
pub use axum::http::StatusCode;
|