#[derive(Debug, Clone, Copy, Default, serde::Serialize)]
pub enum AdminIdType {
#[default]
I32,
I64,
Uuid,
}
impl AdminIdType {
pub fn parse_expr(&self) -> &'static str {
match self {
AdminIdType::I32 => {
"let id = id.parse::<i32>().map_err(|_| DbErr::Custom(\"invalid id\".into()))?;"
}
AdminIdType::I64 => {
"let id = id.parse::<i64>().map_err(|_| DbErr::Custom(\"invalid id\".into()))?;"
}
AdminIdType::Uuid => {
"let id = uuid::Uuid::parse_str(&id).map_err(|_| DbErr::Custom(\"invalid id\".into()))?;"
}
}
}
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct ResourcePermissions {
pub list: Vec<String>,
pub view: Vec<String>,
pub create: Vec<String>,
pub edit: Vec<String>,
pub delete: Vec<String>,
}
impl ResourcePermissions {
pub fn uniform(roles: Vec<String>) -> Self {
Self {
list: roles.clone(),
view: roles.clone(),
create: roles.clone(),
edit: roles.clone(),
delete: roles,
}
}
pub fn can(&self, operation: CrudOperation, role: &str) -> bool {
let allowed = match operation {
CrudOperation::List => &self.list,
CrudOperation::View => &self.view,
CrudOperation::Create => &self.create,
CrudOperation::Edit => &self.edit,
CrudOperation::Delete => &self.delete,
};
allowed.iter().any(|r| r == role)
}
pub fn can_any(&self, operation: CrudOperation, roles: &[&str]) -> bool {
roles.iter().any(|role| self.can(operation, role))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
pub enum CrudOperation {
List,
View,
Create,
Edit,
Delete,
}
#[derive(Debug, Clone, Default, serde::Serialize)]
pub enum ColumnFilter {
#[default]
All,
Include(Vec<(String, String)>),
Exclude(Vec<String>),
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct DisplayConfig {
pub icon: Option<String>,
pub columns: ColumnFilter,
pub pagination: usize,
pub list_filter: Vec<(String, String, u64)>,
}
impl DisplayConfig {
pub fn new() -> Self {
Self {
icon: None,
columns: ColumnFilter::All,
pagination: 25,
list_filter: Vec::new(),
}
}
pub fn icon(mut self, icon: &str) -> Self {
self.icon = Some(icon.to_string());
self
}
pub fn pagination(mut self, per_page: usize) -> Self {
self.pagination = per_page;
self
}
pub fn columns_include(mut self, cols: Vec<(&str, &str)>) -> Self {
self.columns = ColumnFilter::Include(
cols.iter()
.map(|(c, l)| (c.to_string(), l.to_string()))
.collect(),
);
self
}
pub fn columns_exclude(mut self, cols: Vec<&str>) -> Self {
self.columns = ColumnFilter::Exclude(cols.iter().map(|s| s.to_string()).collect());
self
}
pub fn list_filter(mut self, filters: Vec<(&str, &str, u64)>) -> Self {
self.list_filter = filters
.iter()
.map(|(c, l, limit)| (c.to_string(), l.to_string(), *limit))
.collect();
self
}
}
impl Default for DisplayConfig {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct AdminResource {
pub key: &'static str,
pub model_path: &'static str,
pub form_path: &'static str,
pub title: &'static str,
pub permissions: ResourcePermissions,
pub display: DisplayConfig,
pub template_list: Option<String>,
pub template_create: Option<String>,
pub template_edit: Option<String>,
pub template_detail: Option<String>,
pub template_delete: Option<String>,
pub id_type: AdminIdType,
pub extra_context: std::collections::HashMap<String, String>,
pub inject_password: bool,
}
impl AdminResource {
pub fn new(
key: &'static str,
model_path: &'static str,
form_path: &'static str,
title: &'static str,
roles: Vec<String>,
) -> Self {
Self {
key,
model_path,
form_path,
title,
permissions: ResourcePermissions::uniform(roles),
id_type: AdminIdType::I32,
display: DisplayConfig::new(),
template_list: None,
template_create: None,
template_edit: None,
template_detail: None,
template_delete: None,
extra_context: std::collections::HashMap::new(),
inject_password: false,
}
}
pub fn with_permissions(
key: &'static str,
model_path: &'static str,
form_path: &'static str,
title: &'static str,
permissions: ResourcePermissions,
) -> Self {
Self {
key,
model_path,
form_path,
title,
permissions,
id_type: AdminIdType::I32,
display: DisplayConfig::new(),
template_list: None,
template_create: None,
template_edit: None,
template_detail: None,
template_delete: None,
extra_context: std::collections::HashMap::new(),
inject_password: false,
}
}
pub fn inject_password(mut self, v: bool) -> Self {
self.inject_password = v;
self
}
pub fn display(mut self, display: DisplayConfig) -> Self {
self.display = display;
self
}
pub fn list_route(&self) -> String {
format!("/{}/list", self.key)
}
pub fn create_route(&self) -> String {
format!("/{}/create", self.key)
}
pub fn detail_route(&self) -> String {
format!("/{}/{{id}}", self.key)
}
pub fn delete_route(&self) -> String {
format!("/{}/{{id}}/delete", self.key)
}
pub fn resolve_list(&self) -> &str {
self.template_list.as_deref().unwrap_or("admin/list.html")
}
pub fn resolve_create(&self) -> &str {
self.template_create
.as_deref()
.unwrap_or("admin/create.html")
}
pub fn resolve_edit(&self) -> &str {
self.template_edit.as_deref().unwrap_or("admin/edit.html")
}
pub fn resolve_detail(&self) -> &str {
self.template_detail
.as_deref()
.unwrap_or("admin/detail.html")
}
pub fn resolve_delete(&self) -> &str {
self.template_delete
.as_deref()
.unwrap_or("admin/delete.html")
}
pub fn template_list(mut self, path: &str) -> Self {
self.template_list = Some(path.to_string());
self
}
pub fn template_create(mut self, path: &str) -> Self {
self.template_create = Some(path.to_string());
self
}
pub fn template_edit(mut self, path: &str) -> Self {
self.template_edit = Some(path.to_string());
self
}
pub fn template_detail(mut self, path: &str) -> Self {
self.template_detail = Some(path.to_string());
self
}
pub fn template_delete(mut self, path: &str) -> Self {
self.template_delete = Some(path.to_string());
self
}
pub fn id_type(mut self, id_type: AdminIdType) -> Self {
self.id_type = id_type;
self
}
pub fn extra(mut self, key: &str, value: &str) -> Self {
self.extra_context
.insert(key.to_string(), value.to_string());
self
}
pub fn extra_map(mut self, map: std::collections::HashMap<String, String>) -> Self {
self.extra_context.extend(map);
self
}
}