1use async_trait::async_trait;
12use reinhardt_core::exception::{Error, Result};
13use reinhardt_core::security::xss::escape_html;
14use reinhardt_db::orm::Model;
15use serde::{Deserialize, Serialize};
16use std::collections::HashMap;
17use std::marker::PhantomData;
18
19#[derive(Debug, thiserror::Error)]
21pub enum AdminError {
22 #[error("Model not found: {0}")]
23 ModelNotFound(String),
24
25 #[error("Field not found: {0}")]
26 FieldNotFound(String),
27
28 #[error("Invalid filter: {0}")]
29 InvalidFilter(String),
30
31 #[error("Permission denied: {0}")]
32 PermissionDenied(String),
33
34 #[error("Query error: {0}")]
35 QueryError(String),
36}
37
38impl From<AdminError> for Error {
39 fn from(err: AdminError) -> Self {
40 Error::Validation(err.to_string())
41 }
42}
43
44#[async_trait]
46pub trait AdminView: Send + Sync {
47 async fn render(&self) -> Result<String>;
49
50 fn has_view_permission(&self) -> bool {
52 true
53 }
54
55 fn has_add_permission(&self) -> bool {
57 true
58 }
59
60 fn has_change_permission(&self) -> bool {
62 true
63 }
64
65 fn has_delete_permission(&self) -> bool {
67 true
68 }
69}
70
71pub struct ModelAdmin<M>
113where
114 M: Model + Serialize + for<'de> Deserialize<'de> + Send + Sync + Clone,
115{
116 list_display: Vec<String>,
117 search_fields: Vec<String>,
118 list_filter: Vec<String>,
119 ordering: Vec<String>,
120 list_per_page: usize,
121 show_full_result_count: bool,
122 readonly_fields: Vec<String>,
123 queryset: Option<Vec<M>>,
124 _phantom: PhantomData<M>,
125}
126
127impl<T: Model + Serialize + for<'de> Deserialize<'de> + Clone> ModelAdmin<T> {
128 pub fn new() -> Self {
165 Self {
166 list_display: vec![],
167 list_filter: vec![],
168 search_fields: vec![],
169 ordering: vec![],
170 list_per_page: 100,
171 show_full_result_count: true,
172 readonly_fields: vec![],
173 queryset: None,
174 _phantom: PhantomData,
175 }
176 }
177
178 pub fn with_list_display(mut self, fields: Vec<String>) -> Self {
216 self.list_display = fields;
217 self
218 }
219
220 pub fn with_list_filter(mut self, fields: Vec<String>) -> Self {
222 self.list_filter = fields;
223 self
224 }
225
226 pub fn with_search_fields(mut self, fields: Vec<String>) -> Self {
228 self.search_fields = fields;
229 self
230 }
231
232 pub fn with_ordering(mut self, fields: Vec<String>) -> Self {
234 self.ordering = fields;
235 self
236 }
237
238 pub fn with_list_per_page(mut self, count: usize) -> Self {
240 self.list_per_page = count;
241 self
242 }
243
244 pub fn with_show_full_result_count(mut self, show: bool) -> Self {
246 self.show_full_result_count = show;
247 self
248 }
249
250 pub fn with_readonly_fields(mut self, fields: Vec<String>) -> Self {
252 self.readonly_fields = fields;
253 self
254 }
255
256 pub fn with_queryset(mut self, queryset: Vec<T>) -> Self {
258 self.queryset = Some(queryset);
259 self
260 }
261
262 pub fn list_display(&self) -> &[String] {
264 &self.list_display
265 }
266
267 pub fn list_filter(&self) -> &[String] {
269 &self.list_filter
270 }
271
272 pub fn search_fields(&self) -> &[String] {
274 &self.search_fields
275 }
276
277 pub fn ordering(&self) -> &[String] {
279 &self.ordering
280 }
281
282 pub fn list_per_page(&self) -> usize {
284 self.list_per_page
285 }
286
287 pub fn show_full_result_count(&self) -> bool {
289 self.show_full_result_count
290 }
291
292 pub fn readonly_fields(&self) -> &[String] {
294 &self.readonly_fields
295 }
296
297 pub async fn get_queryset(&self) -> Result<Vec<T>> {
299 match &self.queryset {
300 Some(qs) => Ok(qs.clone()),
301 None => Ok(Vec::new()),
302 }
303 }
304
305 pub async fn render_list(&self) -> Result<String> {
307 let objects = self.get_queryset().await?;
308 let count = objects.len();
309
310 let mut html = String::from("<div class=\"admin-list\">\n");
311 html.push_str(&format!("<h2>{} List</h2>\n", escape_html(T::table_name())));
312 html.push_str(&format!("<p>Total: {} items</p>\n", count));
313
314 html.push_str("<table>\n<thead>\n<tr>\n");
316 for field in &self.list_display {
317 html.push_str(&format!("<th>{}</th>\n", escape_html(field)));
318 }
319 html.push_str("</tr>\n</thead>\n<tbody>\n");
320
321 for obj in objects {
323 html.push_str("<tr>\n");
324 let obj_json =
325 serde_json::to_value(&obj).map_err(|e| Error::Serialization(e.to_string()))?;
326
327 for field in &self.list_display {
328 let value = obj_json
329 .get(field)
330 .map(|v| v.to_string())
331 .unwrap_or_else(|| "-".to_string());
332 html.push_str(&format!("<td>{}</td>\n", escape_html(&value)));
334 }
335 html.push_str("</tr>\n");
336 }
337
338 html.push_str("</tbody>\n</table>\n</div>");
339
340 Ok(html)
341 }
342
343 pub fn search(&self, query: &str, objects: Vec<T>) -> Vec<T> {
345 if query.is_empty() || self.search_fields.is_empty() {
346 return objects;
347 }
348
349 objects
350 .into_iter()
351 .filter(|obj| {
352 let obj_json = serde_json::to_value(obj).ok();
353 if let Some(json) = obj_json {
354 self.search_fields.iter().any(|field| {
355 json.get(field)
356 .and_then(|v| v.as_str())
357 .map(|s| s.to_lowercase().contains(&query.to_lowercase()))
358 .unwrap_or(false)
359 })
360 } else {
361 false
362 }
363 })
364 .collect()
365 }
366
367 pub fn filter(&self, filters: &HashMap<String, String>, objects: Vec<T>) -> Vec<T> {
369 if filters.is_empty() {
370 return objects;
371 }
372
373 objects
374 .into_iter()
375 .filter(|obj| {
376 let obj_json = serde_json::to_value(obj).ok();
377 if let Some(json) = obj_json {
378 filters.iter().all(|(field, value)| {
379 json.get(field)
380 .map(|v| {
381 match v {
383 serde_json::Value::String(s) => s == value,
384 serde_json::Value::Bool(b) => {
385 value.to_lowercase() == b.to_string()
386 }
387 serde_json::Value::Number(n) => {
388 let n_str = n.to_string();
390 n_str == value.as_str()
391 }
392 _ => {
393 if let Some(s) = v.as_str() {
395 s == value.as_str()
396 } else {
397 let v_str = v.to_string();
399 v_str == value.as_str()
400 }
401 }
402 }
403 })
404 .unwrap_or(false)
405 })
406 } else {
407 false
408 }
409 })
410 .collect()
411 }
412}
413
414#[async_trait]
415impl<T: Model + Serialize + for<'de> Deserialize<'de> + Clone + Send + Sync> AdminView
416 for ModelAdmin<T>
417{
418 async fn render(&self) -> Result<String> {
419 self.render_list().await
420 }
421}
422
423impl<T: Model + Serialize + for<'de> Deserialize<'de> + Clone> Default for ModelAdmin<T> {
424 fn default() -> Self {
425 Self::new()
426 }
427}