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?
|
-> how would i create widgets? programmatically? in templates?
|
||||||
-> maybe even both approaches available?
|
-> maybe even both approaches available?
|
||||||
-> need a list of MVP widgets, and a way to extend them.
|
-> 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.
|
- validation.
|
||||||
-> client side as well?
|
-> client side as well? fomantic has it.
|
||||||
-> existing solutions?
|
-> existing solutions?
|
||||||
-> transparent way to display error messages at the appropriate field (probably adding results into responses)
|
-> transparent way to display error messages at the appropriate field (probably adding results into responses)
|
||||||
-> required
|
-> handling: "required"
|
||||||
-> invalid
|
-> handling: "invalid"
|
||||||
-> invalid(max-length)
|
-> example implementation: invalid(max-length)
|
||||||
|
|
||||||
auth:
|
auth:
|
||||||
- user model.
|
- user model.
|
||||||
|
@ -56,6 +56,7 @@ pub mod repository {
|
|||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::vec::IntoIter;
|
use std::vec::IntoIter;
|
||||||
|
|
||||||
|
#[derive(PartialEq)]
|
||||||
pub enum LookupKey {
|
pub enum LookupKey {
|
||||||
Integer(usize),
|
Integer(usize),
|
||||||
String(String),
|
String(String),
|
||||||
@ -152,6 +153,7 @@ pub mod repository {
|
|||||||
pub name: &'static str,
|
pub name: &'static str,
|
||||||
pub lookup_key: &'static str,
|
pub lookup_key: &'static str,
|
||||||
pub display_list: &'static [&'static str],
|
pub display_list: &'static [&'static str],
|
||||||
|
pub fields: &'static [&'static str],
|
||||||
}
|
}
|
||||||
|
|
||||||
// consider builder: https://docs.rs/derive_builder/latest/derive_builder/
|
// consider builder: https://docs.rs/derive_builder/latest/derive_builder/
|
||||||
@ -163,6 +165,7 @@ pub mod repository {
|
|||||||
name: String,
|
name: String,
|
||||||
lookup_key: String,
|
lookup_key: String,
|
||||||
display_list: Vec<String>,
|
display_list: Vec<String>,
|
||||||
|
fields: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RepositoryInfo {
|
impl RepositoryInfo {
|
||||||
@ -171,6 +174,7 @@ pub mod repository {
|
|||||||
name: name.to_owned(),
|
name: name.to_owned(),
|
||||||
lookup_key: lookup_key.to_owned(),
|
lookup_key: lookup_key.to_owned(),
|
||||||
display_list: vec![],
|
display_list: vec![],
|
||||||
|
fields: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,6 +196,7 @@ pub mod repository {
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|&s| s.to_string())
|
.map(|&s| s.to_string())
|
||||||
.collect(),
|
.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 info(&self, model: &AdminModel) -> RepositoryInfo;
|
||||||
fn list(&self, model: &AdminModel) -> RepositoryList;
|
fn list(&self, model: &AdminModel) -> RepositoryList;
|
||||||
fn get(&self, model: &AdminModel, id: LookupKey) -> Option<RepositoryItem>;
|
fn get(&self, model: &AdminModel, id: LookupKey) -> Option<RepositoryItem>;
|
||||||
fn create(&self, model: &AdminModel, data: Value) -> Option<RepositoryItem>;
|
fn create(&mut self, model: &AdminModel, data: Value) -> Option<RepositoryItem>;
|
||||||
fn update(&self, model: &AdminModel, id: LookupKey, data: Value) -> Option<RepositoryItem>;
|
fn update(
|
||||||
fn delete(&self, model: &AdminModel, id: LookupKey) -> Option<Value>;
|
&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::domain::*;
|
||||||
use super::state::AdminRegistry;
|
use super::state::AdminRegistry;
|
||||||
use log::warn;
|
use log::{debug, warn};
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
|
|
||||||
struct MyStaticRepository {
|
struct MyStaticRepository {
|
||||||
|
/// In Memory Storage for Example.
|
||||||
content: Vec<Value>,
|
content: Vec<Value>,
|
||||||
|
/// ID Counter for content.
|
||||||
|
next_id: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MyStaticRepository {
|
impl MyStaticRepository {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
MyStaticRepository {
|
MyStaticRepository {
|
||||||
|
next_id: 12,
|
||||||
content: vec![
|
content: vec![
|
||||||
json!({"id": 1, "name": "Strange", "age": 150, "level": "master" }),
|
json!({"id": 1, "name": "Strange", "age": 150, "level": "master" }),
|
||||||
json!({"id": 2, "name": "Adam", "age": 12, "powers": 8}),
|
json!({"id": 2, "name": "Adam", "age": 12, "powers": 8}),
|
||||||
@ -48,7 +52,6 @@ impl AdminRepository for MyStaticRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn list(&self, model: &AdminModel) -> RepositoryList {
|
fn list(&self, model: &AdminModel) -> RepositoryList {
|
||||||
// Admin needs to inject info in these.
|
|
||||||
RepositoryList::List {
|
RepositoryList::List {
|
||||||
values: self
|
values: self
|
||||||
.content
|
.content
|
||||||
@ -66,28 +69,87 @@ impl AdminRepository for MyStaticRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn info(&self, model: &AdminModel) -> RepositoryInfo {
|
fn info(&self, _model: &AdminModel) -> RepositoryInfo {
|
||||||
RepoInfo {
|
RepoInfo {
|
||||||
name: "My Static Repository",
|
name: "My Static Repository",
|
||||||
lookup_key: "id",
|
lookup_key: "id",
|
||||||
display_list: &["id", "name", "level", "age"],
|
display_list: &["id", "name", "level", "age"],
|
||||||
|
fields: &["name", "level", "age", "powers"],
|
||||||
}
|
}
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create(&self, model: &AdminModel, data: Value) -> Option<RepositoryItem> {
|
fn create(&mut self, model: &AdminModel, mut data: Value) -> Option<RepositoryItem> {
|
||||||
println!("I would now create: {}", data);
|
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
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&self, model: &AdminModel, id: LookupKey, data: Value) -> Option<RepositoryItem> {
|
fn delete(&mut self, _model: &AdminModel, id: LookupKey) -> Option<Value> {
|
||||||
println!("I would now update: {}, {}", id, data);
|
debug!("Would delete: {}", id);
|
||||||
|
|
||||||
|
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
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delete(&self, model: &AdminModel, id: LookupKey) -> Option<Value> {
|
|
||||||
println!("Would delete: {}", id);
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
use super::domain::{AdminApp, AdminModel, AdminModelConfig, AdminRepository};
|
use super::domain::{AdminApp, AdminModel, AdminModelConfig, AdminRepository};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@ -9,7 +11,7 @@ pub struct AdminRegistry {
|
|||||||
base_path: String,
|
base_path: String,
|
||||||
apps: HashMap<String, internal::AdminApp>,
|
apps: HashMap<String, internal::AdminApp>,
|
||||||
models: HashMap<String, internal::AdminModel>,
|
models: HashMap<String, internal::AdminModel>,
|
||||||
repositories: HashMap<String, Box<dyn AdminRepository>>,
|
repositories: HashMap<String, Arc<Mutex<dyn AdminRepository>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AdminRegistry {
|
impl AdminRegistry {
|
||||||
@ -105,7 +107,8 @@ impl AdminRegistry {
|
|||||||
repository: R,
|
repository: R,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let model_key = self.register_model_config(model)?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,10 +116,11 @@ impl AdminRegistry {
|
|||||||
&self,
|
&self,
|
||||||
app_key: &str,
|
app_key: &str,
|
||||||
model_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);
|
let full_model_key = format!("{}.{}", app_key, model_key);
|
||||||
if let Some(repo) = self.repositories.get(&full_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 {
|
} else {
|
||||||
return Err("Couldn't find repository".to_owned());
|
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);
|
info!("list_item_collection {} for model {}", app_key, model_key);
|
||||||
|
|
||||||
let context = if let Ok(repo) = registry.get_repository(&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
|
let admin_model = registry
|
||||||
.get_model(&app_key, &model_key)
|
.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
|
.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)>,
|
Path((app_key, model_key, id)): Path<(String, String, String)>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
let context = if let Ok(repo) = registry.get_repository(&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
|
let admin_model = registry
|
||||||
.get_model(&app_key, &model_key)
|
.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
|
.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)>,
|
Path((app_key, model_key)): Path<(String, String)>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
let context = if let Ok(repo) = registry.get_repository(&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
|
let admin_model = registry
|
||||||
.get_model(&app_key, &model_key)
|
.get_model(&app_key, &model_key)
|
||||||
.expect("Admin Model not found?");
|
.expect("Admin Model not found?");
|
||||||
@ -213,6 +216,7 @@ pub async fn create_item(
|
|||||||
Form(form): Form<Value>,
|
Form(form): Form<Value>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
let context = if let Ok(repo) = registry.get_repository(&app_key, &model_key) {
|
let context = if let Ok(repo) = registry.get_repository(&app_key, &model_key) {
|
||||||
|
let mut repo = repo.lock().await;
|
||||||
let admin_model = registry
|
let admin_model = registry
|
||||||
.get_model(&app_key, &model_key)
|
.get_model(&app_key, &model_key)
|
||||||
.expect("Admin Model not found?");
|
.expect("Admin Model not found?");
|
||||||
|
@ -1,17 +1,28 @@
|
|||||||
{% extends base|none("admin/base.jinja") %}
|
{% extends base|none("admin/base.jinja") %}
|
||||||
|
|
||||||
{% macro input(name, value="", type="text") -%}
|
{% macro input(name, value="", type="text") -%}
|
||||||
<input type="{{ type }}" name="{{ name }}" value="{{ value }}">
|
<input type="{{ type }}" name="{{ name }}" value="{{ value }}" placeholder="">
|
||||||
{%- endmacro %}
|
{%- endmacro %}
|
||||||
|
|
||||||
{% block content %}
|
{% 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">
|
<form action="{{item_model.add_url}}" method="POST" class="ui form">
|
||||||
{% set fields = item_info.display_list %}
|
{% set fields = item_info.fields %}
|
||||||
{% for field in fields %}
|
{% for field in fields %}
|
||||||
<p><label>{{field}}</label>{{ input(field) }}</p>
|
{% 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 %}
|
{% endfor %}
|
||||||
<input type="submit" name="submit" value="Create">
|
<button class="ui button" type="submit">Create</button>
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
Loading…
Reference in New Issue
Block a user