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, pub language_code: Option, pub language_bidi: Option, pub user: Option, // Todo: user type pub admin_url: String, pub site_url: Option, pub docsroot: Option, pub messages: Vec, // Todo: message type pub title: Option, pub subtitle: Option, pub content: String, pub request: AdminRequest, pub available_apps: Vec, pub item_model: Option, pub item_info: Option, pub item_list: RepositoryList, pub item: Option, } 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 { 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( admin: State, 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( admin: State, ) -> impl IntoResponse { "There is your answer!".to_owned() } 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( 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 .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( 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( 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 .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( 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 .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( 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 .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( 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 .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( 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 .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( 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( admin: State, Path(data): Path, ) -> impl IntoResponse { println!("debug: {}", data); "Debug!".to_owned() }