1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
//! Central registry of admin resources indexed by URL key.
use indexmap::IndexMap;
use crate::admin::helper::resource_entry::ResourceEntry;
use crate::admin::resource::{AdminResource, DisplayConfig};
use crate::auth::session::CurrentUser;
/// Admin resource registry — IndexMap key → ResourceEntry.
///
/// Powered by the code generated by the daemon (`src/admins/generated.rs`).
/// Shared read-only via `Arc<AdminRegistry>` in the Axum state.
/// Insertion order (order in `generated.rs`) is preserved.
#[derive(Default)]
pub struct AdminRegistry {
pub resources: IndexMap<String, ResourceEntry>,
}
impl AdminRegistry {
pub fn new() -> Self {
Self {
resources: IndexMap::new(),
}
}
/// Registers a resource. Called by generated code at boot.
pub fn register(&mut self, entry: ResourceEntry) {
self.resources.insert(entry.meta.key.to_string(), entry);
}
/// Lookup by URL key (e.g., "users", "blog")
pub fn get(&self, key: &str) -> Option<&ResourceEntry> {
self.resources.get(key)
}
pub fn all(&self) -> impl Iterator<Item = &ResourceEntry> {
self.resources.values()
}
/// Single source of truth for admin visibility: superuser sees everything,
/// otherwise only resources the user has read access to. Keep this the only
/// place the rule lives so a change can't leak a resource in one view but
/// not another.
fn can_see(user: &CurrentUser, entry: &ResourceEntry) -> bool {
user.is_superuser || user.can_access_resource(entry.meta.key)
}
/// Resource metadata the user is allowed to see (for nav / filters). Scoped
/// children stay listed: they are reachable both at the top level (direct
/// access) and inline under their parent — the two coexist by design.
pub fn visible_to<'a>(&'a self, user: &CurrentUser) -> Vec<&'a AdminResource> {
self.all()
.filter(|e| Self::can_see(user, e))
.map(|e| &e.meta)
.collect()
}
/// Keys of the resources the user may see — used to scope history queries
/// so a staff member never sees audit rows for resources they can't access.
pub fn accessible_keys(&self, user: &CurrentUser) -> Vec<String> {
self.all()
.filter(|e| Self::can_see(user, e))
.map(|e| e.meta.key.to_string())
.collect()
}
pub fn is_empty(&self) -> bool {
self.resources.is_empty()
}
pub fn len(&self) -> usize {
self.resources.len()
}
pub fn contains(&self, key: &str) -> bool {
self.resources.contains_key(key)
}
pub fn keys(&self) -> Vec<&str> {
self.resources.keys().map(|k| k.as_str()).collect()
}
/// Applies a display configuration to an existing resource (built-in or declared).
///
/// Called by generated code after `admin_register()` for entries in the `configure {}` block.
/// No effect if the key does not exist.
pub fn configure(&mut self, key: &str, display: DisplayConfig) {
if let Some(entry) = self.resources.get_mut(key) {
entry.meta.display = display;
}
}
/// Applies group actions to an existing resource (built-in or declared).
pub fn configure_group_actions(
&mut self,
key: &str,
actions: Vec<crate::admin::helper::resource_entry::GroupAction>,
) {
if let Some(entry) = self.resources.get_mut(key) {
entry.group_actions = actions;
}
}
/// Removes a resource from the registry (e.g., to hide a builtin when extend!{} takes over).
pub fn remove(&mut self, key: &str) {
self.resources.shift_remove(key);
}
/// Reorders the registry according to the provided list of keys.
/// Unlisted keys are added at the end in their insertion order.
pub fn reorder(&mut self, order: &[String]) {
let mut reordered = indexmap::IndexMap::new();
for key in order {
if let Some(entry) = self.resources.shift_remove(key.as_str()) {
reordered.insert(key.clone(), entry);
}
}
// Remaining unlisted keys
for (key, entry) in std::mem::take(&mut self.resources) {
reordered.insert(key, entry);
}
self.resources = reordered;
}
}