diff --git a/.env.sample b/.env.sample index cc1df28..fa1bd23 100644 --- a/.env.sample +++ b/.env.sample @@ -1 +1,4 @@ +APP_HOST=127.0.0.1 +APP_PORT=3000 DATABASE_URL=postgresql://miniweb:miniweb@localhost:54321/miniweb +RUST_LOG=info diff --git a/rear/src/admin/mod.rs b/rear/src/admin/mod.rs index 268f8ee..fc36eca 100644 --- a/rear/src/admin/mod.rs +++ b/rear/src/admin/mod.rs @@ -4,21 +4,24 @@ pub mod domain; pub mod state; pub mod views; -pub fn route() -> Router { +pub fn routes() -> Router { Router::new() - .route("/", get(views::index).post(views::index_action)) - .route("/app/:app", get(views::list_app)) - .route("/app/:app/model/:model", get(views::list_item_collection)) + .route("/", get(views::index::).post(views::index_action::)) + .route("/app/:app", get(views::list_app::)) + .route( + "/app/:app/model/:model", + get(views::list_item_collection::), + ) .route( "/app/:app/model/:model/add", - get(views::new_item).post(views::create_item), + get(views::new_item::).post(views::create_item::), ) .route( "/app/:app/model/:model/change/:id", - get(views::change_item).patch(views::update_item), + get(views::change_item::).patch(views::update_item::), ) .route( "/app/:app/model/:model/detail/:id", - get(views::view_item_details), + get(views::view_item_details::), ) } diff --git a/rear/src/admin/views.rs b/rear/src/admin/views.rs index 2f441d2..9b80231 100644 --- a/rear/src/admin/views.rs +++ b/rear/src/admin/views.rs @@ -9,6 +9,7 @@ use serde_json::Value; use crate::admin::domain::{AdminApp, AdminModel}; use crate::admin::state; +use crate::admin::state::AdminState; use crate::service::templates; use serde::{Deserialize, Serialize}; @@ -76,11 +77,12 @@ pub fn base_template(headers: &HeaderMap) -> Option { } } -pub async fn index( - templates: State, - registry: State>, +pub async fn index( + admin: State, headers: HeaderMap, ) -> impl IntoResponse { + let templates = admin.get_templates(); + let registry = admin.get_registry(); templates.render_html( "admin/index.html", AdminContext { @@ -92,26 +94,29 @@ pub async fn index( } // Index Action is POST to the index site. We can anchor some general business code here. -pub async fn index_action() -> impl IntoResponse { +pub async fn index_action( + admin: State, +) -> impl IntoResponse { "There is your answer!".to_owned() } -pub async fn list_app( - templates: State, +pub async fn list_app( + admin: State, Path(app_key): Path, ) -> impl IntoResponse { + let templates = admin.get_templates(); templates.render_html("admin/app_list.jinja", ()) } // List Items renders the entire list item page. -pub async fn list_item_collection( - templates: State, - registry: State>, +pub async fn list_item_collection( + admin: State, headers: HeaderMap, Path((app_key, model_key)): Path<(String, String)>, ) -> impl IntoResponse { info!("list_item_collection {} for model {}", app_key, model_key); - + let templates = admin.get_templates(); + let registry = admin.get_registry(); let context = if let Ok(repo) = registry.get_repository(&app_key, &model_key) { let repo = repo.lock().await; let admin_model = registry @@ -136,19 +141,21 @@ pub async fn list_item_collection( } // Items Action is a POST to an item list. By default these are actions, that work on a list of items as input. -pub async fn item_collection_action( +pub async fn item_collection_action( + admin: State, Path((app_key, model_key)): Path<(String, String)>, ) -> impl IntoResponse { "There is your answer!".to_owned() } // Item Details shows one single dataset. -pub async fn view_item_details( - templates: State, - registry: State>, +pub async fn view_item_details( + admin: State, headers: HeaderMap, Path((app_key, model_key, id)): Path<(String, String, String)>, ) -> impl IntoResponse { + let templates = admin.get_templates(); + let registry = admin.get_registry(); let context = if let Ok(repo) = registry.get_repository(&app_key, &model_key) { let repo = repo.lock().await; let admin_model = registry @@ -174,12 +181,13 @@ pub async fn view_item_details( templates.render_html("admin/items/item_detail.jinja", context) } -pub async fn new_item( - templates: State, - registry: State>, +pub async fn new_item( + admin: State, headers: HeaderMap, Path((app_key, model_key)): Path<(String, String)>, ) -> impl IntoResponse { + let templates = admin.get_templates(); + let registry = admin.get_registry(); let context = if let Ok(repo) = registry.get_repository(&app_key, &model_key) { let repo = repo.lock().await; let admin_model = registry @@ -203,13 +211,14 @@ pub async fn new_item( templates.render_html("admin/items/item_create.jinja", context) } -pub async fn create_item( - templates: State, - registry: State>, +pub async fn create_item( + admin: State, headers: HeaderMap, Path((app_key, model_key)): Path<(String, String)>, Form(form): Form, ) -> impl IntoResponse { + let templates = admin.get_templates(); + let registry = admin.get_registry(); let context = if let Ok(repo) = registry.get_repository(&app_key, &model_key) { let mut repo = repo.lock().await; let admin_model = registry @@ -240,12 +249,13 @@ pub async fn create_item( } /// Change is the GET version. -pub async fn change_item( - templates: State, - registry: State>, +pub async fn change_item( + admin: State, headers: HeaderMap, Path((app_key, model_key, id)): Path<(String, String, String)>, ) -> impl IntoResponse { + let templates = admin.get_templates(); + let registry = admin.get_registry(); let context = if let Ok(repo) = registry.get_repository(&app_key, &model_key) { let repo = repo.lock().await; let admin_model = registry @@ -271,13 +281,14 @@ pub async fn change_item( templates.render_html("admin/items/item_change.jinja", context) } -pub async fn update_item( - templates: State, - registry: State>, +pub async fn update_item( + admin: State, headers: HeaderMap, Path((app_key, model_key, id)): Path<(String, String, String)>, Form(form): Form, ) -> impl IntoResponse { + let templates = admin.get_templates(); + let registry = admin.get_registry(); let context = if let Ok(repo) = registry.get_repository(&app_key, &model_key) { let mut repo = repo.lock().await; let admin_model = registry @@ -307,13 +318,17 @@ pub async fn update_item( } // Item Action allows running an action on one single dataset. -pub async fn item_action( +pub async fn item_action( + admin: State, Path((app_key, model_key, model_id)): Path<(String, String, String)>, ) -> impl IntoResponse { "There is your answer!".to_owned() } -pub async fn debug_view(Path(data): Path) -> impl IntoResponse { +pub async fn debug_view( + admin: State, + Path(data): Path, +) -> impl IntoResponse { println!("debug: {}", data); "Debug!".to_owned() } diff --git a/rear/src/service/handlers.rs b/rear/src/service/handlers.rs index 8f0a1ab..01a6be1 100644 --- a/rear/src/service/handlers.rs +++ b/rear/src/service/handlers.rs @@ -9,17 +9,6 @@ use axum::{ use rust_embed::RustEmbed; use std::marker::PhantomData; -// usage: .route_service("/static/*file", static_handler.into_service()) -pub async fn static_handler(uri: Uri) -> impl IntoResponse { - let mut path = uri.path().trim_start_matches('/').to_string(); - - if path.starts_with("static/") { - path = path.replace("static/", ""); - } - - StaticFile::get(path) -} - // usage: .fallback(not_found_handler) pub async fn not_found_handler( State(templates): State, @@ -31,43 +20,3 @@ fn not_found(templates: Templates) -> axum::response::Result let body = templates.render_html("http404.html", ()); Ok((StatusCode::NOT_FOUND, body)) } - -pub struct EmbeddedFile { - pub path: T, - embed: PhantomData, -} - -impl EmbeddedFile { - pub fn get(path: T) -> Self { - Self { - path, - embed: PhantomData, - } - } -} - -impl IntoResponse for EmbeddedFile -where - E: RustEmbed, - T: AsRef, -{ - fn into_response(self) -> Response { - let path: &str = self.path.as_ref(); - match E::get(path) { - Some(content) => { - let body = Body::from(content.data); - let mime = mime_guess::from_path(path).first_or_octet_stream(); - Response::builder() - .header(header::CONTENT_TYPE, mime.as_ref()) - .body(body) - .unwrap() - } - None => StatusCode::NOT_FOUND.into_response(), - } - } -} - -#[derive(RustEmbed)] -#[folder = "static"] -struct StaticDir; -type StaticFile = EmbeddedFile; diff --git a/src/embed.rs b/src/embed.rs new file mode 100644 index 0000000..285b815 --- /dev/null +++ b/src/embed.rs @@ -0,0 +1,58 @@ +use axum::{ + body::Body, + http::{header, StatusCode, Uri}, + response::{IntoResponse, Response}, +}; +use rust_embed::RustEmbed; +use std::marker::PhantomData; + +pub struct EmbeddedFile { + pub path: T, + embed: PhantomData, +} + +impl EmbeddedFile { + pub fn get(path: T) -> Self { + Self { + path, + embed: PhantomData, + } + } +} + +impl IntoResponse for EmbeddedFile +where + E: RustEmbed, + T: AsRef, +{ + fn into_response(self) -> Response { + let path: &str = self.path.as_ref(); + match E::get(path) { + Some(content) => { + let body = Body::from(content.data); + let mime = mime_guess::from_path(path).first_or_octet_stream(); + Response::builder() + .header(header::CONTENT_TYPE, mime.as_ref()) + .body(body) + .unwrap() + } + None => StatusCode::NOT_FOUND.into_response(), + } + } +} + +#[derive(RustEmbed)] +#[folder = "static"] +struct StaticDir; +type StaticFile = EmbeddedFile; + +// usage: .route_service("/static/*file", static_handler.into_service()) +pub async fn static_handler(uri: Uri) -> impl IntoResponse { + let mut path = uri.path().trim_start_matches('/').to_string(); + + if path.starts_with("static/") { + path = path.replace("static/", ""); + } + + StaticFile::get(path) +} diff --git a/src/howto/views.rs b/src/howto/views.rs index 0974caf..a69765c 100644 --- a/src/howto/views.rs +++ b/src/howto/views.rs @@ -1,6 +1,6 @@ use axum::{extract::State, response::IntoResponse, Form}; -use crate::service::templates; +use rear::service::templates; use serde::Deserialize; #[derive(Deserialize)] diff --git a/src/lib.rs b/src/lib.rs index 1ed76c4..eba3a3e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1 @@ -pub mod admin; -pub mod auth; -pub mod service; pub mod state; diff --git a/src/main.rs b/src/main.rs index a4ab76f..bf6fad5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ mod admin_examples; +mod embed; mod howto; mod state; @@ -16,6 +17,7 @@ use axum::{ }; use dotenvy::dotenv; use log::info; +use rear::admin; use rear::service::{handlers, templates}; use std::env; use std::net::SocketAddr; @@ -66,7 +68,7 @@ async fn main() { //.merge(admin_router) .nest("/admin", admin::routes()) .nest("/howto", howto::routes()) - .route_service("/static/*file", handlers::static_handler.into_service()) + .route_service("/static/*file", embed::static_handler.into_service()) .fallback(handlers::not_found_handler) .with_state(state) .layer( diff --git a/src/state.rs b/src/state.rs index 2b4eb48..4b2d1df 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,12 +1,12 @@ use axum::extract::FromRef; -use rear::admin::state::SharedRegistry; +use rear::admin::state::{AdminState, SharedRegistry}; use rear::service::templates; #[derive(Clone)] pub struct AppState { pub templates: templates::Templates, - pub admin: AdminState, + pub admin: SharedRegistry, } impl FromRef for templates::Templates { @@ -20,3 +20,13 @@ impl FromRef for SharedRegistry { app_state.admin.clone() } } + +impl AdminState for AppState { + fn get_templates(&self) -> &templates::Templates { + &self.templates + } + + fn get_registry(&self) -> SharedRegistry { + self.admin.clone() + } +}