Files
zesdex/apps/interfaces/api/src/lib.rs
T
asepharyana 7b0b53671f style: format seluruh workspace dengan cargo fmt
Menyeragamkan format kode sesuai rustfmt (126 file). Sebelumnya
lefthook pre-commit 'cargo fmt --check' akan gagal pada commit apa pun.
2026-08-27 22:10:28 +07:00

114 lines
3.9 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 axum::Router;
use std::sync::Arc;
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();
// JWT auth middleware — protects session/chat routes.
// Auth (login/register/refresh) and health endpoints stay public.
let jwt_auth = middleware::auth::JwtAuthLayer::new(shared_state.clone());
// Combine all sub-routers under a versioned prefix
Router::new()
.nest("/api/v1/auth", auth_router())
.nest("/api/v1/health", health_router())
.nest("/api/v1", protected_router().layer(jwt_auth))
.layer(cors)
.with_state(shared_state)
}
/// Auth + health sub-routers — publicly accessible (no JWT required).
fn auth_router() -> Router<Arc<ApiState>> {
handlers::auth::router()
}
fn health_router() -> Router<Arc<ApiState>> {
Router::new().route("/", axum::routing::get(handlers::health::health))
}
/// Protected sub-router — sessions + chat, guarded by JWT auth layer.
fn protected_router() -> Router<Arc<ApiState>> {
use handlers::{chat, conversations, 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()
.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;