webapi, inital schema and routes (get_users)

provides the infrastructure for the webapi including setting up the
server and the routes.

Implements get_users as a test route
This commit is contained in:
Julia Lange 2026-03-03 10:45:36 -08:00
parent 1400b2fc95
commit 92763fd7dc
Signed by: Julia
SSH key fingerprint: SHA256:5DJcfxa5/fKCYn57dcabJa2vN2e6eT0pBerYi5SUbto
7 changed files with 197 additions and 0 deletions

View file

@ -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();
}

View file

@ -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<User>,
}
pub async fn handler(
State(state): State<AppState>,
Json(_body): Json<Input>,
) -> ApiResult<Output> {
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<User> = dbusers.iter().map(|u| User {
name: u.name().to_string(),
}).collect();
Ok(ApiResponse(StatusCode::OK, Output { users }))
}

View file

@ -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<T> = Result<ApiResponse<T>, ApiError>;
pub struct ApiResponse<T>(pub StatusCode, pub T);
pub struct ApiError {
pub status: StatusCode,
pub error: &'static str,
pub message: String,
}
pub fn router() -> Router<AppState> {
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<T: Serialize> IntoResponse for ApiResponse<T> {
fn into_response(self) -> axum::response::Response {
(self.0, Json(self.1)).into_response()
}
}

View file

@ -0,0 +1,6 @@
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
pub struct User {
pub name: String,
}