Refactoring to Rear #2
@ -1 +1,4 @@
|
|||||||
|
APP_HOST=127.0.0.1
|
||||||
|
APP_PORT=3000
|
||||||
DATABASE_URL=postgresql://miniweb:miniweb@localhost:54321/miniweb
|
DATABASE_URL=postgresql://miniweb:miniweb@localhost:54321/miniweb
|
||||||
|
RUST_LOG=info
|
||||||
|
@ -4,21 +4,24 @@ pub mod domain;
|
|||||||
pub mod state;
|
pub mod state;
|
||||||
pub mod views;
|
pub mod views;
|
||||||
|
|
||||||
pub fn route() -> Router<AppState> {
|
pub fn routes<S: state::AdminState + Clone + Send + Sync + 'static>() -> Router<S> {
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/", get(views::index).post(views::index_action))
|
.route("/", get(views::index::<S>).post(views::index_action::<S>))
|
||||||
.route("/app/:app", get(views::list_app))
|
.route("/app/:app", get(views::list_app::<S>))
|
||||||
.route("/app/:app/model/:model", get(views::list_item_collection))
|
.route(
|
||||||
|
"/app/:app/model/:model",
|
||||||
|
get(views::list_item_collection::<S>),
|
||||||
|
)
|
||||||
.route(
|
.route(
|
||||||
"/app/:app/model/:model/add",
|
"/app/:app/model/:model/add",
|
||||||
get(views::new_item).post(views::create_item),
|
get(views::new_item::<S>).post(views::create_item::<S>),
|
||||||
)
|
)
|
||||||
.route(
|
.route(
|
||||||
"/app/:app/model/:model/change/:id",
|
"/app/:app/model/:model/change/:id",
|
||||||
get(views::change_item).patch(views::update_item),
|
get(views::change_item::<S>).patch(views::update_item::<S>),
|
||||||
)
|
)
|
||||||
.route(
|
.route(
|
||||||
"/app/:app/model/:model/detail/:id",
|
"/app/:app/model/:model/detail/:id",
|
||||||
get(views::view_item_details),
|
get(views::view_item_details::<S>),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ use serde_json::Value;
|
|||||||
|
|
||||||
use crate::admin::domain::{AdminApp, AdminModel};
|
use crate::admin::domain::{AdminApp, AdminModel};
|
||||||
use crate::admin::state;
|
use crate::admin::state;
|
||||||
|
use crate::admin::state::AdminState;
|
||||||
use crate::service::templates;
|
use crate::service::templates;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@ -76,11 +77,12 @@ pub fn base_template(headers: &HeaderMap) -> Option<String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn index(
|
pub async fn index<S: AdminState + Clone + Send + Sync + 'static>(
|
||||||
templates: State<templates::Templates>,
|
admin: State<S>,
|
||||||
registry: State<Arc<state::AdminRegistry>>,
|
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
|
let templates = admin.get_templates();
|
||||||
|
let registry = admin.get_registry();
|
||||||
templates.render_html(
|
templates.render_html(
|
||||||
"admin/index.html",
|
"admin/index.html",
|
||||||
AdminContext {
|
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.
|
// 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<S: AdminState + Clone + Send + Sync + 'static>(
|
||||||
|
admin: State<S>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
"There is your answer!".to_owned()
|
"There is your answer!".to_owned()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn list_app(
|
pub async fn list_app<S: AdminState + Clone + Send + Sync + 'static>(
|
||||||
templates: State<templates::Templates>,
|
admin: State<S>,
|
||||||
Path(app_key): Path<String>,
|
Path(app_key): Path<String>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
|
let templates = admin.get_templates();
|
||||||
templates.render_html("admin/app_list.jinja", ())
|
templates.render_html("admin/app_list.jinja", ())
|
||||||
}
|
}
|
||||||
|
|
||||||
// List Items renders the entire list item page.
|
// List Items renders the entire list item page.
|
||||||
pub async fn list_item_collection(
|
pub async fn list_item_collection<S: AdminState + Clone + Send + Sync + 'static>(
|
||||||
templates: State<templates::Templates>,
|
admin: State<S>,
|
||||||
registry: State<Arc<state::AdminRegistry>>,
|
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
Path((app_key, model_key)): Path<(String, String)>,
|
Path((app_key, model_key)): Path<(String, String)>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
info!("list_item_collection {} for model {}", app_key, model_key);
|
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 context = if let Ok(repo) = registry.get_repository(&app_key, &model_key) {
|
||||||
let repo = repo.lock().await;
|
let repo = repo.lock().await;
|
||||||
let admin_model = registry
|
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.
|
// 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<S: AdminState + Clone + Send + Sync + 'static>(
|
||||||
|
admin: State<S>,
|
||||||
Path((app_key, model_key)): Path<(String, String)>,
|
Path((app_key, model_key)): Path<(String, String)>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
"There is your answer!".to_owned()
|
"There is your answer!".to_owned()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Item Details shows one single dataset.
|
// Item Details shows one single dataset.
|
||||||
pub async fn view_item_details(
|
pub async fn view_item_details<S: AdminState + Clone + Send + Sync + 'static>(
|
||||||
templates: State<templates::Templates>,
|
admin: State<S>,
|
||||||
registry: State<Arc<state::AdminRegistry>>,
|
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
Path((app_key, model_key, id)): Path<(String, String, String)>,
|
Path((app_key, model_key, id)): Path<(String, String, String)>,
|
||||||
) -> impl IntoResponse {
|
) -> 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 context = if let Ok(repo) = registry.get_repository(&app_key, &model_key) {
|
||||||
let repo = repo.lock().await;
|
let repo = repo.lock().await;
|
||||||
let admin_model = registry
|
let admin_model = registry
|
||||||
@ -174,12 +181,13 @@ pub async fn view_item_details(
|
|||||||
templates.render_html("admin/items/item_detail.jinja", context)
|
templates.render_html("admin/items/item_detail.jinja", context)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn new_item(
|
pub async fn new_item<S: AdminState + Clone + Send + Sync + 'static>(
|
||||||
templates: State<templates::Templates>,
|
admin: State<S>,
|
||||||
registry: State<Arc<state::AdminRegistry>>,
|
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
Path((app_key, model_key)): Path<(String, String)>,
|
Path((app_key, model_key)): Path<(String, String)>,
|
||||||
) -> impl IntoResponse {
|
) -> 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 context = if let Ok(repo) = registry.get_repository(&app_key, &model_key) {
|
||||||
let repo = repo.lock().await;
|
let repo = repo.lock().await;
|
||||||
let admin_model = registry
|
let admin_model = registry
|
||||||
@ -203,13 +211,14 @@ pub async fn new_item(
|
|||||||
templates.render_html("admin/items/item_create.jinja", context)
|
templates.render_html("admin/items/item_create.jinja", context)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create_item(
|
pub async fn create_item<S: AdminState + Clone + Send + Sync + 'static>(
|
||||||
templates: State<templates::Templates>,
|
admin: State<S>,
|
||||||
registry: State<Arc<state::AdminRegistry>>,
|
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
Path((app_key, model_key)): Path<(String, String)>,
|
Path((app_key, model_key)): Path<(String, String)>,
|
||||||
Form(form): Form<Value>,
|
Form(form): Form<Value>,
|
||||||
) -> impl IntoResponse {
|
) -> 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 context = if let Ok(repo) = registry.get_repository(&app_key, &model_key) {
|
||||||
let mut repo = repo.lock().await;
|
let mut repo = repo.lock().await;
|
||||||
let admin_model = registry
|
let admin_model = registry
|
||||||
@ -240,12 +249,13 @@ pub async fn create_item(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Change is the GET version.
|
/// Change is the GET version.
|
||||||
pub async fn change_item(
|
pub async fn change_item<S: AdminState + Clone + Send + Sync + 'static>(
|
||||||
templates: State<templates::Templates>,
|
admin: State<S>,
|
||||||
registry: State<Arc<state::AdminRegistry>>,
|
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
Path((app_key, model_key, id)): Path<(String, String, String)>,
|
Path((app_key, model_key, id)): Path<(String, String, String)>,
|
||||||
) -> impl IntoResponse {
|
) -> 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 context = if let Ok(repo) = registry.get_repository(&app_key, &model_key) {
|
||||||
let repo = repo.lock().await;
|
let repo = repo.lock().await;
|
||||||
let admin_model = registry
|
let admin_model = registry
|
||||||
@ -271,13 +281,14 @@ pub async fn change_item(
|
|||||||
templates.render_html("admin/items/item_change.jinja", context)
|
templates.render_html("admin/items/item_change.jinja", context)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update_item(
|
pub async fn update_item<S: AdminState + Clone + Send + Sync + 'static>(
|
||||||
templates: State<templates::Templates>,
|
admin: State<S>,
|
||||||
registry: State<Arc<state::AdminRegistry>>,
|
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
Path((app_key, model_key, id)): Path<(String, String, String)>,
|
Path((app_key, model_key, id)): Path<(String, String, String)>,
|
||||||
Form(form): Form<Value>,
|
Form(form): Form<Value>,
|
||||||
) -> impl IntoResponse {
|
) -> 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 context = if let Ok(repo) = registry.get_repository(&app_key, &model_key) {
|
||||||
let mut repo = repo.lock().await;
|
let mut repo = repo.lock().await;
|
||||||
let admin_model = registry
|
let admin_model = registry
|
||||||
@ -307,13 +318,17 @@ pub async fn update_item(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Item Action allows running an action on one single dataset.
|
// Item Action allows running an action on one single dataset.
|
||||||
pub async fn item_action(
|
pub async fn item_action<S: AdminState + Clone + Send + Sync + 'static>(
|
||||||
|
admin: State<S>,
|
||||||
Path((app_key, model_key, model_id)): Path<(String, String, String)>,
|
Path((app_key, model_key, model_id)): Path<(String, String, String)>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
"There is your answer!".to_owned()
|
"There is your answer!".to_owned()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn debug_view(Path(data): Path<String>) -> impl IntoResponse {
|
pub async fn debug_view<S: AdminState + Clone + Send + Sync + 'static>(
|
||||||
|
admin: State<S>,
|
||||||
|
Path(data): Path<String>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
println!("debug: {}", data);
|
println!("debug: {}", data);
|
||||||
"Debug!".to_owned()
|
"Debug!".to_owned()
|
||||||
}
|
}
|
||||||
|
@ -9,17 +9,6 @@ use axum::{
|
|||||||
use rust_embed::RustEmbed;
|
use rust_embed::RustEmbed;
|
||||||
use std::marker::PhantomData;
|
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)
|
// usage: .fallback(not_found_handler)
|
||||||
pub async fn not_found_handler(
|
pub async fn not_found_handler(
|
||||||
State(templates): State<Templates>,
|
State(templates): State<Templates>,
|
||||||
@ -31,43 +20,3 @@ fn not_found(templates: Templates) -> axum::response::Result<impl IntoResponse>
|
|||||||
let body = templates.render_html("http404.html", ());
|
let body = templates.render_html("http404.html", ());
|
||||||
Ok((StatusCode::NOT_FOUND, body))
|
Ok((StatusCode::NOT_FOUND, body))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct EmbeddedFile<E, T> {
|
|
||||||
pub path: T,
|
|
||||||
embed: PhantomData<E>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E, T> EmbeddedFile<E, T> {
|
|
||||||
pub fn get(path: T) -> Self {
|
|
||||||
Self {
|
|
||||||
path,
|
|
||||||
embed: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E, T> IntoResponse for EmbeddedFile<E, T>
|
|
||||||
where
|
|
||||||
E: RustEmbed,
|
|
||||||
T: AsRef<str>,
|
|
||||||
{
|
|
||||||
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<T> = EmbeddedFile<StaticDir, T>;
|
|
||||||
|
58
src/embed.rs
Normal file
58
src/embed.rs
Normal file
@ -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<E, T> {
|
||||||
|
pub path: T,
|
||||||
|
embed: PhantomData<E>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E, T> EmbeddedFile<E, T> {
|
||||||
|
pub fn get(path: T) -> Self {
|
||||||
|
Self {
|
||||||
|
path,
|
||||||
|
embed: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E, T> IntoResponse for EmbeddedFile<E, T>
|
||||||
|
where
|
||||||
|
E: RustEmbed,
|
||||||
|
T: AsRef<str>,
|
||||||
|
{
|
||||||
|
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<T> = EmbeddedFile<StaticDir, T>;
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
use axum::{extract::State, response::IntoResponse, Form};
|
use axum::{extract::State, response::IntoResponse, Form};
|
||||||
|
|
||||||
use crate::service::templates;
|
use rear::service::templates;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
|
@ -1,4 +1 @@
|
|||||||
pub mod admin;
|
|
||||||
pub mod auth;
|
|
||||||
pub mod service;
|
|
||||||
pub mod state;
|
pub mod state;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
mod admin_examples;
|
mod admin_examples;
|
||||||
|
mod embed;
|
||||||
mod howto;
|
mod howto;
|
||||||
mod state;
|
mod state;
|
||||||
|
|
||||||
@ -16,6 +17,7 @@ use axum::{
|
|||||||
};
|
};
|
||||||
use dotenvy::dotenv;
|
use dotenvy::dotenv;
|
||||||
use log::info;
|
use log::info;
|
||||||
|
use rear::admin;
|
||||||
use rear::service::{handlers, templates};
|
use rear::service::{handlers, templates};
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
@ -66,7 +68,7 @@ async fn main() {
|
|||||||
//.merge(admin_router)
|
//.merge(admin_router)
|
||||||
.nest("/admin", admin::routes())
|
.nest("/admin", admin::routes())
|
||||||
.nest("/howto", howto::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)
|
.fallback(handlers::not_found_handler)
|
||||||
.with_state(state)
|
.with_state(state)
|
||||||
.layer(
|
.layer(
|
||||||
|
14
src/state.rs
14
src/state.rs
@ -1,12 +1,12 @@
|
|||||||
use axum::extract::FromRef;
|
use axum::extract::FromRef;
|
||||||
|
|
||||||
use rear::admin::state::SharedRegistry;
|
use rear::admin::state::{AdminState, SharedRegistry};
|
||||||
use rear::service::templates;
|
use rear::service::templates;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
pub templates: templates::Templates,
|
pub templates: templates::Templates,
|
||||||
pub admin: AdminState,
|
pub admin: SharedRegistry,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromRef<AppState> for templates::Templates {
|
impl FromRef<AppState> for templates::Templates {
|
||||||
@ -20,3 +20,13 @@ impl FromRef<AppState> for SharedRegistry {
|
|||||||
app_state.admin.clone()
|
app_state.admin.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AdminState for AppState {
|
||||||
|
fn get_templates(&self) -> &templates::Templates {
|
||||||
|
&self.templates
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_registry(&self) -> SharedRegistry {
|
||||||
|
self.admin.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user