code: functioning example for repository registry; using tokio::sync::Mutex for read/write lock; prototype create
This commit is contained in:
parent
9feb2f4ae7
commit
0e6649de1b
11
TODOS.md
11
TODOS.md
@ -17,13 +17,16 @@ edit functionality:
|
||||
-> how would i create widgets? programmatically? in templates?
|
||||
-> maybe even both approaches available?
|
||||
-> need a list of MVP widgets, and a way to extend them.
|
||||
-> widgets should be groupable.
|
||||
-> taking a look at djangos solution!
|
||||
-> are there any smart json data => form things out there?
|
||||
- validation.
|
||||
-> client side as well?
|
||||
-> client side as well? fomantic has it.
|
||||
-> existing solutions?
|
||||
-> transparent way to display error messages at the appropriate field (probably adding results into responses)
|
||||
-> required
|
||||
-> invalid
|
||||
-> invalid(max-length)
|
||||
-> handling: "required"
|
||||
-> handling: "invalid"
|
||||
-> example implementation: invalid(max-length)
|
||||
|
||||
auth:
|
||||
- user model.
|
||||
|
@ -56,6 +56,7 @@ pub mod repository {
|
||||
use serde_json::Value;
|
||||
use std::vec::IntoIter;
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum LookupKey {
|
||||
Integer(usize),
|
||||
String(String),
|
||||
@ -152,6 +153,7 @@ pub mod repository {
|
||||
pub name: &'static str,
|
||||
pub lookup_key: &'static str,
|
||||
pub display_list: &'static [&'static str],
|
||||
pub fields: &'static [&'static str],
|
||||
}
|
||||
|
||||
// consider builder: https://docs.rs/derive_builder/latest/derive_builder/
|
||||
@ -163,6 +165,7 @@ pub mod repository {
|
||||
name: String,
|
||||
lookup_key: String,
|
||||
display_list: Vec<String>,
|
||||
fields: Vec<String>,
|
||||
}
|
||||
|
||||
impl RepositoryInfo {
|
||||
@ -171,6 +174,7 @@ pub mod repository {
|
||||
name: name.to_owned(),
|
||||
lookup_key: lookup_key.to_owned(),
|
||||
display_list: vec![],
|
||||
fields: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
@ -192,6 +196,7 @@ pub mod repository {
|
||||
.iter()
|
||||
.map(|&s| s.to_string())
|
||||
.collect(),
|
||||
fields: repo_info.fields.iter().map(|&s| s.to_string()).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -200,8 +205,13 @@ pub mod repository {
|
||||
fn info(&self, model: &AdminModel) -> RepositoryInfo;
|
||||
fn list(&self, model: &AdminModel) -> RepositoryList;
|
||||
fn get(&self, model: &AdminModel, id: LookupKey) -> Option<RepositoryItem>;
|
||||
fn create(&self, model: &AdminModel, data: Value) -> Option<RepositoryItem>;
|
||||
fn update(&self, model: &AdminModel, id: LookupKey, data: Value) -> Option<RepositoryItem>;
|
||||
fn delete(&self, model: &AdminModel, id: LookupKey) -> Option<Value>;
|
||||
fn create(&mut self, model: &AdminModel, data: Value) -> Option<RepositoryItem>;
|
||||
fn update(
|
||||
&mut self,
|
||||
model: &AdminModel,
|
||||
id: LookupKey,
|
||||
data: Value,
|
||||
) -> Option<RepositoryItem>;
|
||||
fn delete(&mut self, model: &AdminModel, id: LookupKey) -> Option<Value>;
|
||||
}
|
||||
}
|
||||
|
@ -2,16 +2,20 @@
|
||||
|
||||
use super::domain::*;
|
||||
use super::state::AdminRegistry;
|
||||
use log::warn;
|
||||
use log::{debug, warn};
|
||||
use serde_json::{json, Value};
|
||||
|
||||
struct MyStaticRepository {
|
||||
/// In Memory Storage for Example.
|
||||
content: Vec<Value>,
|
||||
/// ID Counter for content.
|
||||
next_id: usize,
|
||||
}
|
||||
|
||||
impl MyStaticRepository {
|
||||
pub fn new() -> Self {
|
||||
MyStaticRepository {
|
||||
next_id: 12,
|
||||
content: vec![
|
||||
json!({"id": 1, "name": "Strange", "age": 150, "level": "master" }),
|
||||
json!({"id": 2, "name": "Adam", "age": 12, "powers": 8}),
|
||||
@ -48,7 +52,6 @@ impl AdminRepository for MyStaticRepository {
|
||||
}
|
||||
|
||||
fn list(&self, model: &AdminModel) -> RepositoryList {
|
||||
// Admin needs to inject info in these.
|
||||
RepositoryList::List {
|
||||
values: self
|
||||
.content
|
||||
@ -66,28 +69,87 @@ impl AdminRepository for MyStaticRepository {
|
||||
}
|
||||
}
|
||||
|
||||
fn info(&self, model: &AdminModel) -> RepositoryInfo {
|
||||
fn info(&self, _model: &AdminModel) -> RepositoryInfo {
|
||||
RepoInfo {
|
||||
name: "My Static Repository",
|
||||
lookup_key: "id",
|
||||
display_list: &["id", "name", "level", "age"],
|
||||
fields: &["name", "level", "age", "powers"],
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
fn create(&self, model: &AdminModel, data: Value) -> Option<RepositoryItem> {
|
||||
println!("I would now create: {}", data);
|
||||
fn create(&mut self, model: &AdminModel, mut data: Value) -> Option<RepositoryItem> {
|
||||
debug!("Asked to create: {}", data);
|
||||
|
||||
let new_id = self.next_id;
|
||||
self.next_id += 1;
|
||||
|
||||
data["id"] = Value::Number(new_id.into());
|
||||
|
||||
debug!("I create: {}", data);
|
||||
|
||||
// Push the data into the repository
|
||||
self.content.push(data.clone());
|
||||
|
||||
// Return the newly created item
|
||||
Some(RepositoryItem {
|
||||
detail_url: Some(format!("{}/detail/{}", model.admin_url, new_id)),
|
||||
fields: data,
|
||||
})
|
||||
}
|
||||
|
||||
fn update(&mut self, model: &AdminModel, id: LookupKey, data: Value) -> Option<RepositoryItem> {
|
||||
debug!("I would now update: {}, {}", id, data);
|
||||
|
||||
// First, find the index of the item to update
|
||||
let item_index = self.content.iter().position(|item| {
|
||||
if let Some(item_id) = item.get("id") {
|
||||
match id {
|
||||
LookupKey::Integer(i) => item_id == &i,
|
||||
LookupKey::String(ref s) => item_id == s,
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
|
||||
// Then, update the item if found
|
||||
if let Some(index) = item_index {
|
||||
let item = &mut self.content[index];
|
||||
*item = data.clone();
|
||||
|
||||
if let Some(item_id) = item.get("id") {
|
||||
return Some(RepositoryItem {
|
||||
detail_url: Some(format!("{}/detail/{}", model.admin_url, item_id)),
|
||||
fields: data,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn update(&self, model: &AdminModel, id: LookupKey, data: Value) -> Option<RepositoryItem> {
|
||||
println!("I would now update: {}, {}", id, data);
|
||||
None
|
||||
}
|
||||
fn delete(&mut self, _model: &AdminModel, id: LookupKey) -> Option<Value> {
|
||||
debug!("Would delete: {}", id);
|
||||
|
||||
fn delete(&self, model: &AdminModel, id: LookupKey) -> Option<Value> {
|
||||
println!("Would delete: {}", id);
|
||||
None
|
||||
let item_index = self.content.iter().position(|item| {
|
||||
if let Some(item_id) = item.get("id") {
|
||||
match id {
|
||||
LookupKey::Integer(i) => item_id == &i,
|
||||
LookupKey::String(ref s) => item_id == s,
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(index) = item_index {
|
||||
// Remove and return the item
|
||||
Some(self.content.remove(index))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use super::domain::{AdminApp, AdminModel, AdminModelConfig, AdminRepository};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
@ -9,7 +11,7 @@ pub struct AdminRegistry {
|
||||
base_path: String,
|
||||
apps: HashMap<String, internal::AdminApp>,
|
||||
models: HashMap<String, internal::AdminModel>,
|
||||
repositories: HashMap<String, Box<dyn AdminRepository>>,
|
||||
repositories: HashMap<String, Arc<Mutex<dyn AdminRepository>>>,
|
||||
}
|
||||
|
||||
impl AdminRegistry {
|
||||
@ -105,7 +107,8 @@ impl AdminRegistry {
|
||||
repository: R,
|
||||
) -> Result<(), String> {
|
||||
let model_key = self.register_model_config(model)?;
|
||||
self.repositories.insert(model_key, Box::new(repository));
|
||||
self.repositories
|
||||
.insert(model_key, Arc::new(Mutex::new(repository)));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -113,10 +116,11 @@ impl AdminRegistry {
|
||||
&self,
|
||||
app_key: &str,
|
||||
model_key: &str,
|
||||
) -> Result<&Box<dyn AdminRepository>, String> {
|
||||
) -> Result<Arc<Mutex<dyn AdminRepository>>, String> {
|
||||
let full_model_key = format!("{}.{}", app_key, model_key);
|
||||
if let Some(repo) = self.repositories.get(&full_model_key) {
|
||||
return Ok(repo);
|
||||
// Clone the Arc to return a reference to the repository
|
||||
return Ok(Arc::clone(repo));
|
||||
} else {
|
||||
return Err("Couldn't find repository".to_owned());
|
||||
}
|
||||
|
@ -114,6 +114,7 @@ pub async fn list_item_collection(
|
||||
info!("list_item_collection {} for model {}", app_key, model_key);
|
||||
|
||||
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?"); // TODO: do we really need to differentiate between AdminModel and Repository, if both need to co-exist
|
||||
@ -152,6 +153,7 @@ pub async fn item_details(
|
||||
Path((app_key, model_key, id)): Path<(String, String, String)>,
|
||||
) -> impl IntoResponse {
|
||||
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?"); // TODO: do we really need to differentiate between AdminModel and Repository, if both need to co-exist
|
||||
@ -184,6 +186,7 @@ pub async fn new_item(
|
||||
Path((app_key, model_key)): Path<(String, String)>,
|
||||
) -> impl IntoResponse {
|
||||
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?");
|
||||
@ -213,6 +216,7 @@ pub async fn create_item(
|
||||
Form(form): Form<Value>,
|
||||
) -> impl IntoResponse {
|
||||
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?");
|
||||
|
@ -1,17 +1,28 @@
|
||||
{% extends base|none("admin/base.jinja") %}
|
||||
|
||||
{% macro input(name, value="", type="text") -%}
|
||||
<input type="{{ type }}" name="{{ name }}" value="{{ value }}">
|
||||
<input type="{{ type }}" name="{{ name }}" value="{{ value }}" placeholder="">
|
||||
{%- endmacro %}
|
||||
|
||||
{% block content %}
|
||||
Create {{item_model.name}} in {{item_info.name}}
|
||||
<div class="ui container">
|
||||
<h1>Create {{item_model.name}} in {{item_info.name}}</h1>
|
||||
|
||||
<form action="{{item_model.add_url}}" method="POST">
|
||||
{% set fields = item_info.display_list %}
|
||||
{% for field in fields %}
|
||||
<p><label>{{field}}</label>{{ input(field) }}</p>
|
||||
{% endfor %}
|
||||
<input type="submit" name="submit" value="Create">
|
||||
</form>
|
||||
<form action="{{item_model.add_url}}" method="POST" class="ui form">
|
||||
{% set fields = item_info.fields %}
|
||||
{% for field in fields %}
|
||||
{% if item %}
|
||||
{% set field_value = item.fields[field]|none("") %}
|
||||
{% else %}
|
||||
{% set field_value = "" %}
|
||||
{% endif %}
|
||||
|
||||
<div class="inline field">
|
||||
<label>{{field|capitalize}}</label>
|
||||
{{ input(field, field_value) }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
<button class="ui button" type="submit">Create</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock content %}
|
Loading…
Reference in New Issue
Block a user