diff --git a/koucha/Cargo.lock b/koucha/Cargo.lock index c9fd8ab..d78118a 100644 --- a/koucha/Cargo.lock +++ b/koucha/Cargo.lock @@ -73,6 +73,70 @@ dependencies = [ "fs_extra", ] +[[package]] +name = "axum" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" +dependencies = [ + "axum-core", + "axum-macros", + "bytes", + "form_urlencoded", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "base64" version = "0.22.1" @@ -726,6 +790,12 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + [[package]] name = "hyper" version = "1.8.1" @@ -740,6 +810,7 @@ dependencies = [ "http", "http-body", "httparse", + "httpdate", "itoa", "pin-project-lite", "pin-utils", @@ -999,9 +1070,12 @@ dependencies = [ name = "koucha" version = "0.1.0" dependencies = [ + "axum", "chrono", "reqwest", "rss", + "serde", + "serde_json", "sqlx", "tokio", ] @@ -1076,6 +1150,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + [[package]] name = "md-5" version = "0.10.6" @@ -1708,6 +1788,17 @@ dependencies = [ "zmij", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -2221,6 +2312,7 @@ dependencies = [ "tokio", "tower-layer", "tower-service", + "tracing", ] [[package]] diff --git a/koucha/Cargo.toml b/koucha/Cargo.toml index 1106901..2de5afc 100644 --- a/koucha/Cargo.toml +++ b/koucha/Cargo.toml @@ -9,3 +9,6 @@ rss = "2.0.12" tokio = { version = "1.49.0", features = ["full"] } sqlx = { version = "0.8.6", features = [ "runtime-tokio", "sqlite" ] } chrono = "0.4.43" +axum = { version= "0.8.8", features = [ "macros" ] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0.149" diff --git a/koucha/src/bin/server.rs b/koucha/src/bin/server.rs deleted file mode 100644 index e69de29..0000000 diff --git a/koucha/src/bin/webapi/main.rs b/koucha/src/bin/webapi/main.rs new file mode 100644 index 0000000..21cb1eb --- /dev/null +++ b/koucha/src/bin/webapi/main.rs @@ -0,0 +1,25 @@ +use koucha::{ + AdapterBuilder, + Adapter, +}; + +mod routes; +mod types; + +#[derive(Clone)] +struct AppState { + pub adapter: Adapter, +} + +#[tokio::main] +async fn main() { + let db_url = std::env::var("DATABASE_URL").unwrap(); + let adapter = AdapterBuilder::new() + .database_url(&db_url) + .create().await.unwrap(); + + let app = routes::router().with_state(AppState { adapter }); + + let listener = tokio::net::TcpListener::bind("0.0.0.0:4142").await.unwrap(); + axum::serve(listener, app).await.unwrap(); +} diff --git a/koucha/src/bin/webapi/routes/get_users.rs b/koucha/src/bin/webapi/routes/get_users.rs new file mode 100644 index 0000000..14ecf38 --- /dev/null +++ b/koucha/src/bin/webapi/routes/get_users.rs @@ -0,0 +1,36 @@ +use axum::{Json, extract::State}; +use koucha::db::User as DbUser; +use reqwest::StatusCode; +use serde::{Serialize, Deserialize}; + +use crate::{ + AppState, routes::{ApiError, ApiResult, ApiResponse}, types::User +}; + +#[derive(Deserialize)] +pub struct Input { } +#[derive(Serialize, Deserialize)] +pub struct Output { + users: Vec, +} + +pub async fn handler( + State(state): State, + Json(_body): Json, +) -> ApiResult { + let dbusers = DbUser::get_all(state.adapter.get_pool()) + .await.map_err(|_e| { + // TODO: Logging + ApiError { + status: StatusCode::INTERNAL_SERVER_ERROR, + error: "InternalError", + message: "Error getting all users from DB.".to_string(), + } + })?; + + let users: Vec = dbusers.iter().map(|u| User { + name: u.name().to_string(), + }).collect(); + + Ok(ApiResponse(StatusCode::OK, Output { users })) +} diff --git a/koucha/src/bin/webapi/routes/mod.rs b/koucha/src/bin/webapi/routes/mod.rs new file mode 100644 index 0000000..acc21cf --- /dev/null +++ b/koucha/src/bin/webapi/routes/mod.rs @@ -0,0 +1,35 @@ +use serde::Serialize; +use serde_json::json; +use axum::{Json, Router, response::IntoResponse, routing::get}; +use reqwest::StatusCode; + +mod get_users; + +use crate::AppState; + +pub type ApiResult = Result, ApiError>; + +pub struct ApiResponse(pub StatusCode, pub T); + +pub struct ApiError { + pub status: StatusCode, + pub error: &'static str, + pub message: String, +} + +pub fn router() -> Router { + Router::new() + .route("/get_users", get(get_users::handler)) +} + +impl IntoResponse for ApiError { + fn into_response(self) -> axum::response::Response { + let body = json!({ "error": self.error, "message": self.message }); + (self.status, Json(body)).into_response() + } +} +impl IntoResponse for ApiResponse { + fn into_response(self) -> axum::response::Response { + (self.0, Json(self.1)).into_response() + } +} diff --git a/koucha/src/bin/webapi/types.rs b/koucha/src/bin/webapi/types.rs new file mode 100644 index 0000000..2216352 --- /dev/null +++ b/koucha/src/bin/webapi/types.rs @@ -0,0 +1,6 @@ +use serde::{Serialize, Deserialize}; + +#[derive(Serialize, Deserialize)] +pub struct User { + pub name: String, +}