350 lines
12 KiB
Rust
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()
|
|
}
|