1use crate::{adapter::DataAdapter, error::AdminError, field::Field};
2use serde_json::Value;
3use std::{collections::HashMap, future::Future, marker::PhantomData, pin::Pin};
4
5pub enum ActionTarget {
6 List,
7 Detail,
8}
9
10pub struct ActionContext {
11 pub ids: Vec<Value>,
12 pub params: HashMap<String, String>,
13}
14
15pub enum ActionResult {
16 Success(String),
17 Redirect(String),
18 Error(String),
19}
20
21type ActionHandler = Box<
22 dyn Fn(ActionContext) -> Pin<Box<dyn Future<Output = Result<ActionResult, AdminError>> + Send>>
23 + Send
24 + Sync,
25>;
26
27type BeforeSaveHook =
28 Box<dyn Fn(&mut HashMap<String, Value>) -> Result<(), AdminError> + Send + Sync>;
29
30type AfterDeleteHook = Box<dyn Fn(&Value) -> Result<(), AdminError> + Send + Sync>;
31
32pub struct CustomAction {
33 pub name: String,
34 pub label: String,
35 pub target: ActionTarget,
36 pub confirm: Option<String>,
37 pub icon: Option<String>,
38 pub class: Option<String>,
39 pub handler: ActionHandler,
40}
41
42impl CustomAction {
43 pub fn builder(name: &str, label: &str) -> CustomActionBuilder {
44 CustomActionBuilder {
45 name: name.to_string(),
46 label: label.to_string(),
47 target: ActionTarget::List,
48 confirm: None,
49 icon: None,
50 class: None,
51 }
52 }
53}
54
55pub struct CustomActionBuilder {
56 name: String,
57 label: String,
58 target: ActionTarget,
59 confirm: Option<String>,
60 icon: Option<String>,
61 class: Option<String>,
62}
63
64impl CustomActionBuilder {
65 pub fn target(mut self, target: ActionTarget) -> Self {
66 self.target = target;
67 self
68 }
69
70 pub fn confirm(mut self, message: &str) -> Self {
71 self.confirm = Some(message.to_string());
72 self
73 }
74
75 pub fn icon(mut self, icon_class: &str) -> Self {
76 self.icon = Some(icon_class.to_string());
77 self
78 }
79
80 pub fn class(mut self, css_class: &str) -> Self {
81 self.class = Some(css_class.to_string());
82 self
83 }
84
85 pub fn handler<F, Fut>(self, f: F) -> CustomAction
86 where
87 F: Fn(ActionContext) -> Fut + Send + Sync + 'static,
88 Fut: Future<Output = Result<ActionResult, AdminError>> + Send + 'static,
89 {
90 CustomAction {
91 name: self.name,
92 label: self.label,
93 target: self.target,
94 confirm: self.confirm,
95 icon: self.icon,
96 class: self.class,
97 handler: Box::new(move |ctx| Box::pin(f(ctx))),
98 }
99 }
100}
101
102#[derive(Debug, Clone, Default)]
103pub struct EntityPermissions {
104 pub view: Option<String>,
106 pub create: Option<String>,
108 pub edit: Option<String>,
110 pub delete: Option<String>,
112}
113
114pub struct EntityAdmin {
115 pub entity_name: String,
116 pub label: String,
117 pub icon: String,
118 pub group: Option<String>,
119 pub pk_field: String,
120 pub fields: Vec<Field>,
121 pub list_display: Vec<String>,
122 pub search_fields: Vec<String>,
123 pub filter_fields: Vec<String>,
124 pub filters: Vec<Field>,
125 pub actions: Vec<CustomAction>,
126 pub bulk_delete: bool,
127 pub bulk_export: bool,
128 pub adapter: Option<Box<dyn DataAdapter>>,
129 pub before_save: Option<BeforeSaveHook>,
130 pub after_delete: Option<AfterDeleteHook>,
131 pub permissions: EntityPermissions,
132 _marker: PhantomData<()>,
133}
134
135impl EntityAdmin {
136 pub fn new<T>(_entity: &str) -> Self {
137 Self {
138 entity_name: _entity.to_string(),
139 label: crate::field::default_label(_entity),
140 icon: "fa-solid fa-layer-group".to_string(),
141 group: None,
142 pk_field: "id".to_string(),
143 fields: Vec::new(),
144 list_display: Vec::new(),
145 search_fields: Vec::new(),
146 filter_fields: Vec::new(),
147 filters: Vec::new(),
148 actions: Vec::new(),
149 bulk_delete: true,
150 bulk_export: true,
151 adapter: None,
152 before_save: None,
153 after_delete: None,
154 permissions: EntityPermissions::default(),
155 _marker: PhantomData,
156 }
157 }
158
159 #[cfg(feature = "seaorm")]
160 pub fn from_entity<E>(name: &str) -> Self
161 where
162 E: sea_orm::EntityTrait,
163 E::Column: sea_orm::ColumnTrait,
164 {
165 let fields = crate::adapters::seaorm::seaorm_fields_for::<E>();
166 Self {
167 entity_name: name.to_string(),
168 label: crate::field::default_label(name),
169 icon: "fa-solid fa-layer-group".to_string(),
170 group: None,
171 pk_field: "id".to_string(),
172 fields,
173 list_display: Vec::new(),
174 search_fields: Vec::new(),
175 filter_fields: Vec::new(),
176 filters: Vec::new(),
177 actions: Vec::new(),
178 bulk_delete: true,
179 bulk_export: true,
180 adapter: None,
181 before_save: None,
182 after_delete: None,
183 permissions: EntityPermissions::default(),
184 _marker: PhantomData,
185 }
186 }
187
188 pub fn label(mut self, label: &str) -> Self {
189 self.label = label.to_string();
190 self
191 }
192
193 pub fn pk_field(mut self, pk: &str) -> Self {
194 self.pk_field = pk.to_string();
195 self
196 }
197
198 pub fn icon(mut self, icon: &str) -> Self {
201 self.icon = icon.to_string();
202 self
203 }
204
205 pub fn group(mut self, group: &str) -> Self {
208 self.group = Some(group.to_string());
209 self
210 }
211
212 pub fn field(mut self, field: Field) -> Self {
213 if let Some(pos) = self.fields.iter().position(|f| f.name == field.name) {
214 self.fields[pos] = field;
215 } else {
216 self.fields.push(field);
217 }
218 self
219 }
220
221 pub fn list_display(mut self, fields: Vec<String>) -> Self {
222 self.list_display = fields;
223 self
224 }
225
226 pub fn search_fields(mut self, fields: Vec<String>) -> Self {
227 self.search_fields = fields;
228 self
229 }
230
231 pub fn filter_fields(mut self, fields: Vec<String>) -> Self {
232 self.filter_fields = fields;
233 self
234 }
235
236 pub fn filter(mut self, field: Field) -> Self {
237 if let Some(pos) = self.filters.iter().position(|f| f.name == field.name) {
238 self.filters[pos] = field;
239 } else {
240 self.filters.push(field);
241 }
242 self
243 }
244
245 pub fn bulk_delete(mut self, enabled: bool) -> Self {
246 self.bulk_delete = enabled;
247 self
248 }
249
250 pub fn bulk_export(mut self, enabled: bool) -> Self {
251 self.bulk_export = enabled;
252 self
253 }
254
255 pub fn adapter(mut self, adapter: Box<dyn DataAdapter>) -> Self {
256 self.adapter = Some(adapter);
257 self
258 }
259
260 pub fn action(mut self, action: CustomAction) -> Self {
261 self.actions.push(action);
262 self
263 }
264
265 pub fn before_save<F>(mut self, f: F) -> Self
266 where
267 F: Fn(&mut HashMap<String, Value>) -> Result<(), AdminError> + Send + Sync + 'static,
268 {
269 self.before_save = Some(Box::new(f));
270 self
271 }
272
273 pub fn after_delete<F>(mut self, f: F) -> Self
274 where
275 F: Fn(&Value) -> Result<(), AdminError> + Send + Sync + 'static,
276 {
277 self.after_delete = Some(Box::new(f));
278 self
279 }
280
281 pub fn require_view(mut self, perm: &str) -> Self {
283 self.permissions.view = Some(perm.to_string());
284 self
285 }
286
287 pub fn require_create(mut self, perm: &str) -> Self {
289 self.permissions.create = Some(perm.to_string());
290 self
291 }
292
293 pub fn require_edit(mut self, perm: &str) -> Self {
295 self.permissions.edit = Some(perm.to_string());
296 self
297 }
298
299 pub fn require_delete(mut self, perm: &str) -> Self {
301 self.permissions.delete = Some(perm.to_string());
302 self
303 }
304
305 pub fn require_role(mut self, role: &str) -> Self {
307 let s = role.to_string();
308 self.permissions.view = Some(s.clone());
309 self.permissions.create = Some(s.clone());
310 self.permissions.edit = Some(s.clone());
311 self.permissions.delete = Some(s);
312 self
313 }
314}
315
316pub struct EntityGroupAdmin {
319 pub label: String,
320 pub icon: Option<String>,
321 entities: Vec<EntityAdmin>,
322}
323
324impl EntityGroupAdmin {
325 pub fn new(label: &str) -> Self {
326 Self {
327 label: label.to_string(),
328 icon: None,
329 entities: Vec::new(),
330 }
331 }
332
333 pub fn icon(mut self, icon: &str) -> Self {
335 self.icon = Some(icon.to_string());
336 self
337 }
338
339 pub fn register(mut self, entity: EntityAdmin) -> Self {
341 self.entities.push(entity);
342 self
343 }
344
345 pub(crate) fn into_entities(self) -> Vec<EntityAdmin> {
347 self.entities
348 .into_iter()
349 .map(|mut e| {
350 e.group = Some(self.label.clone());
351 e
352 })
353 .collect()
354 }
355}