diff --git a/apps/app_api/src/api.rs b/apps/app_api/src/api.rs index 4d016fd..dbca53b 100644 --- a/apps/app_api/src/api.rs +++ b/apps/app_api/src/api.rs @@ -2,8 +2,11 @@ use crate::state::AppState; use axum::Router; use std::sync::Arc; +pub mod dictionary; pub mod health; pub fn routes() -> Router> { - Router::new().nest("/api", health::routes()) + Router::new() + .nest("/api", health::routes()) + .nest("/api", dictionary::routes()) } diff --git a/apps/app_api/src/api/dictionary.rs b/apps/app_api/src/api/dictionary.rs new file mode 100644 index 0000000..ae7afcb --- /dev/null +++ b/apps/app_api/src/api/dictionary.rs @@ -0,0 +1,78 @@ +use axum::{Json, Router, extract::State, http::StatusCode, response::IntoResponse, routing::get}; +use serde::Serialize; +use std::sync::Arc; + +use crate::state::AppState; + +// --- DTOs --- + +#[derive(Debug, Serialize)] +pub struct DictListResponse { + pub dictionaries: Vec, +} + +#[derive(Debug, Serialize)] +pub struct DictListEntryResponse { + pub name: String, + pub entry_count: u64, +} + +#[derive(Debug, Serialize)] +pub struct ErrorResponse { + pub error: String, +} + +impl IntoResponse for ErrorResponse { + fn into_response(self) -> axum::response::Response { + (StatusCode::INTERNAL_SERVER_ERROR, Json(self)).into_response() + } +} + +impl From for ErrorResponse { + fn from(err: anyhow::Error) -> Self { + Self { + error: err.to_string(), + } + } +} + +impl From for ErrorResponse { + fn from(err: applib::RepositoryError) -> Self { + Self { + error: err.to_string(), + } + } +} + +// --- Handlers --- + +pub async fn list_dicts_handler( + State(state): State>, +) -> Result, ErrorResponse> { + let default_repo = state.container.create_dict_repo("default").await?; + + let dict_names = default_repo.fetch_dicts().await?; + + let mut entries = Vec::with_capacity(dict_names.len()); + + for dict_name in dict_names { + let dict_repo = state.container.create_dict_repo(&dict_name).await?; + + let entry_count = dict_repo.count_entries().await?; + + entries.push(DictListEntryResponse { + name: dict_name, + entry_count, + }); + } + + Ok(Json(DictListResponse { + dictionaries: entries, + })) +} + +// --- Router --- + +pub fn routes() -> Router> { + Router::new().route("/dicts", get(list_dicts_handler)) +} diff --git a/apps/app_api/src/commands/listen.rs b/apps/app_api/src/commands/listen.rs index 431004d..56e156a 100644 --- a/apps/app_api/src/commands/listen.rs +++ b/apps/app_api/src/commands/listen.rs @@ -61,7 +61,7 @@ impl Executable for ListenCmd { .as_ref() .ok_or_else(|| anyhow::anyhow!("Listen config missing"))?; - let app = router::create_router(); + let app = router::create_router().await?; let addr = format!("{}:{}", listen_config.host, listen_config.port); let listener = TcpListener::bind(&addr).await?; diff --git a/apps/app_api/src/container.rs b/apps/app_api/src/container.rs index 981a0b3..9a319a7 100644 --- a/apps/app_api/src/container.rs +++ b/apps/app_api/src/container.rs @@ -1,11 +1,11 @@ -// use std::sync::Arc; +use std::sync::Arc; -// use applib::DictImporter; -// use applib::DictRepository; -// use applib::SqliteDictRepository; -// use applib::SystemDecoder; -// use applib::SystemEncoder; -// use applib::sys_major as major; +use applib::DictImporter; +use applib::DictRepository; +use applib::SqliteDictRepository; +use applib::SystemDecoder; +use applib::SystemEncoder; +use applib::sys_major as major; #[derive(Clone)] pub struct Container; @@ -15,17 +15,17 @@ impl Container { Ok(Self) } - // pub async fn create_dict_importer(&self, dict_name: &str) -> anyhow::Result { - // let repo = self.create_dict_repo(dict_name).await?; - // Ok(DictImporter::new(repo)) - // } + pub async fn create_dict_importer(&self, dict_name: &str) -> anyhow::Result { + let repo = self.create_dict_repo(dict_name).await?; + Ok(DictImporter::new(repo)) + } - // pub async fn create_dict_repo( - // &self, - // dict_name: &str, - // ) -> anyhow::Result> { - // let mut dict_repo = SqliteDictRepository::new("sqlite:app.db").await?; - // dict_repo.use_dict(dict_name); - // Ok(Arc::new(dict_repo)) - // } + pub async fn create_dict_repo( + &self, + dict_name: &str, + ) -> anyhow::Result> { + let mut dict_repo = SqliteDictRepository::new("sqlite:app.db").await?; + dict_repo.use_dict(dict_name); + Ok(Arc::new(dict_repo)) + } } diff --git a/apps/app_api/src/router.rs b/apps/app_api/src/router.rs index 14b8078..32c5051 100644 --- a/apps/app_api/src/router.rs +++ b/apps/app_api/src/router.rs @@ -5,11 +5,11 @@ use tower_http::{cors::CorsLayer, trace::TraceLayer}; use crate::api; use crate::state::AppState; -pub fn create_router() -> Router { - let state = Arc::new(AppState::new()); +pub async fn create_router() -> anyhow::Result { + let state = Arc::new(AppState::new().await?); - api::routes() + Ok(api::routes() .with_state(state) .layer(TraceLayer::new_for_http()) - .layer(CorsLayer::permissive()) + .layer(CorsLayer::permissive())) } diff --git a/apps/app_api/src/state.rs b/apps/app_api/src/state.rs index 5a2e6b4..2aa0d4e 100644 --- a/apps/app_api/src/state.rs +++ b/apps/app_api/src/state.rs @@ -1,23 +1,21 @@ pub const APP_NAME: &str = env!("CARGO_PKG_NAME"); pub const APP_VERSION: &str = env!("CARGO_PKG_VERSION"); +use crate::container::Container; + #[derive(Clone)] pub struct AppState { pub name: String, pub version: String, + pub container: Container, } impl AppState { - pub fn new() -> Self { - Self { + pub async fn new() -> anyhow::Result { + Ok(Self { name: APP_NAME.to_string(), version: APP_VERSION.to_string(), - } - } -} - -impl Default for AppState { - fn default() -> Self { - Self::new() + container: Container::new().await?, + }) } } diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 5fc5398..accf429 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -14,6 +14,8 @@ pub mod sys_major; pub use self::common::SystemDecoder; pub use self::common::SystemEncoder; +pub use self::common::errors::RepositoryError; + pub use self::dictionary::DictImporter; pub use self::dictionary::DictRepository; pub use self::dictionary::JsonFileDictSource;