Skip to main content

armature_admin/
registry.rs

1//! Model registry for admin
2
3use crate::model::ModelDefinition;
4use std::collections::HashMap;
5
6/// Registry of admin models
7#[derive(Debug, Default)]
8pub struct ModelRegistry {
9    /// Registered models by name
10    models: HashMap<String, ModelDefinition>,
11    /// Model ordering (for sidebar)
12    order: Vec<String>,
13    /// Model groups
14    groups: HashMap<String, Vec<String>>,
15}
16
17impl ModelRegistry {
18    /// Create a new registry
19    pub fn new() -> Self {
20        Self {
21            models: HashMap::new(),
22            order: Vec::new(),
23            groups: HashMap::new(),
24        }
25    }
26
27    /// Register a model
28    pub fn register(&mut self, model: ModelDefinition) {
29        let name = model.name.clone();
30        self.order.push(name.clone());
31        self.models.insert(name, model);
32    }
33
34    /// Register a model in a group
35    pub fn register_in_group(&mut self, group: impl Into<String>, model: ModelDefinition) {
36        let group = group.into();
37        let name = model.name.clone();
38
39        self.models.insert(name.clone(), model);
40
41        if !self.order.contains(&name) {
42            self.order.push(name.clone());
43        }
44
45        self.groups.entry(group).or_default().push(name);
46    }
47
48    /// Get a model by name
49    pub fn get(&self, name: &str) -> Option<&ModelDefinition> {
50        self.models.get(name)
51    }
52
53    /// Get all models
54    pub fn all(&self) -> Vec<&ModelDefinition> {
55        self.order
56            .iter()
57            .filter_map(|name| self.models.get(name))
58            .collect()
59    }
60
61    /// Get model names in order
62    pub fn names(&self) -> &[String] {
63        &self.order
64    }
65
66    /// Get models in a group
67    pub fn group(&self, name: &str) -> Vec<&ModelDefinition> {
68        self.groups
69            .get(name)
70            .map(|names| names.iter().filter_map(|n| self.models.get(n)).collect())
71            .unwrap_or_default()
72    }
73
74    /// Get all groups
75    pub fn groups(&self) -> &HashMap<String, Vec<String>> {
76        &self.groups
77    }
78
79    /// Check if a model exists
80    pub fn contains(&self, name: &str) -> bool {
81        self.models.contains_key(name)
82    }
83
84    /// Get model count
85    pub fn count(&self) -> usize {
86        self.models.len()
87    }
88
89    /// Remove a model
90    pub fn unregister(&mut self, name: &str) -> Option<ModelDefinition> {
91        self.order.retain(|n| n != name);
92        for group in self.groups.values_mut() {
93            group.retain(|n| n != name);
94        }
95        self.models.remove(name)
96    }
97
98    /// Get models for sidebar navigation
99    pub fn sidebar_items(&self) -> Vec<SidebarItem> {
100        // If there are groups, organize by group
101        if !self.groups.is_empty() {
102            let mut items = Vec::new();
103            let mut ungrouped = Vec::new();
104
105            // Add grouped models
106            for (group_name, model_names) in &self.groups {
107                let models: Vec<_> = model_names
108                    .iter()
109                    .filter_map(|n| self.models.get(n))
110                    .map(|m| SidebarItem::Model {
111                        name: m.name.clone(),
112                        label: m.verbose_name.clone(),
113                        icon: m.icon.clone(),
114                    })
115                    .collect();
116
117                if !models.is_empty() {
118                    items.push(SidebarItem::Group {
119                        name: group_name.clone(),
120                        items: models,
121                    });
122                }
123            }
124
125            // Add ungrouped models
126            for name in &self.order {
127                let in_group = self.groups.values().any(|g| g.contains(name));
128                if !in_group && let Some(model) = self.models.get(name) {
129                    ungrouped.push(SidebarItem::Model {
130                        name: model.name.clone(),
131                        label: model.verbose_name.clone(),
132                        icon: model.icon.clone(),
133                    });
134                }
135            }
136
137            items.extend(ungrouped);
138            items
139        } else {
140            // Just list all models
141            self.order
142                .iter()
143                .filter_map(|name| self.models.get(name))
144                .map(|m| SidebarItem::Model {
145                    name: m.name.clone(),
146                    label: m.verbose_name.clone(),
147                    icon: m.icon.clone(),
148                })
149                .collect()
150        }
151    }
152}
153
154/// Sidebar navigation item
155#[derive(Debug, Clone)]
156pub enum SidebarItem {
157    /// Single model
158    Model {
159        name: String,
160        label: String,
161        icon: Option<String>,
162    },
163    /// Group of models
164    Group {
165        name: String,
166        items: Vec<SidebarItem>,
167    },
168    /// Divider
169    Divider,
170    /// Custom link
171    Link {
172        label: String,
173        url: String,
174        icon: Option<String>,
175    },
176}
177
178#[cfg(test)]
179mod tests {
180    use super::*;
181    use crate::field::{FieldDefinition, FieldType};
182
183    #[test]
184    fn test_registry() {
185        let mut registry = ModelRegistry::new();
186
187        let user = ModelDefinition::builder("user")
188            .id_field()
189            .field(FieldDefinition::new("name", FieldType::String))
190            .build();
191
192        let post = ModelDefinition::builder("post")
193            .id_field()
194            .field(FieldDefinition::new("title", FieldType::String))
195            .build();
196
197        registry.register(user);
198        registry.register(post);
199
200        assert_eq!(registry.count(), 2);
201        assert!(registry.contains("user"));
202        assert!(registry.contains("post"));
203        assert!(!registry.contains("comment"));
204    }
205
206    #[test]
207    fn test_registry_groups() {
208        let mut registry = ModelRegistry::new();
209
210        let user = ModelDefinition::builder("user").id_field().build();
211
212        let role = ModelDefinition::builder("role").id_field().build();
213
214        registry.register_in_group("Auth", user);
215        registry.register_in_group("Auth", role);
216
217        let auth_models = registry.group("Auth");
218        assert_eq!(auth_models.len(), 2);
219    }
220}