refactor: result type, removing rear.admin
This commit is contained in:
parent
966291dbd9
commit
843e432ec4
17
Cargo.lock
generated
17
Cargo.lock
generated
@ -2005,6 +2005,12 @@ dependencies = [
|
|||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-conv"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-integer"
|
name = "num-integer"
|
||||||
version = "0.1.45"
|
version = "0.1.45"
|
||||||
@ -2464,6 +2470,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"slug",
|
"slug",
|
||||||
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
@ -3459,12 +3466,13 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time"
|
name = "time"
|
||||||
version = "0.3.31"
|
version = "0.3.36"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e"
|
checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"deranged",
|
"deranged",
|
||||||
"itoa",
|
"itoa",
|
||||||
|
"num-conv",
|
||||||
"powerfmt",
|
"powerfmt",
|
||||||
"serde",
|
"serde",
|
||||||
"time-core",
|
"time-core",
|
||||||
@ -3479,10 +3487,11 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time-macros"
|
name = "time-macros"
|
||||||
version = "0.2.16"
|
version = "0.2.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f"
|
checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"num-conv",
|
||||||
"time-core",
|
"time-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -26,3 +26,4 @@ serde_json = "1.0.108"
|
|||||||
slug = "0.1.5"
|
slug = "0.1.5"
|
||||||
async-trait = "0.1.77"
|
async-trait = "0.1.77"
|
||||||
tracing = "0.1.40"
|
tracing = "0.1.40"
|
||||||
|
thiserror = "1.0.61"
|
||||||
|
@ -1,486 +0,0 @@
|
|||||||
pub use config::AdminModelConfig;
|
|
||||||
pub use dto::AdminApp;
|
|
||||||
pub use dto::AdminModel;
|
|
||||||
pub use repository::{
|
|
||||||
AdminRepository, DynAdminRepository, RepoInfo, RepositoryContext, RepositoryInfo,
|
|
||||||
RepositoryItem, RepositoryList, Widget,
|
|
||||||
};
|
|
||||||
|
|
||||||
mod auth {
|
|
||||||
|
|
||||||
struct AdminUser {}
|
|
||||||
|
|
||||||
struct AdminRole {}
|
|
||||||
|
|
||||||
struct AdminGroup {}
|
|
||||||
|
|
||||||
struct AdminActionLog {}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod config {
|
|
||||||
// user uses this configuration object to register another model.
|
|
||||||
pub struct AdminModelConfig {
|
|
||||||
pub name: String,
|
|
||||||
pub app_key: String,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod dto {
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
|
||||||
pub struct AdminModel {
|
|
||||||
pub key: String,
|
|
||||||
pub name: String,
|
|
||||||
|
|
||||||
pub admin_url: String,
|
|
||||||
pub view_only: bool,
|
|
||||||
|
|
||||||
pub add_url: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
|
||||||
pub struct AdminApp {
|
|
||||||
pub key: String,
|
|
||||||
pub name: String,
|
|
||||||
|
|
||||||
pub app_url: String,
|
|
||||||
pub models: Vec<AdminModel>,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod repository {
|
|
||||||
use super::dto::AdminModel;
|
|
||||||
use async_trait::async_trait;
|
|
||||||
use serde::{Serialize, Serializer};
|
|
||||||
use serde_json::Value;
|
|
||||||
use std::any::Any;
|
|
||||||
use std::fmt::Debug;
|
|
||||||
use std::vec::IntoIter;
|
|
||||||
|
|
||||||
pub type RepositoryContext = AdminModel;
|
|
||||||
|
|
||||||
impl RepositoryContext {
|
|
||||||
pub fn get_default_detail_url(&self, key: &str) -> Option<String> {
|
|
||||||
Some(format!("{}/detail/{}", self.admin_url, key))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_default_change_url(&self, key: &str) -> Option<String> {
|
|
||||||
Some(format!("{}/change/{}", self.admin_url, key))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build_item(&self, key: &str, fields: Value) -> RepositoryItem {
|
|
||||||
RepositoryItem {
|
|
||||||
detail_url: self.get_default_detail_url(key),
|
|
||||||
change_url: self.get_default_change_url(key),
|
|
||||||
fields: fields,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This is a static configuration object.
|
|
||||||
/// It might be changed in the future to have a dynamic counterpart.
|
|
||||||
///
|
|
||||||
/// ## Example:
|
|
||||||
/// Creating a simple required and readonly text input field with a label:
|
|
||||||
///
|
|
||||||
/// ```ignore
|
|
||||||
/// let my_field = Field::widget("/admin/widgets/input_text.jinja")
|
|
||||||
/// .labelled("Username")
|
|
||||||
/// .required()
|
|
||||||
/// .readonly();
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Serialize, Clone, Copy)]
|
|
||||||
pub struct Widget {
|
|
||||||
pub widget: &'static str,
|
|
||||||
pub label: Option<&'static str>,
|
|
||||||
#[serde(rename = "type")]
|
|
||||||
pub field_type: &'static str,
|
|
||||||
pub required: bool,
|
|
||||||
pub readonly: bool,
|
|
||||||
pub options: &'static [(&'static str, &'static str)],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Widget {
|
|
||||||
pub fn widget(widget: &'static str) -> Self {
|
|
||||||
Widget {
|
|
||||||
widget: widget,
|
|
||||||
label: None,
|
|
||||||
field_type: "text",
|
|
||||||
required: false,
|
|
||||||
readonly: false,
|
|
||||||
options: &[],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn default() -> Self {
|
|
||||||
Self::widget("/admin/widgets/input_text.jinja")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn textarea() -> Self {
|
|
||||||
Self::widget("/admin/widgets/input_textarea.jinja")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn checkbox() -> Self {
|
|
||||||
Self::widget("/admin/widgets/checkbox_toggle.jinja")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn required(mut self) -> Self {
|
|
||||||
self.required = true;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn readonly(mut self) -> Self {
|
|
||||||
self.readonly = true;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn labeled(mut self, label: &'static str) -> Self {
|
|
||||||
self.label = Some(label);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_password(mut self) -> Self {
|
|
||||||
self.field_type = "password";
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_hidden(mut self) -> Self {
|
|
||||||
self.field_type = "hidden";
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn options(mut self, options: &'static [(&'static str, &'static str)]) -> Self {
|
|
||||||
self.options = options;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
|
||||||
pub struct Field {
|
|
||||||
widget: String,
|
|
||||||
label: Option<String>,
|
|
||||||
#[serde(rename = "type")]
|
|
||||||
field_type: String,
|
|
||||||
readonly: bool,
|
|
||||||
required: bool,
|
|
||||||
options: Value,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Widget> for Field {
|
|
||||||
fn from(value: Widget) -> Self {
|
|
||||||
Field {
|
|
||||||
widget: value.widget.to_string(),
|
|
||||||
label: value.label.map(|s| s.to_string()),
|
|
||||||
field_type: value.field_type.to_string(),
|
|
||||||
readonly: value.readonly,
|
|
||||||
required: value.required,
|
|
||||||
options: value
|
|
||||||
.options
|
|
||||||
.iter()
|
|
||||||
.map(|(key, val)| (key.to_string(), serde_json::json!(val)))
|
|
||||||
.collect::<serde_json::Map<String, Value>>()
|
|
||||||
.into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
pub struct RepositoryItem {
|
|
||||||
pub fields: Value,
|
|
||||||
pub detail_url: Option<String>,
|
|
||||||
pub change_url: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum RepositoryList {
|
|
||||||
Empty,
|
|
||||||
List {
|
|
||||||
values: Vec<RepositoryItem>,
|
|
||||||
},
|
|
||||||
Page {
|
|
||||||
values: Vec<RepositoryItem>,
|
|
||||||
offset: usize,
|
|
||||||
total: usize,
|
|
||||||
},
|
|
||||||
Stream {
|
|
||||||
values: Vec<RepositoryItem>,
|
|
||||||
next_index: Option<String>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoIterator for RepositoryList {
|
|
||||||
type Item = RepositoryItem;
|
|
||||||
type IntoIter = IntoIter<Self::Item>;
|
|
||||||
|
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
|
||||||
match self {
|
|
||||||
RepositoryList::Empty => vec![].into_iter(),
|
|
||||||
RepositoryList::List { values } => values.into_iter(),
|
|
||||||
RepositoryList::Page { values, .. } => values.into_iter(),
|
|
||||||
RepositoryList::Stream { values, .. } => values.into_iter(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Serialize for RepositoryList {
|
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: Serializer,
|
|
||||||
{
|
|
||||||
match self {
|
|
||||||
RepositoryList::Empty => serializer.serialize_unit(),
|
|
||||||
RepositoryList::List { values }
|
|
||||||
| RepositoryList::Page { values, .. }
|
|
||||||
| RepositoryList::Stream { values, .. } => values.serialize(serializer),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Static initializer for RepositoryInfo.
|
|
||||||
pub struct RepoInfo {
|
|
||||||
pub name: &'static str,
|
|
||||||
pub lookup_key: &'static str,
|
|
||||||
pub display_list: &'static [&'static str],
|
|
||||||
pub fields: &'static [&'static str],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RepoInfo {
|
|
||||||
pub fn build(self) -> RepositoryInfo {
|
|
||||||
self.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
pub struct RepositoryInfo {
|
|
||||||
name: String,
|
|
||||||
lookup_key: String,
|
|
||||||
display_list: Vec<String>,
|
|
||||||
fields: Vec<(String, Field)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
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![],
|
|
||||||
fields: vec![],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// self mutating builder pattern
|
|
||||||
pub fn display_list(mut self, display_list: &[&str]) -> Self {
|
|
||||||
self.display_list = display_list.iter().map(|&e| e.to_string()).collect();
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_widget<T: Into<Field>>(mut self, name: &str, item: T) -> Self {
|
|
||||||
let field = item.into(); // Convert the input into a Field
|
|
||||||
|
|
||||||
// Find the index of the existing entry with the same name, if it exists
|
|
||||||
let pos = self
|
|
||||||
.fields
|
|
||||||
.iter()
|
|
||||||
.position(|(existing_name, _)| existing_name == name);
|
|
||||||
|
|
||||||
match pos {
|
|
||||||
Some(index) => {
|
|
||||||
self.fields[index].1 = field;
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
self.fields.push((name.to_owned(), field));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<RepoInfo> for RepositoryInfo {
|
|
||||||
fn from(repo_info: RepoInfo) -> Self {
|
|
||||||
RepositoryInfo {
|
|
||||||
name: repo_info.name.to_string(),
|
|
||||||
lookup_key: repo_info.lookup_key.to_string(),
|
|
||||||
display_list: repo_info
|
|
||||||
.display_list
|
|
||||||
.iter()
|
|
||||||
.map(|&s| s.to_string())
|
|
||||||
.collect(),
|
|
||||||
fields: repo_info
|
|
||||||
.fields
|
|
||||||
.iter()
|
|
||||||
.map(|x| (x.to_string(), Field::from(Widget::default())))
|
|
||||||
.collect(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait PrimaryKeyType: Any + Debug + Send + Sync {
|
|
||||||
fn as_any(&self) -> &dyn Any;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PrimaryKeyType for i64 {
|
|
||||||
fn as_any(&self) -> &dyn Any {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PrimaryKeyType for String {
|
|
||||||
fn as_any(&self) -> &dyn Any {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
pub trait AdminRepository: Send + Sync {
|
|
||||||
type Key: PrimaryKeyType;
|
|
||||||
|
|
||||||
fn key_from_string(&self, s: String) -> Option<Self::Key>;
|
|
||||||
|
|
||||||
async fn info(&self, context: &RepositoryContext) -> RepositoryInfo;
|
|
||||||
async fn list(&self, context: &RepositoryContext) -> RepositoryList;
|
|
||||||
async fn get(&self, context: &RepositoryContext, id: &Self::Key) -> Option<RepositoryItem>;
|
|
||||||
async fn create(
|
|
||||||
&mut self,
|
|
||||||
context: &RepositoryContext,
|
|
||||||
data: Value,
|
|
||||||
) -> Option<RepositoryItem>;
|
|
||||||
async fn update(
|
|
||||||
&mut self,
|
|
||||||
context: &RepositoryContext,
|
|
||||||
id: &Self::Key,
|
|
||||||
data: Value,
|
|
||||||
) -> Option<RepositoryItem>;
|
|
||||||
async fn replace(
|
|
||||||
&mut self,
|
|
||||||
context: &RepositoryContext,
|
|
||||||
id: &Self::Key,
|
|
||||||
data: Value,
|
|
||||||
) -> Option<RepositoryItem>;
|
|
||||||
async fn delete(&mut self, context: &RepositoryContext, id: &Self::Key) -> Option<Value>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
pub trait DynAdminRepository: Send + Sync {
|
|
||||||
fn key_from_string(&self, s: String) -> Option<Box<dyn PrimaryKeyType>>;
|
|
||||||
async fn info(&self, context: &RepositoryContext) -> RepositoryInfo;
|
|
||||||
async fn list(&self, context: &RepositoryContext) -> RepositoryList;
|
|
||||||
async fn get(
|
|
||||||
&self,
|
|
||||||
context: &RepositoryContext,
|
|
||||||
id: &dyn PrimaryKeyType,
|
|
||||||
) -> Option<RepositoryItem>;
|
|
||||||
async fn create(
|
|
||||||
&mut self,
|
|
||||||
context: &RepositoryContext,
|
|
||||||
data: Value,
|
|
||||||
) -> Option<RepositoryItem>;
|
|
||||||
async fn update(
|
|
||||||
&mut self,
|
|
||||||
context: &RepositoryContext,
|
|
||||||
id: &dyn PrimaryKeyType,
|
|
||||||
data: Value,
|
|
||||||
) -> Option<RepositoryItem>;
|
|
||||||
async fn replace(
|
|
||||||
&mut self,
|
|
||||||
context: &RepositoryContext,
|
|
||||||
id: &dyn PrimaryKeyType,
|
|
||||||
data: Value,
|
|
||||||
) -> Option<RepositoryItem>;
|
|
||||||
async fn delete(
|
|
||||||
&mut self,
|
|
||||||
context: &RepositoryContext,
|
|
||||||
id: &dyn PrimaryKeyType,
|
|
||||||
) -> Option<Value>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct AdminRepositoryWrapper<T: AdminRepository> {
|
|
||||||
inner: T,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: AdminRepository> AdminRepositoryWrapper<T> {
|
|
||||||
pub fn new(inner: T) -> Self {
|
|
||||||
Self { inner }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn key_from_string(&self, s: String) -> Option<<T as AdminRepository>::Key> {
|
|
||||||
self.inner.key_from_string(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl<T: AdminRepository> DynAdminRepository for AdminRepositoryWrapper<T> {
|
|
||||||
fn key_from_string(&self, s: String) -> Option<Box<dyn PrimaryKeyType>> {
|
|
||||||
if let Some(key) = self.inner.key_from_string(s) {
|
|
||||||
Some(Box::new(key))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn info(&self, context: &RepositoryContext) -> RepositoryInfo {
|
|
||||||
self.inner.info(context).await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn list(&self, context: &RepositoryContext) -> RepositoryList {
|
|
||||||
self.inner.list(context).await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get(
|
|
||||||
&self,
|
|
||||||
context: &RepositoryContext,
|
|
||||||
id: &dyn PrimaryKeyType,
|
|
||||||
) -> Option<RepositoryItem> {
|
|
||||||
if let Some(key) = id.as_any().downcast_ref::<T::Key>() {
|
|
||||||
self.inner.get(context, key).await
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn create(
|
|
||||||
&mut self,
|
|
||||||
context: &RepositoryContext,
|
|
||||||
data: Value,
|
|
||||||
) -> Option<RepositoryItem> {
|
|
||||||
self.inner.create(context, data).await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn update(
|
|
||||||
&mut self,
|
|
||||||
context: &RepositoryContext,
|
|
||||||
id: &dyn PrimaryKeyType,
|
|
||||||
data: Value,
|
|
||||||
) -> Option<RepositoryItem> {
|
|
||||||
if let Some(key) = id.as_any().downcast_ref::<T::Key>() {
|
|
||||||
self.inner.update(context, key, data).await
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn replace(
|
|
||||||
&mut self,
|
|
||||||
context: &RepositoryContext,
|
|
||||||
id: &dyn PrimaryKeyType,
|
|
||||||
data: Value,
|
|
||||||
) -> Option<RepositoryItem> {
|
|
||||||
if let Some(key) = id.as_any().downcast_ref::<T::Key>() {
|
|
||||||
self.inner.replace(context, key, data).await
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn delete(
|
|
||||||
&mut self,
|
|
||||||
context: &RepositoryContext,
|
|
||||||
id: &dyn PrimaryKeyType,
|
|
||||||
) -> Option<Value> {
|
|
||||||
if let Some(key) = id.as_any().downcast_ref::<T::Key>() {
|
|
||||||
self.inner.delete(context, key).await
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
use axum::{routing::get, Router};
|
|
||||||
|
|
||||||
pub mod domain;
|
|
||||||
pub mod state;
|
|
||||||
pub mod views;
|
|
||||||
|
|
||||||
pub fn routes<S: state::AdminState + Clone + Send + Sync + 'static>() -> Router<S> {
|
|
||||||
Router::new()
|
|
||||||
.route("/", get(views::index::<S>).post(views::index_action::<S>))
|
|
||||||
.route("/app/:app", get(views::list_app::<S>))
|
|
||||||
.route(
|
|
||||||
"/app/:app/model/:model",
|
|
||||||
get(views::list_item_collection::<S>),
|
|
||||||
)
|
|
||||||
.route(
|
|
||||||
"/app/:app/model/:model/add",
|
|
||||||
get(views::new_item::<S>).post(views::create_item::<S>),
|
|
||||||
)
|
|
||||||
.route(
|
|
||||||
"/app/:app/model/:model/change/:id",
|
|
||||||
get(views::change_item::<S>).patch(views::update_item::<S>),
|
|
||||||
)
|
|
||||||
.route(
|
|
||||||
"/app/:app/model/:model/detail/:id",
|
|
||||||
get(views::view_item_details::<S>),
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,174 +0,0 @@
|
|||||||
use tokio::sync::Mutex;
|
|
||||||
|
|
||||||
use super::domain::repository::AdminRepositoryWrapper;
|
|
||||||
use super::domain::{AdminApp, AdminModel, AdminModelConfig, AdminRepository, DynAdminRepository};
|
|
||||||
use crate::service::templates::Templates;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
pub trait AdminState {
|
|
||||||
fn get_templates(&self) -> &Templates;
|
|
||||||
fn get_registry(&self) -> SharedAdminRegistry;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type SharedAdminRegistry = Arc<AdminRegistry>;
|
|
||||||
|
|
||||||
// main registry.
|
|
||||||
pub struct AdminRegistry {
|
|
||||||
base_path: String,
|
|
||||||
apps: HashMap<String, internal::AdminApp>,
|
|
||||||
models: HashMap<String, internal::AdminModel>,
|
|
||||||
repositories: HashMap<String, Arc<Mutex<dyn DynAdminRepository>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AdminRegistry {
|
|
||||||
pub fn new(base_path: &str) -> Self {
|
|
||||||
AdminRegistry {
|
|
||||||
base_path: base_path.to_owned(),
|
|
||||||
apps: HashMap::new(),
|
|
||||||
models: HashMap::new(),
|
|
||||||
repositories: HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_apps(&self) -> Vec<AdminApp> {
|
|
||||||
self.apps
|
|
||||||
.iter()
|
|
||||||
.map(|(key, node)| self.get_app(key, node))
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_app(&self, key: &str, node: &internal::AdminApp) -> AdminApp {
|
|
||||||
let my_models = self.get_models(key);
|
|
||||||
AdminApp {
|
|
||||||
name: key.to_owned(),
|
|
||||||
key: node.name.to_owned(),
|
|
||||||
app_url: format!("/{}/app/{}", self.base_path, key.to_owned()),
|
|
||||||
models: my_models,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn register_app(&mut self, name: &str) -> String {
|
|
||||||
let key = self.get_key(name);
|
|
||||||
self.apps.insert(
|
|
||||||
key.to_owned(),
|
|
||||||
internal::AdminApp {
|
|
||||||
key: key.to_owned(),
|
|
||||||
name: name.to_owned(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
key
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_key(&self, name: &str) -> String {
|
|
||||||
slug::slugify(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn model_from_internal(&self, internal_model: &internal::AdminModel) -> AdminModel {
|
|
||||||
let admin_url = format!(
|
|
||||||
"/{}/app/{}/model/{}",
|
|
||||||
self.base_path, internal_model.app_key, internal_model.model_key
|
|
||||||
);
|
|
||||||
AdminModel {
|
|
||||||
key: internal_model.model_key.clone(),
|
|
||||||
name: internal_model.name.clone(),
|
|
||||||
view_only: false,
|
|
||||||
add_url: Some(format!("{}/add", admin_url)),
|
|
||||||
admin_url: admin_url,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_models(&self, app_key: &str) -> Vec<AdminModel> {
|
|
||||||
self.models
|
|
||||||
.iter()
|
|
||||||
.filter(|(key, _)| key.starts_with(&format!("{}.", app_key)))
|
|
||||||
.map(|(_, model)| self.model_from_internal(model))
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_model(&self, app_key: &str, model_key: &str) -> Option<AdminModel> {
|
|
||||||
let full_model_key = format!("{}.{}", app_key, model_key);
|
|
||||||
let internal_model = self.models.get(&full_model_key)?;
|
|
||||||
|
|
||||||
// unfinished: we need to think about model_key vs. model_id vs. entry_id, as "name" is ambiguous.
|
|
||||||
Some(self.model_from_internal(internal_model))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn register_model_config(&mut self, model: AdminModelConfig) -> Result<String, String> {
|
|
||||||
let local_config = internal::AdminModel::from(model);
|
|
||||||
if local_config.model_key.is_empty() {
|
|
||||||
return Err("No model name".to_owned());
|
|
||||||
}
|
|
||||||
let local_config_name = format!("{}.{}", local_config.app_key, local_config.model_key);
|
|
||||||
if self.models.contains_key(&local_config_name) {
|
|
||||||
return Err(format!("Model {} already exists", local_config_name));
|
|
||||||
}
|
|
||||||
let full_model_key = local_config_name.clone();
|
|
||||||
self.models.insert(local_config_name, local_config);
|
|
||||||
Ok(full_model_key)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn register_model<R: AdminRepository + 'static>(
|
|
||||||
&mut self,
|
|
||||||
model: AdminModelConfig,
|
|
||||||
repository: R,
|
|
||||||
) -> Result<(), String> {
|
|
||||||
let model_key = self.register_model_config(model)?;
|
|
||||||
let repository = AdminRepositoryWrapper::new(repository);
|
|
||||||
self.repositories
|
|
||||||
.insert(model_key, Arc::new(Mutex::new(repository)));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_repository(
|
|
||||||
&self,
|
|
||||||
app_key: &str,
|
|
||||||
model_key: &str,
|
|
||||||
) -> Result<Arc<Mutex<dyn DynAdminRepository>>, String> {
|
|
||||||
let full_model_key = format!("{}.{}", app_key, model_key);
|
|
||||||
if let Some(repo) = self.repositories.get(&full_model_key) {
|
|
||||||
// Clone the Arc to return a reference to the repository
|
|
||||||
return Ok(Arc::clone(repo));
|
|
||||||
} else {
|
|
||||||
return Err("Couldn't find repository".to_owned());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod internal {
|
|
||||||
// how the registry saves data internally.
|
|
||||||
|
|
||||||
use super::super::domain::AdminModelConfig;
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct AdminApp {
|
|
||||||
pub key: String,
|
|
||||||
pub name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct AdminModel {
|
|
||||||
pub app_key: String,
|
|
||||||
pub model_key: String,
|
|
||||||
pub name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<AdminModelConfig> for AdminModel {
|
|
||||||
fn from(value: AdminModelConfig) -> Self {
|
|
||||||
AdminModel {
|
|
||||||
app_key: value.app_key,
|
|
||||||
model_key: slug::slugify(value.name.clone()),
|
|
||||||
name: value.name,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<(&str, &str)> for AdminModel {
|
|
||||||
fn from(value: (&str, &str)) -> Self {
|
|
||||||
AdminModel {
|
|
||||||
app_key: value.0.to_owned(),
|
|
||||||
model_key: slug::slugify(value.1.to_owned()),
|
|
||||||
name: value.1.to_owned(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,350 +0,0 @@
|
|||||||
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()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let response = templates.render_html("admin/items/item_change.jinja", context);
|
|
||||||
response
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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()
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
pub struct User {
|
|
||||||
pub id: i32,
|
|
||||||
pub username: String,
|
|
||||||
}
|
|
@ -11,21 +11,21 @@ pub mod widgets;
|
|||||||
pub fn routes<S: state::DepotState + Clone + Send + Sync + 'static>() -> Router<S> {
|
pub fn routes<S: state::DepotState + Clone + Send + Sync + 'static>() -> Router<S> {
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/", get(views::index::<S>).post(views::index_action::<S>))
|
.route("/", get(views::index::<S>).post(views::index_action::<S>))
|
||||||
.route("/app/:app", get(views::list_app::<S>))
|
.route("/:section", get(views::list_section::<S>))
|
||||||
.route(
|
.route(
|
||||||
"/app/:app/model/:model",
|
"/:section/model/:model",
|
||||||
get(views::list_item_collection::<S>),
|
get(views::list_item_collection::<S>),
|
||||||
)
|
)
|
||||||
.route(
|
.route(
|
||||||
"/app/:app/model/:model/add",
|
"/:section/model/:model/add",
|
||||||
get(views::new_item::<S>).post(views::create_item::<S>),
|
get(views::new_item::<S>).post(views::create_item::<S>),
|
||||||
)
|
)
|
||||||
.route(
|
.route(
|
||||||
"/app/:app/model/:model/change/:id",
|
"/:section/model/:model/change/:id",
|
||||||
get(views::change_item::<S>).patch(views::update_item::<S>),
|
get(views::change_item::<S>).patch(views::update_item::<S>),
|
||||||
)
|
)
|
||||||
.route(
|
.route(
|
||||||
"/app/:app/model/:model/detail/:id",
|
"/:section/model/:model/detail/:id",
|
||||||
get(views::view_item_details::<S>),
|
get(views::view_item_details::<S>),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
pub use super::context::{DepotContext, DepotModel};
|
pub use super::context::{DepotContext, DepotModel};
|
||||||
pub use super::registry::DepotRegistry;
|
pub use super::registry::DepotRegistry;
|
||||||
pub use super::repository::{
|
pub use super::repository::{
|
||||||
DepotModelConfig, DepotRepository, RepoInfo, RepositoryContext, RepositoryInfo, RepositoryItem,
|
DepotModelConfig, DepotRepository, RepoInfo, RepositoryContext, RepositoryError,
|
||||||
RepositoryList,
|
RepositoryInfo, RepositoryItem, RepositoryList, RepositoryResponse, RepositoryResult,
|
||||||
};
|
};
|
||||||
pub use super::widgets::Widget;
|
pub use super::widgets::Widget;
|
||||||
|
@ -35,7 +35,7 @@ impl DepotRegistry {
|
|||||||
DepotSection {
|
DepotSection {
|
||||||
key: key.to_owned(),
|
key: key.to_owned(),
|
||||||
name: section_info.name.to_owned(),
|
name: section_info.name.to_owned(),
|
||||||
section_url: format!("/{}/section/{}", self.base_path, key.to_owned()),
|
section_url: format!("/{}/{}", self.base_path, key.to_owned()),
|
||||||
models: my_models,
|
models: my_models,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -58,7 +58,7 @@ impl DepotRegistry {
|
|||||||
|
|
||||||
fn model_from_model_info(&self, model_info: &internal::DepotModelInfo) -> DepotModel {
|
fn model_from_model_info(&self, model_info: &internal::DepotModelInfo) -> DepotModel {
|
||||||
let model_url = format!(
|
let model_url = format!(
|
||||||
"/{}/section/{}/model/{}",
|
"/{}/{}/model/{}",
|
||||||
self.base_path, model_info.section_key, model_info.model_key
|
self.base_path, model_info.section_key, model_info.model_key
|
||||||
);
|
);
|
||||||
DepotModel {
|
DepotModel {
|
||||||
@ -110,7 +110,7 @@ impl DepotRegistry {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_repository(
|
pub(crate) fn get_repository(
|
||||||
&self,
|
&self,
|
||||||
section_key: &str,
|
section_key: &str,
|
||||||
model_key: &str,
|
model_key: &str,
|
||||||
|
@ -4,6 +4,7 @@ use serde_json::Value;
|
|||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::vec::IntoIter;
|
use std::vec::IntoIter;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
use super::context::DepotModel;
|
use super::context::DepotModel;
|
||||||
use super::widgets::Widget;
|
use super::widgets::Widget;
|
||||||
@ -80,6 +81,32 @@ pub struct RepositoryItem {
|
|||||||
pub change_url: Option<String>,
|
pub change_url: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum RepositoryResponse {
|
||||||
|
NoItem,
|
||||||
|
ItemOnly(RepositoryItem),
|
||||||
|
ItemAndHeaders(RepositoryItem, axum::http::HeaderMap),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<Option<RepositoryItem>> for RepositoryResponse {
|
||||||
|
fn into(self) -> Option<RepositoryItem> {
|
||||||
|
match self {
|
||||||
|
RepositoryResponse::NoItem => None,
|
||||||
|
RepositoryResponse::ItemOnly(some) => Some(some),
|
||||||
|
RepositoryResponse::ItemAndHeaders(some, _) => Some(some),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum RepositoryError {
|
||||||
|
#[error("an unknown occurred: {0}")]
|
||||||
|
UnknownError(String),
|
||||||
|
#[error("key not found in downcast?")]
|
||||||
|
KeyNotFound,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type RepositoryResult = Result<RepositoryResponse, RepositoryError>;
|
||||||
|
|
||||||
pub enum RepositoryList {
|
pub enum RepositoryList {
|
||||||
Empty,
|
Empty,
|
||||||
List {
|
List {
|
||||||
@ -226,20 +253,20 @@ pub trait DepotRepository: Send + Sync {
|
|||||||
|
|
||||||
async fn info(&self, context: &RepositoryContext) -> RepositoryInfo;
|
async fn info(&self, context: &RepositoryContext) -> RepositoryInfo;
|
||||||
async fn list(&self, context: &RepositoryContext) -> RepositoryList;
|
async fn list(&self, context: &RepositoryContext) -> RepositoryList;
|
||||||
async fn get(&self, context: &RepositoryContext, id: &Self::Key) -> Option<RepositoryItem>;
|
async fn get(&self, context: &RepositoryContext, id: &Self::Key) -> RepositoryResult;
|
||||||
async fn create(&mut self, context: &RepositoryContext, data: Value) -> Option<RepositoryItem>;
|
async fn create(&mut self, context: &RepositoryContext, data: Value) -> RepositoryResult;
|
||||||
async fn update(
|
async fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
context: &RepositoryContext,
|
context: &RepositoryContext,
|
||||||
id: &Self::Key,
|
id: &Self::Key,
|
||||||
data: Value,
|
data: Value,
|
||||||
) -> Option<RepositoryItem>;
|
) -> RepositoryResult;
|
||||||
async fn replace(
|
async fn replace(
|
||||||
&mut self,
|
&mut self,
|
||||||
context: &RepositoryContext,
|
context: &RepositoryContext,
|
||||||
id: &Self::Key,
|
id: &Self::Key,
|
||||||
data: Value,
|
data: Value,
|
||||||
) -> Option<RepositoryItem>;
|
) -> RepositoryResult;
|
||||||
async fn delete(&mut self, context: &RepositoryContext, id: &Self::Key) -> Option<Value>;
|
async fn delete(&mut self, context: &RepositoryContext, id: &Self::Key) -> Option<Value>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -248,24 +275,20 @@ pub(crate) trait DynDepotRepository: Send + Sync {
|
|||||||
fn key_from_string(&self, s: String) -> Option<Box<dyn PrimaryKeyType>>;
|
fn key_from_string(&self, s: String) -> Option<Box<dyn PrimaryKeyType>>;
|
||||||
async fn info(&self, context: &RepositoryContext) -> RepositoryInfo;
|
async fn info(&self, context: &RepositoryContext) -> RepositoryInfo;
|
||||||
async fn list(&self, context: &RepositoryContext) -> RepositoryList;
|
async fn list(&self, context: &RepositoryContext) -> RepositoryList;
|
||||||
async fn get(
|
async fn get(&self, context: &RepositoryContext, id: &dyn PrimaryKeyType) -> RepositoryResult;
|
||||||
&self,
|
async fn create(&mut self, context: &RepositoryContext, data: Value) -> RepositoryResult;
|
||||||
context: &RepositoryContext,
|
|
||||||
id: &dyn PrimaryKeyType,
|
|
||||||
) -> Option<RepositoryItem>;
|
|
||||||
async fn create(&mut self, context: &RepositoryContext, data: Value) -> Option<RepositoryItem>;
|
|
||||||
async fn update(
|
async fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
context: &RepositoryContext,
|
context: &RepositoryContext,
|
||||||
id: &dyn PrimaryKeyType,
|
id: &dyn PrimaryKeyType,
|
||||||
data: Value,
|
data: Value,
|
||||||
) -> Option<RepositoryItem>;
|
) -> RepositoryResult;
|
||||||
async fn replace(
|
async fn replace(
|
||||||
&mut self,
|
&mut self,
|
||||||
context: &RepositoryContext,
|
context: &RepositoryContext,
|
||||||
id: &dyn PrimaryKeyType,
|
id: &dyn PrimaryKeyType,
|
||||||
data: Value,
|
data: Value,
|
||||||
) -> Option<RepositoryItem>;
|
) -> RepositoryResult;
|
||||||
async fn delete(
|
async fn delete(
|
||||||
&mut self,
|
&mut self,
|
||||||
context: &RepositoryContext,
|
context: &RepositoryContext,
|
||||||
@ -305,19 +328,15 @@ impl<T: DepotRepository> DynDepotRepository for DepotRepositoryWrapper<T> {
|
|||||||
self.inner.list(context).await
|
self.inner.list(context).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get(
|
async fn get(&self, context: &RepositoryContext, id: &dyn PrimaryKeyType) -> RepositoryResult {
|
||||||
&self,
|
|
||||||
context: &RepositoryContext,
|
|
||||||
id: &dyn PrimaryKeyType,
|
|
||||||
) -> Option<RepositoryItem> {
|
|
||||||
if let Some(key) = id.as_any().downcast_ref::<T::Key>() {
|
if let Some(key) = id.as_any().downcast_ref::<T::Key>() {
|
||||||
self.inner.get(context, key).await
|
self.inner.get(context, key).await
|
||||||
} else {
|
} else {
|
||||||
None
|
Err(RepositoryError::KeyNotFound)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create(&mut self, context: &RepositoryContext, data: Value) -> Option<RepositoryItem> {
|
async fn create(&mut self, context: &RepositoryContext, data: Value) -> RepositoryResult {
|
||||||
self.inner.create(context, data).await
|
self.inner.create(context, data).await
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -326,11 +345,11 @@ impl<T: DepotRepository> DynDepotRepository for DepotRepositoryWrapper<T> {
|
|||||||
context: &RepositoryContext,
|
context: &RepositoryContext,
|
||||||
id: &dyn PrimaryKeyType,
|
id: &dyn PrimaryKeyType,
|
||||||
data: Value,
|
data: Value,
|
||||||
) -> Option<RepositoryItem> {
|
) -> RepositoryResult {
|
||||||
if let Some(key) = id.as_any().downcast_ref::<T::Key>() {
|
if let Some(key) = id.as_any().downcast_ref::<T::Key>() {
|
||||||
self.inner.update(context, key, data).await
|
self.inner.update(context, key, data).await
|
||||||
} else {
|
} else {
|
||||||
None
|
Err(RepositoryError::KeyNotFound)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -339,11 +358,11 @@ impl<T: DepotRepository> DynDepotRepository for DepotRepositoryWrapper<T> {
|
|||||||
context: &RepositoryContext,
|
context: &RepositoryContext,
|
||||||
id: &dyn PrimaryKeyType,
|
id: &dyn PrimaryKeyType,
|
||||||
data: Value,
|
data: Value,
|
||||||
) -> Option<RepositoryItem> {
|
) -> RepositoryResult {
|
||||||
if let Some(key) = id.as_any().downcast_ref::<T::Key>() {
|
if let Some(key) = id.as_any().downcast_ref::<T::Key>() {
|
||||||
self.inner.replace(context, key, data).await
|
self.inner.replace(context, key, data).await
|
||||||
} else {
|
} else {
|
||||||
None
|
Err(RepositoryError::KeyNotFound)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ pub async fn index_action<S: DepotState + Clone + Send + Sync + 'static>(
|
|||||||
"There is your answer!".to_owned()
|
"There is your answer!".to_owned()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn list_app<S: DepotState + Clone + Send + Sync + 'static>(
|
pub async fn list_section<S: DepotState + Clone + Send + Sync + 'static>(
|
||||||
depot: State<S>,
|
depot: State<S>,
|
||||||
Path(depot_key): Path<String>,
|
Path(depot_key): Path<String>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
@ -107,7 +107,7 @@ pub async fn view_item_details<S: DepotState + Clone + Send + Sync + 'static>(
|
|||||||
sections: registry.get_sections(),
|
sections: registry.get_sections(),
|
||||||
item_info: Some(repo.info(&depot_model).await),
|
item_info: Some(repo.info(&depot_model).await),
|
||||||
item_list: repo.list(&depot_model).await,
|
item_list: repo.list(&depot_model).await,
|
||||||
item: repo.get(&depot_model, key.as_ref()).await,
|
item: repo.get(&depot_model, key.as_ref()).await.unwrap().into(),
|
||||||
item_model: Some(depot_model),
|
item_model: Some(depot_model),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
@ -182,7 +182,7 @@ pub async fn create_item<S: DepotState + Clone + Send + Sync + 'static>(
|
|||||||
item_info: Some(repo.info(&depot_model).await),
|
item_info: Some(repo.info(&depot_model).await),
|
||||||
item_list: repo.list(&depot_model).await,
|
item_list: repo.list(&depot_model).await,
|
||||||
item_model: Some(depot_model),
|
item_model: Some(depot_model),
|
||||||
item: result,
|
item: result.unwrap().into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -207,18 +207,19 @@ pub async fn change_item<S: DepotState + Clone + Send + Sync + 'static>(
|
|||||||
let repo = repo.lock().await;
|
let repo = repo.lock().await;
|
||||||
let depot_model = registry
|
let depot_model = registry
|
||||||
.get_model(&depot_key, &model_key)
|
.get_model(&depot_key, &model_key)
|
||||||
.expect("depot Model not found?");
|
.expect("Depot Model not found?");
|
||||||
if let Some(key) = repo.key_from_string(id) {
|
if let Some(key) = repo.key_from_string(id) {
|
||||||
DepotContext {
|
DepotContext {
|
||||||
base: base_template(&headers),
|
base: base_template(&headers),
|
||||||
sections: registry.get_sections(),
|
sections: registry.get_sections(),
|
||||||
item_info: Some(repo.info(&depot_model).await),
|
item_info: Some(repo.info(&depot_model).await),
|
||||||
item_list: repo.list(&depot_model).await,
|
item_list: repo.list(&depot_model).await,
|
||||||
item: repo.get(&depot_model, key.as_ref()).await,
|
item: repo.get(&depot_model, key.as_ref()).await.unwrap().into(),
|
||||||
item_model: Some(depot_model),
|
item_model: Some(depot_model),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Repository did not have key.
|
||||||
DepotContext {
|
DepotContext {
|
||||||
base: base_template(&headers),
|
base: base_template(&headers),
|
||||||
sections: registry.get_sections(),
|
sections: registry.get_sections(),
|
||||||
@ -226,6 +227,7 @@ pub async fn change_item<S: DepotState + Clone + Send + Sync + 'static>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Repository could not be loaded.
|
||||||
DepotContext {
|
DepotContext {
|
||||||
base: base_template(&headers),
|
base: base_template(&headers),
|
||||||
sections: registry.get_sections(),
|
sections: registry.get_sections(),
|
||||||
@ -255,7 +257,7 @@ pub async fn update_item<S: DepotState + Clone + Send + Sync + 'static>(
|
|||||||
sections: registry.get_sections(),
|
sections: registry.get_sections(),
|
||||||
item_info: Some(repo.info(&depot_model).await),
|
item_info: Some(repo.info(&depot_model).await),
|
||||||
item_list: repo.list(&depot_model).await,
|
item_list: repo.list(&depot_model).await,
|
||||||
item: result,
|
item: result.unwrap().into(),
|
||||||
item_model: Some(depot_model),
|
item_model: Some(depot_model),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
|
@ -45,25 +45,25 @@ impl DepotRepository for UserRepository {
|
|||||||
.set_widget("is_superuser", Widget::checkbox())
|
.set_widget("is_superuser", Widget::checkbox())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get(&self, model: &RepositoryContext, id: &Self::Key) -> Option<RepositoryItem> {
|
async fn get(&self, model: &RepositoryContext, id: &Self::Key) -> RepositoryResult {
|
||||||
let id: i32 = *id as i32; // use try_into() instead.
|
let id: i32 = *id as i32; // use try_into() instead.
|
||||||
let get_user = entity::User::find_by_id(id).one(&self.connection).await;
|
let get_user = entity::User::find_by_id(id).one(&self.connection).await;
|
||||||
match get_user {
|
if let Ok(get_user) = get_user {
|
||||||
Ok(get_user) => {
|
if let Some(user) = get_user {
|
||||||
if let Some(user) = get_user {
|
let id = user.id.to_string();
|
||||||
let id = user.id.to_string();
|
match serde_json::to_value(&user) {
|
||||||
match serde_json::to_value(&user) {
|
Ok(item) => {
|
||||||
Ok(item) => {
|
return Ok(RepositoryResponse::ItemOnly(model.build_item(&*id, item)));
|
||||||
return Some(model.build_item(&*id, item));
|
}
|
||||||
}
|
Err(_) => {
|
||||||
Err(_) => return None,
|
return Err(RepositoryError::UnknownError(
|
||||||
|
"JSON Error creating value".to_owned(),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(_) => return None,
|
|
||||||
}
|
}
|
||||||
|
Ok(RepositoryResponse::NoItem)
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn list(&self, model: &RepositoryContext) -> RepositoryList {
|
async fn list(&self, model: &RepositoryContext) -> RepositoryList {
|
||||||
@ -87,7 +87,7 @@ impl DepotRepository for UserRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create(&mut self, model: &RepositoryContext, data: Value) -> Option<RepositoryItem> {
|
async fn create(&mut self, model: &RepositoryContext, data: Value) -> RepositoryResult {
|
||||||
if let Value::Object(data) = data {
|
if let Value::Object(data) = data {
|
||||||
let username = data.get("username").unwrap().as_str().unwrap();
|
let username = data.get("username").unwrap().as_str().unwrap();
|
||||||
let password = data.get("password").unwrap().as_str().unwrap();
|
let password = data.get("password").unwrap().as_str().unwrap();
|
||||||
@ -119,10 +119,11 @@ impl DepotRepository for UserRepository {
|
|||||||
|
|
||||||
if let Ok(user) = user.insert(&self.connection).await {
|
if let Ok(user) = user.insert(&self.connection).await {
|
||||||
let id = user.id.to_string();
|
let id = user.id.to_string();
|
||||||
return Some(model.build_item(&*id, serde_json::to_value(&user).unwrap()));
|
let item = model.build_item(&*id, serde_json::to_value(&user).unwrap());
|
||||||
|
return Ok(RepositoryResponse::ItemOnly(item));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
Ok(RepositoryResponse::NoItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn update(
|
async fn update(
|
||||||
@ -130,7 +131,7 @@ impl DepotRepository for UserRepository {
|
|||||||
model: &RepositoryContext,
|
model: &RepositoryContext,
|
||||||
id: &Self::Key,
|
id: &Self::Key,
|
||||||
data: Value,
|
data: Value,
|
||||||
) -> Option<RepositoryItem> {
|
) -> RepositoryResult {
|
||||||
let id: i32 = *id as i32;
|
let id: i32 = *id as i32;
|
||||||
let user: Option<entity::user::Model> = entity::User::find_by_id(id)
|
let user: Option<entity::user::Model> = entity::User::find_by_id(id)
|
||||||
.one(&self.connection)
|
.one(&self.connection)
|
||||||
@ -171,10 +172,12 @@ impl DepotRepository for UserRepository {
|
|||||||
// update
|
// update
|
||||||
if let Ok(user) = user.update(&self.connection).await {
|
if let Ok(user) = user.update(&self.connection).await {
|
||||||
let id = user.id.to_string();
|
let id = user.id.to_string();
|
||||||
return Some(model.build_item(&*id, serde_json::to_value(&user).unwrap()));
|
return Ok(RepositoryResponse::ItemOnly(
|
||||||
|
model.build_item(&*id, serde_json::to_value(&user).unwrap()),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
Ok(RepositoryResponse::NoItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn replace(
|
async fn replace(
|
||||||
@ -182,7 +185,7 @@ impl DepotRepository for UserRepository {
|
|||||||
model: &RepositoryContext,
|
model: &RepositoryContext,
|
||||||
id: &Self::Key,
|
id: &Self::Key,
|
||||||
data: Value,
|
data: Value,
|
||||||
) -> Option<RepositoryItem> {
|
) -> RepositoryResult {
|
||||||
self.update(model, id, data).await
|
self.update(model, id, data).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,17 +34,13 @@ impl DepotRepository for Repository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// POST on item collection.
|
// POST on item collection.
|
||||||
async fn create(
|
async fn create(&mut self, model: &RepositoryContext, mut data: Value) -> RepositoryResult {
|
||||||
&mut self,
|
Ok(RepositoryResponse::NoItem)
|
||||||
model: &RepositoryContext,
|
|
||||||
mut data: Value,
|
|
||||||
) -> Option<RepositoryItem> {
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET single item.
|
// GET single item.
|
||||||
async fn get(&self, model: &RepositoryContext, id: &Self::Key) -> Option<RepositoryItem> {
|
async fn get(&self, model: &RepositoryContext, id: &Self::Key) -> RepositoryResult {
|
||||||
None
|
Ok(RepositoryResponse::NoItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PATCH single item.
|
// PATCH single item.
|
||||||
@ -53,8 +49,8 @@ impl DepotRepository for Repository {
|
|||||||
model: &RepositoryContext,
|
model: &RepositoryContext,
|
||||||
id: &Self::Key,
|
id: &Self::Key,
|
||||||
data: Value,
|
data: Value,
|
||||||
) -> Option<RepositoryItem> {
|
) -> RepositoryResult {
|
||||||
None
|
Ok(RepositoryResponse::NoItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PUT single item.
|
// PUT single item.
|
||||||
@ -63,8 +59,8 @@ impl DepotRepository for Repository {
|
|||||||
model: &RepositoryContext,
|
model: &RepositoryContext,
|
||||||
id: &Self::Key,
|
id: &Self::Key,
|
||||||
data: Value,
|
data: Value,
|
||||||
) -> Option<RepositoryItem> {
|
) -> RepositoryResult {
|
||||||
None
|
Ok(RepositoryResponse::NoItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DELETE single item.
|
// DELETE single item.
|
||||||
|
@ -61,17 +61,13 @@ impl DepotRepository for FileRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// POST on item collection.
|
// POST on item collection.
|
||||||
async fn create(
|
async fn create(&mut self, model: &RepositoryContext, mut data: Value) -> RepositoryResult {
|
||||||
&mut self,
|
Ok(RepositoryResponse::NoItem)
|
||||||
model: &RepositoryContext,
|
|
||||||
mut data: Value,
|
|
||||||
) -> Option<RepositoryItem> {
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET single item.
|
// GET single item.
|
||||||
async fn get(&self, model: &RepositoryContext, id: &Self::Key) -> Option<RepositoryItem> {
|
async fn get(&self, model: &RepositoryContext, id: &Self::Key) -> RepositoryResult {
|
||||||
None
|
Ok(RepositoryResponse::NoItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PATCH single item.
|
// PATCH single item.
|
||||||
@ -80,8 +76,8 @@ impl DepotRepository for FileRepository {
|
|||||||
model: &RepositoryContext,
|
model: &RepositoryContext,
|
||||||
id: &Self::Key,
|
id: &Self::Key,
|
||||||
data: Value,
|
data: Value,
|
||||||
) -> Option<RepositoryItem> {
|
) -> RepositoryResult {
|
||||||
None
|
Ok(RepositoryResponse::NoItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PUT single item.
|
// PUT single item.
|
||||||
@ -90,8 +86,8 @@ impl DepotRepository for FileRepository {
|
|||||||
model: &RepositoryContext,
|
model: &RepositoryContext,
|
||||||
id: &Self::Key,
|
id: &Self::Key,
|
||||||
data: Value,
|
data: Value,
|
||||||
) -> Option<RepositoryItem> {
|
) -> RepositoryResult {
|
||||||
None
|
Ok(RepositoryResponse::NoItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DELETE single item.
|
// DELETE single item.
|
||||||
|
@ -57,11 +57,13 @@ impl DepotRepository for MyStaticRepository {
|
|||||||
//.set_widget("name", Widget::textarea().options(&[("disabled", "true")]))
|
//.set_widget("name", Widget::textarea().options(&[("disabled", "true")]))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get(&self, model: &RepositoryContext, id: &Self::Key) -> Option<RepositoryItem> {
|
async fn get(&self, model: &RepositoryContext, id: &Self::Key) -> RepositoryResult {
|
||||||
let id = *id as usize;
|
let id = *id as usize;
|
||||||
let item = self.content.get(id - 1).cloned().unwrap();
|
let item = self.content.get(id - 1).cloned().unwrap();
|
||||||
let id = item.get("id").unwrap();
|
let id = item.get("id").unwrap();
|
||||||
Some(model.build_item(&*id.to_string(), item))
|
Ok(RepositoryResponse::ItemOnly(
|
||||||
|
model.build_item(&*id.to_string(), item),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn list(&self, model: &RepositoryContext) -> RepositoryList {
|
async fn list(&self, model: &RepositoryContext) -> RepositoryList {
|
||||||
@ -75,11 +77,7 @@ impl DepotRepository for MyStaticRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create(
|
async fn create(&mut self, model: &RepositoryContext, mut data: Value) -> RepositoryResult {
|
||||||
&mut self,
|
|
||||||
model: &RepositoryContext,
|
|
||||||
mut data: Value,
|
|
||||||
) -> Option<RepositoryItem> {
|
|
||||||
debug!("Asked to create: {}", data);
|
debug!("Asked to create: {}", data);
|
||||||
|
|
||||||
let new_id = self.next_id;
|
let new_id = self.next_id;
|
||||||
@ -93,7 +91,9 @@ impl DepotRepository for MyStaticRepository {
|
|||||||
self.content.push(data.clone());
|
self.content.push(data.clone());
|
||||||
|
|
||||||
// Return the newly created item
|
// Return the newly created item
|
||||||
Some(model.build_item(&*new_id.to_string(), data))
|
Ok(RepositoryResponse::ItemOnly(
|
||||||
|
model.build_item(&*new_id.to_string(), data),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn update(
|
async fn update(
|
||||||
@ -101,7 +101,7 @@ impl DepotRepository for MyStaticRepository {
|
|||||||
model: &RepositoryContext,
|
model: &RepositoryContext,
|
||||||
id: &Self::Key,
|
id: &Self::Key,
|
||||||
data: Value,
|
data: Value,
|
||||||
) -> Option<RepositoryItem> {
|
) -> RepositoryResult {
|
||||||
debug!("I would now update: {}, {}", id, data);
|
debug!("I would now update: {}, {}", id, data);
|
||||||
|
|
||||||
// First, find the index of the item to update
|
// First, find the index of the item to update
|
||||||
@ -119,11 +119,13 @@ impl DepotRepository for MyStaticRepository {
|
|||||||
*item = data.clone();
|
*item = data.clone();
|
||||||
|
|
||||||
if let Some(item_id) = item.get("id") {
|
if let Some(item_id) = item.get("id") {
|
||||||
return Some(model.build_item(&*item_id.to_string(), data));
|
return Ok(RepositoryResponse::ItemOnly(
|
||||||
|
model.build_item(&*item_id.to_string(), data),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
Ok(RepositoryResponse::NoItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn replace(
|
async fn replace(
|
||||||
@ -131,7 +133,7 @@ impl DepotRepository for MyStaticRepository {
|
|||||||
model: &RepositoryContext,
|
model: &RepositoryContext,
|
||||||
id: &Self::Key,
|
id: &Self::Key,
|
||||||
data: Value,
|
data: Value,
|
||||||
) -> Option<RepositoryItem> {
|
) -> RepositoryResult {
|
||||||
self.update(model, id, data).await
|
self.update(model, id, data).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user