1pub mod config;
60pub mod dashboard;
61pub mod error;
62pub mod field;
63pub mod model;
64pub mod registry;
65pub mod ui;
66pub mod views;
67
68pub use config::*;
69pub use dashboard::*;
70pub use error::*;
71pub use field::*;
72pub use model::*;
73pub use registry::*;
74pub use ui::*;
75pub use views::*;
76
77use serde::{Deserialize, Serialize};
78use std::collections::HashMap;
79use std::sync::Arc;
80
81pub struct Admin {
83 config: AdminConfig,
85 registry: ModelRegistry,
87}
88
89impl Admin {
90 pub fn new() -> Self {
92 Self {
93 config: AdminConfig::default(),
94 registry: ModelRegistry::new(),
95 }
96 }
97
98 pub fn title(mut self, title: impl Into<String>) -> Self {
100 self.config.title = title.into();
101 self
102 }
103
104 pub fn base_path(mut self, path: impl Into<String>) -> Self {
106 self.config.base_path = path.into();
107 self
108 }
109
110 pub fn theme(mut self, theme: Theme) -> Self {
112 self.config.theme = theme;
113 self
114 }
115
116 pub fn items_per_page(mut self, count: usize) -> Self {
118 self.config.items_per_page = count;
119 self
120 }
121
122 pub fn require_auth(mut self, required: bool) -> Self {
124 self.config.require_auth = required;
125 self
126 }
127
128 pub fn register_model(mut self, model: ModelDefinition) -> Self {
130 self.registry.register(model);
131 self
132 }
133
134 pub fn build(self) -> AdminInstance {
136 AdminInstance {
137 config: Arc::new(self.config),
138 registry: Arc::new(self.registry),
139 }
140 }
141}
142
143impl Default for Admin {
144 fn default() -> Self {
145 Self::new()
146 }
147}
148
149#[derive(Clone)]
151pub struct AdminInstance {
152 pub config: Arc<AdminConfig>,
154 pub registry: Arc<ModelRegistry>,
156}
157
158impl AdminInstance {
159 pub fn config(&self) -> &AdminConfig {
161 &self.config
162 }
163
164 pub fn registry(&self) -> &ModelRegistry {
166 &self.registry
167 }
168
169 pub fn routes(&self) -> AdminRoutes {
171 AdminRoutes::new(self.clone())
172 }
173
174 pub fn get_model(&self, name: &str) -> Option<&ModelDefinition> {
176 self.registry.get(name)
177 }
178
179 pub fn models(&self) -> Vec<&ModelDefinition> {
181 self.registry.all()
182 }
183}
184
185pub struct AdminRoutes {
187 admin: AdminInstance,
188}
189
190impl AdminRoutes {
191 pub fn new(admin: AdminInstance) -> Self {
193 Self { admin }
194 }
195
196 pub fn base_path(&self) -> &str {
198 &self.admin.config.base_path
199 }
200
201 pub async fn dashboard(&self) -> DashboardView {
203 DashboardView::new(&self.admin)
204 }
205
206 pub async fn list(&self, model_name: &str, params: ListParams) -> Option<ListView> {
208 self.admin
209 .get_model(model_name)
210 .map(|model| ListView::new(model, params))
211 }
212
213 pub async fn detail(&self, model_name: &str, id: &str) -> Option<DetailView> {
215 self.admin
216 .get_model(model_name)
217 .map(|model| DetailView::new(model, id.to_string()))
218 }
219
220 pub async fn create(&self, model_name: &str) -> Option<CreateView> {
222 self.admin
223 .get_model(model_name)
224 .map(|model| CreateView::new(model))
225 }
226}
227
228#[derive(Debug, Clone, Default, Serialize, Deserialize)]
230pub struct ListParams {
231 pub page: Option<usize>,
233 pub per_page: Option<usize>,
235 pub sort: Option<String>,
237 pub order: Option<SortOrder>,
239 pub search: Option<String>,
241 pub filters: HashMap<String, String>,
243}
244
245impl ListParams {
246 pub fn page(&self) -> usize {
248 self.page.unwrap_or(1).max(1)
249 }
250
251 pub fn per_page(&self, default: usize) -> usize {
253 self.per_page.unwrap_or(default).min(100)
254 }
255
256 pub fn offset(&self, default_per_page: usize) -> usize {
258 (self.page() - 1) * self.per_page(default_per_page)
259 }
260}
261
262#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
264pub enum SortOrder {
265 #[default]
266 Asc,
267 Desc,
268}
269
270impl SortOrder {
271 pub fn as_sql(&self) -> &'static str {
273 match self {
274 Self::Asc => "ASC",
275 Self::Desc => "DESC",
276 }
277 }
278
279 pub fn toggle(&self) -> Self {
281 match self {
282 Self::Asc => Self::Desc,
283 Self::Desc => Self::Asc,
284 }
285 }
286}
287
288#[cfg(test)]
289mod tests {
290 use super::*;
291
292 #[test]
293 fn test_admin_builder() {
294 let admin = Admin::new()
295 .title("Test Admin")
296 .base_path("/admin")
297 .items_per_page(25)
298 .build();
299
300 assert_eq!(admin.config.title, "Test Admin");
301 assert_eq!(admin.config.base_path, "/admin");
302 assert_eq!(admin.config.items_per_page, 25);
303 }
304
305 #[test]
306 fn test_list_params() {
307 let params = ListParams {
308 page: Some(2),
309 per_page: Some(20),
310 ..Default::default()
311 };
312
313 assert_eq!(params.page(), 2);
314 assert_eq!(params.per_page(10), 20);
315 assert_eq!(params.offset(10), 20);
316 }
317
318 #[test]
319 fn test_sort_order() {
320 assert_eq!(SortOrder::Asc.toggle(), SortOrder::Desc);
321 assert_eq!(SortOrder::Desc.toggle(), SortOrder::Asc);
322 }
323}
324