use std::collections::{HashMap, HashSet};
use umbral_auth::AuthUser;
use crate::AdminModel;
#[derive(Debug, Clone)]
pub struct AdminRegistration {
pub model: AdminModel,
pub plugin: String,
pub label: String,
pub icon: Option<String>,
}
#[derive(Debug, Clone)]
pub struct App {
pub plugin: String,
pub label: String,
pub models: Vec<AdminRegistration>,
}
#[derive(Debug, Default, Clone)]
pub struct AdminRegistry {
entries: HashMap<String, AdminRegistration>,
}
impl AdminRegistry {
pub fn register(&mut self, plugin: &str, model: AdminModel) {
let label = model.label.clone().unwrap_or_else(|| {
titlecase(&model.table)
});
let icon = model.icon.clone();
let table = model.table.clone();
self.entries.insert(
table,
AdminRegistration {
model,
plugin: plugin.to_string(),
label,
icon,
},
);
}
pub fn apps(&self, _viewer: &AuthUser, viewer_codenames: Option<&HashSet<String>>) -> Vec<App> {
let mut merged: HashMap<String, AdminRegistration> = HashMap::new();
for plugin_name in umbral::migrate::registered_plugins() {
for meta in umbral::migrate::models_for_plugin(&plugin_name) {
let label = titlecase(&meta.display);
let icon = meta.icon.clone();
let table = meta.table.clone();
let reg = AdminRegistration {
model: AdminModel::new(&table),
plugin: plugin_name.clone(),
label,
icon: Some(icon),
};
merged.insert(table, reg);
}
}
for meta in umbral::migrate::registered_models() {
if !merged.contains_key(&meta.table) {
let label = titlecase(&meta.display);
let icon = meta.icon.clone();
let table = meta.table.clone();
let reg = AdminRegistration {
model: AdminModel::new(&table),
plugin: "app".to_string(),
label,
icon: Some(icon),
};
merged.insert(table, reg);
}
}
for (table, explicit) in &self.entries {
merged.insert(table.clone(), explicit.clone());
}
let merged_filtered: Vec<AdminRegistration> = merged
.into_values()
.filter(|reg| match viewer_codenames {
None => true,
Some(codenames) => {
let required = format!("{}.view_{}", reg.plugin, reg.model.table);
codenames.contains(&required)
}
})
.collect();
let mut by_plugin: HashMap<String, Vec<AdminRegistration>> = HashMap::new();
for reg in merged_filtered {
by_plugin.entry(reg.plugin.clone()).or_default().push(reg);
}
let mut apps: Vec<App> = by_plugin
.into_iter()
.map(|(plugin, mut models)| {
models.sort_by(|a, b| a.label.cmp(&b.label));
let label = plugin.clone();
App {
plugin,
label,
models,
}
})
.collect();
apps.sort_by(|a, b| match (a.plugin.as_str(), b.plugin.as_str()) {
("app", "app") => std::cmp::Ordering::Equal,
("app", _) => std::cmp::Ordering::Greater,
(_, "app") => std::cmp::Ordering::Less,
_ => a.plugin.cmp(&b.plugin),
});
apps
}
pub fn get(&self, table: &str) -> Option<&AdminRegistration> {
self.entries.get(table)
}
pub fn all(&self) -> impl Iterator<Item = &AdminRegistration> {
self.entries.values()
}
}
fn titlecase(s: &str) -> String {
if s.is_empty() {
return String::new();
}
s.split('_')
.map(|word| {
let mut c = word.chars();
match c.next() {
None => String::new(),
Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
}
})
.collect::<Vec<_>>()
.join(" ")
}