code: upgrade to axum 0.7, working on repo info system
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
pub use config::AdminModelConfig;
|
||||
pub use dto::AdminApp;
|
||||
pub use dto::AdminModel;
|
||||
pub use repository::{AdminRepository, RepositoryList};
|
||||
pub use repository::{AdminRepository, RepositoryInfo, RepositoryList};
|
||||
|
||||
mod auth {
|
||||
|
||||
@@ -95,8 +95,33 @@ pub mod repository {
|
||||
}
|
||||
}
|
||||
|
||||
// each repository has to implement a repo info.
|
||||
#[derive(Serialize)]
|
||||
pub struct RepositoryInfo {
|
||||
name: String,
|
||||
lookup_key: String,
|
||||
display_list: Vec<String>,
|
||||
}
|
||||
|
||||
impl RepositoryInfo {
|
||||
pub fn new(name: &str, lookup_key: &str) -> Self {
|
||||
RepositoryInfo {
|
||||
name: name.to_owned(),
|
||||
lookup_key: lookup_key.to_owned(),
|
||||
display_list: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn display_list(mut self, display_list: &[&str]) -> RepositoryInfo {
|
||||
self.display_list = display_list.iter().map(|&e| e.to_string()).collect();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub trait AdminRepository: Send + Sync {
|
||||
fn get_item(&self, id: usize) -> Option<Value>;
|
||||
|
||||
fn get_repo_info(&self) -> RepositoryInfo;
|
||||
fn get_list(&self) -> RepositoryList;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +1,49 @@
|
||||
// implementation of static repository
|
||||
|
||||
use super::domain::{AdminModelConfig, AdminRepository, RepositoryList};
|
||||
use super::domain::{AdminModelConfig, AdminRepository, RepositoryInfo, RepositoryList};
|
||||
use super::state::AdminRegistry;
|
||||
use serde_json::{json, Value};
|
||||
|
||||
struct MyStaticRepository {}
|
||||
struct MyStaticRepository {
|
||||
content: Vec<Value>,
|
||||
}
|
||||
|
||||
impl AdminRepository for MyStaticRepository {
|
||||
fn get_item(&self, id: usize) -> Option<Value> {
|
||||
Some(json!({
|
||||
"name": "Adam",
|
||||
"age": id,
|
||||
}))
|
||||
}
|
||||
|
||||
fn get_list(&self) -> RepositoryList {
|
||||
RepositoryList::List {
|
||||
values: vec![
|
||||
json!({"name": "Strange", "age": 150 }),
|
||||
json!({"name": "Adam", "age": 12}),
|
||||
impl MyStaticRepository {
|
||||
pub fn new() -> Self {
|
||||
MyStaticRepository {
|
||||
content: vec![
|
||||
json!({"id": 1, "name": "Strange", "age": 150, "level": "master" }),
|
||||
json!({"id": 2, "name": "Adam", "age": 12, "powers": 8}),
|
||||
json!({"id": 3, "name": "Tony", "age": 42, "powers": 0}),
|
||||
json!({"id": 4, "name": "Rex", "age": 72}),
|
||||
json!({"id": 5, "name": "Justin", "age": 46}),
|
||||
json!({"id": 6, "name": "Reacher", "age": 39, "level": "adept", "powers": 0}),
|
||||
json!({"id": 7, "name": "Arnold", "age": 64}),
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AdminRepository for MyStaticRepository {
|
||||
fn get_item(&self, id: usize) -> Option<Value> {
|
||||
self.content.get(id).cloned()
|
||||
}
|
||||
|
||||
fn get_list(&self) -> RepositoryList {
|
||||
RepositoryList::List {
|
||||
values: self.content.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_repo_info(&self) -> RepositoryInfo {
|
||||
RepositoryInfo::new("My Static Repository", "id")
|
||||
.display_list(&["id", "name", "age", "level"])
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_example(registry: &mut AdminRegistry) {
|
||||
let app_key = registry.register_app("Example App");
|
||||
let repo = MyStaticRepository {};
|
||||
let repo = MyStaticRepository::new();
|
||||
let model_config = AdminModelConfig {
|
||||
app_key: app_key,
|
||||
name: "ExampleModel".to_owned(),
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::service::templates;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
|
||||
use super::domain::RepositoryList;
|
||||
use super::domain::{RepositoryInfo, RepositoryList};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Question {
|
||||
@@ -43,6 +43,7 @@ pub struct AdminContext {
|
||||
|
||||
pub request: AdminRequest,
|
||||
pub available_apps: Vec<AdminApp>,
|
||||
pub item_info: Option<RepositoryInfo>,
|
||||
pub item_list: RepositoryList,
|
||||
pub item: Option<Value>,
|
||||
}
|
||||
@@ -64,6 +65,7 @@ impl Default for AdminContext {
|
||||
request: AdminRequest {
|
||||
path: "".to_owned(),
|
||||
},
|
||||
item_info: None,
|
||||
item_list: RepositoryList::Empty,
|
||||
item: None,
|
||||
}
|
||||
@@ -106,6 +108,8 @@ pub async fn list_item_collection(
|
||||
// we should consider using Vec<Value> instead in get_list.
|
||||
AdminContext {
|
||||
available_apps: registry.get_apps(),
|
||||
content: model_key.to_owned(),
|
||||
item_info: Some(repo.get_repo_info()),
|
||||
item_list: repo.get_list(),
|
||||
..Default::default()
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ use log::info;
|
||||
use std::env;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
async fn home(templates: State<templates::Templates>) -> impl IntoResponse {
|
||||
templates.render_html("index.html", ())
|
||||
@@ -70,11 +71,13 @@ async fn main() {
|
||||
};
|
||||
info!("listening on {}", listen_addr);
|
||||
info!("admin on: http://{}/admin", server_addr);
|
||||
axum::Server::bind(&listen_addr)
|
||||
.serve(app.into_make_service())
|
||||
let listener = TcpListener::bind(&listen_addr)
|
||||
.await
|
||||
.expect("Could not bind TCP Listener.");
|
||||
axum::serve(listener, app.into_make_service())
|
||||
.with_graceful_shutdown(shutdown_signal())
|
||||
.await
|
||||
.unwrap();
|
||||
.expect("Could not start serving Axum");
|
||||
}
|
||||
|
||||
async fn shutdown_signal() {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* Default handlers */
|
||||
use crate::service::templates::Templates;
|
||||
use axum::{
|
||||
body::{boxed, Full},
|
||||
body::Body,
|
||||
extract::State,
|
||||
http::{header, StatusCode, Uri},
|
||||
response::{IntoResponse, Response},
|
||||
@@ -55,7 +55,7 @@ where
|
||||
let path: &str = self.path.as_ref();
|
||||
match E::get(path) {
|
||||
Some(content) => {
|
||||
let body = boxed(Full::from(content.data));
|
||||
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())
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
use crate::service::error::Error;
|
||||
use axum::response::Html;
|
||||
use minijinja::value::ValueKind;
|
||||
use minijinja::{path_loader, Environment, Value};
|
||||
use minijinja_autoreload::AutoReloader;
|
||||
use pulldown_cmark::Event;
|
||||
use serde::Deserializer;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -19,6 +21,7 @@ impl Templates {
|
||||
environment.add_filter("none", none);
|
||||
environment.add_filter("markdown", markdown);
|
||||
environment.add_filter("yesno", filter_yesno);
|
||||
environment.add_filter("table_keys", collect_unique_keys);
|
||||
environment.add_function("static", tpl_static);
|
||||
environment.add_function("url", tpl_url);
|
||||
environment.add_function("csrf_token", tpl_to_be_implemented);
|
||||
@@ -93,3 +96,71 @@ pub fn none(value: Value, other: Option<Value>) -> Value {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
fn tpl_table(values: Vec<Value>) -> Value {
|
||||
Value::from_safe_string(format!(
|
||||
"Output: {}",
|
||||
values.first().expect("No values found.").kind()
|
||||
))
|
||||
}
|
||||
|
||||
mod helper {
|
||||
use std::collections::HashSet;
|
||||
|
||||
pub struct OrderedSet<T> {
|
||||
set: HashSet<T>,
|
||||
vec: Vec<T>,
|
||||
}
|
||||
|
||||
impl<T: Eq + std::hash::Hash + Clone> OrderedSet<T> {
|
||||
pub fn new() -> Self {
|
||||
OrderedSet {
|
||||
set: HashSet::new(),
|
||||
vec: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, value: T) {
|
||||
if self.set.insert(value.clone()) {
|
||||
self.vec.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
|
||||
for item in iter {
|
||||
self.insert(item);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> std::slice::Iter<T> {
|
||||
self.vec.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoIterator for OrderedSet<T> {
|
||||
type Item = T;
|
||||
type IntoIter = std::vec::IntoIter<T>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.vec.into_iter()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_unique_keys(values: Value) -> Value {
|
||||
use helper::OrderedSet;
|
||||
use serde::Deserialize;
|
||||
let mut unique_keys: OrderedSet<String> = OrderedSet::new();
|
||||
|
||||
if let Ok(vec) = Vec::<serde_json::Value>::deserialize(values) {
|
||||
for value in vec.iter() {
|
||||
if let Some(dict) = value.as_object() {
|
||||
let keys_vec: Vec<String> = dict.keys().map(|s| s.to_owned()).collect();
|
||||
unique_keys.extend(keys_vec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let keys_list = unique_keys.into_iter().collect::<Vec<_>>();
|
||||
Value::from(keys_list)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user