Files
miniweb/rear/src/admin/views.rs
T

350 lines
12 KiB
Rust

use axum::extract::Path;
use axum::http::HeaderMap;
use axum::Form;
use axum::{extract::State, response::IntoResponse};
use log::info;
use serde_json::Value;
use crate::admin::domain::{AdminApp, AdminModel};
use crate::admin::state::AdminState;
use serde::{Deserialize, Serialize};
use super::domain::{RepositoryInfo, RepositoryItem, RepositoryList};
#[derive(Serialize, Deserialize)]
pub struct AdminRequest {
pub path: String,
}
#[derive(Serialize)]
pub struct AdminContext {
pub base: Option<String>,
pub language_code: Option<String>,
pub language_bidi: Option<bool>,
pub user: Option<String>, // Todo: user type
pub admin_url: String,
pub site_url: Option<String>,
pub docsroot: Option<String>,
pub messages: Vec<String>, // Todo: message type
pub title: Option<String>,
pub subtitle: Option<String>,
pub content: String,
pub request: AdminRequest,
pub available_apps: Vec<AdminApp>,
pub item_model: Option<AdminModel>,
pub item_info: Option<RepositoryInfo>,
pub item_list: RepositoryList,
pub item: Option<RepositoryItem>,
}
impl Default for AdminContext {
fn default() -> Self {
AdminContext {
base: None, // TODO: what is this used for?
language_code: Some("en-us".to_string()), // Default language code
language_bidi: Some(false), // Default language bidi
user: None, //UserType::default(), // Assuming UserType has a Default impl
admin_url: "/admin".to_owned(),
site_url: None,
docsroot: None,
messages: Vec::new(), // Empty vector for messages
title: None,
subtitle: None,
content: String::new(), // Empty string for content
available_apps: Vec::new(),
request: AdminRequest {
path: "".to_owned(),
},
item_model: None,
item_info: None,
item_list: RepositoryList::Empty,
item: None,
}
}
}
pub fn base_template(headers: &HeaderMap) -> Option<String> {
let hx_request = headers.get("HX-Request").is_some();
if hx_request {
Some("admin/base_hx.jinja".to_string())
} else {
None
}
}
pub async fn index<S: AdminState + Clone + Send + Sync + 'static>(
admin: State<S>,
headers: HeaderMap,
) -> impl IntoResponse {
let templates = admin.get_templates();
let registry = admin.get_registry();
templates.render_html(
"admin/index.html",
AdminContext {
base: base_template(&headers),
available_apps: registry.get_apps(),
..Default::default()
},
)
}
// Index Action is POST to the index site. We can anchor some general business code here.
pub async fn index_action<S: AdminState + Clone + Send + Sync + 'static>(
admin: State<S>,
) -> impl IntoResponse {
"There is your answer!".to_owned()
}
pub async fn list_app<S: AdminState + Clone + Send + Sync + 'static>(
admin: State<S>,
Path(app_key): Path<String>,
) -> 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<S: AdminState + Clone + Send + Sync + 'static>(
admin: State<S>,
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
.get_model(&app_key, &model_key)
.expect("Admin Model not found?"); // we will need a proper error route; so something that implements IntoResponse and can be substituted in the unwraps and expects.
AdminContext {
base: base_template(&headers),
available_apps: registry.get_apps(),
item_info: Some(repo.info(&admin_model).await),
item_list: repo.list(&admin_model).await,
item_model: Some(admin_model),
..Default::default()
}
} else {
AdminContext {
base: base_template(&headers),
available_apps: registry.get_apps(),
..Default::default()
}
};
templates.render_html("admin/items/item_list.jinja", context)
}
// 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<S: AdminState + Clone + Send + Sync + 'static>(
admin: State<S>,
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<S: AdminState + Clone + Send + Sync + 'static>(
admin: State<S>,
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
.get_model(&app_key, &model_key)
.expect("Admin Model not found?");
if let Some(key) = repo.key_from_string(id) {
AdminContext {
base: base_template(&headers),
available_apps: registry.get_apps(),
item_info: Some(repo.info(&admin_model).await),
item_list: repo.list(&admin_model).await,
item: repo.get(&admin_model, key.as_ref()).await,
item_model: Some(admin_model),
..Default::default()
}
} else {
AdminContext {
base: base_template(&headers),
available_apps: registry.get_apps(),
..Default::default()
}
}
} else {
AdminContext {
base: base_template(&headers),
available_apps: registry.get_apps(),
..Default::default()
}
};
templates.render_html("admin/items/item_detail.jinja", context)
}
pub async fn new_item<S: AdminState + Clone + Send + Sync + 'static>(
admin: State<S>,
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
.get_model(&app_key, &model_key)
.expect("Admin Model not found?");
AdminContext {
base: base_template(&headers),
available_apps: registry.get_apps(),
item_info: Some(repo.info(&admin_model).await),
item_list: repo.list(&admin_model).await,
item_model: Some(admin_model),
..Default::default()
}
} else {
AdminContext {
base: base_template(&headers),
available_apps: registry.get_apps(),
..Default::default()
}
};
templates.render_html("admin/items/item_create.jinja", context)
}
pub async fn create_item<S: AdminState + Clone + Send + Sync + 'static>(
admin: State<S>,
headers: HeaderMap,
Path((app_key, model_key)): Path<(String, String)>,
Form(form): Form<Value>,
) -> 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
.get_model(&app_key, &model_key)
.expect("Admin Model not found?");
// create our item.
let result = repo.create(&admin_model, form).await;
// TODO: refactor run over these views, way too much repetition.
AdminContext {
base: base_template(&headers),
available_apps: registry.get_apps(),
item_info: Some(repo.info(&admin_model).await),
item_list: repo.list(&admin_model).await,
item_model: Some(admin_model),
item: result,
..Default::default()
}
} else {
AdminContext {
base: base_template(&headers),
available_apps: registry.get_apps(),
..Default::default()
}
};
templates.render_html("admin/items/item_create.jinja", context)
}
/// Change is the GET version.
pub async fn change_item<S: AdminState + Clone + Send + Sync + 'static>(
admin: State<S>,
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
.get_model(&app_key, &model_key)
.expect("Admin Model not found?");
if let Some(key) = repo.key_from_string(id) {
AdminContext {
base: base_template(&headers),
available_apps: registry.get_apps(),
item_info: Some(repo.info(&admin_model).await),
item_list: repo.list(&admin_model).await,
item: repo.get(&admin_model, key.as_ref()).await,
item_model: Some(admin_model),
..Default::default()
}
} else {
AdminContext {
base: base_template(&headers),
available_apps: registry.get_apps(),
..Default::default()
}
}
} else {
AdminContext {
base: base_template(&headers),
available_apps: registry.get_apps(),
..Default::default()
}
};
templates.render_html("admin/items/item_change.jinja", context)
}
pub async fn update_item<S: AdminState + Clone + Send + Sync + 'static>(
admin: State<S>,
headers: HeaderMap,
Path((app_key, model_key, id)): Path<(String, String, String)>,
Form(form): Form<Value>,
) -> 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
.get_model(&app_key, &model_key)
.expect("Admin Model not found?");
if let Some(key) = repo.key_from_string(id) {
let result = repo.update(&admin_model, key.as_ref(), form).await;
AdminContext {
base: base_template(&headers),
available_apps: registry.get_apps(),
item_info: Some(repo.info(&admin_model).await),
item_list: repo.list(&admin_model).await,
item: result,
item_model: Some(admin_model),
..Default::default()
}
} else {
AdminContext {
base: base_template(&headers),
available_apps: registry.get_apps(),
..Default::default()
}
}
} else {
AdminContext {
base: base_template(&headers),
available_apps: registry.get_apps(),
..Default::default()
}
};
templates.render_html("admin/items/item_change.jinja", context)
}
// Item Action allows running an action on one single dataset.
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)>,
) -> impl IntoResponse {
"There is your answer!".to_owned()
}
pub async fn debug_view<S: AdminState + Clone + Send + Sync + 'static>(
admin: State<S>,
Path(data): Path<String>,
) -> impl IntoResponse {
println!("debug: {}", data);
"Debug!".to_owned()
}