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}")]
24 ModelNotFound(String),
25
26 #[error("Field not found: {0}")]
28 FieldNotFound(String),
29
30 #[error("Invalid filter: {0}")]
32 InvalidFilter(String),
33
34 #[error("Permission denied: {0}")]
36 PermissionDenied(String),
37
38 #[error("Query error: {0}")]
40 QueryError(String),
41}
42
43impl From<AdminError> for Error {
44 fn from(err: AdminError) -> Self {
45 Error::Validation(err.to_string())
46 }
47}
48
49#[async_trait]
51pub trait AdminView: Send + Sync {
52 async fn render(&self) -> Result<String>;
54
55 fn has_view_permission(&self) -> bool {
57 true
58 }
59
60 fn has_add_permission(&self) -> bool {
62 true
63 }
64
65 fn has_change_permission(&self) -> bool {
67 true
68 }
69
70 fn has_delete_permission(&self) -> bool {
72 true
73 }
74}
75
76pub struct ModelAdmin<M>
119where
120 M: Model + Serialize + for<'de> Deserialize<'de> + Send + Sync + Clone,
121{
122 list_display: Vec<String>,
123 search_fields: Vec<String>,
124 list_filter: Vec<String>,
125 ordering: Vec<String>,
126 list_per_page: usize,
127 show_full_result_count: bool,
128 readonly_fields: Vec<String>,
129 queryset: Option<Vec<M>>,
130 _phantom: PhantomData<M>,
131}
132
133impl<T: Model + Serialize + for<'de> Deserialize<'de> + Clone> ModelAdmin<T> {
134 pub fn new() -> Self {
172 Self {
173 list_display: vec![],
174 list_filter: vec![],
175 search_fields: vec![],
176 ordering: vec![],
177 list_per_page: 100,
178 show_full_result_count: true,
179 readonly_fields: vec![],
180 queryset: None,
181 _phantom: PhantomData,
182 }
183 }
184
185 pub fn with_list_display(mut self, fields: Vec<String>) -> Self {
224 self.list_display = fields;
225 self
226 }
227
228 pub fn with_list_filter(mut self, fields: Vec<String>) -> Self {
230 self.list_filter = fields;
231 self
232 }
233
234 pub fn with_search_fields(mut self, fields: Vec<String>) -> Self {
236 self.search_fields = fields;
237 self
238 }
239
240 pub fn with_ordering(mut self, fields: Vec<String>) -> Self {
242 self.ordering = fields;
243 self
244 }
245
246 pub fn with_list_per_page(mut self, count: usize) -> Self {
248 self.list_per_page = count;
249 self
250 }
251
252 pub fn with_show_full_result_count(mut self, show: bool) -> Self {
254 self.show_full_result_count = show;
255 self
256 }
257
258 pub fn with_readonly_fields(mut self, fields: Vec<String>) -> Self {
260 self.readonly_fields = fields;
261 self
262 }
263
264 pub fn with_queryset(mut self, queryset: Vec<T>) -> Self {
266 self.queryset = Some(queryset);
267 self
268 }
269
270 pub fn list_display(&self) -> &[String] {
272 &self.list_display
273 }
274
275 pub fn list_filter(&self) -> &[String] {
277 &self.list_filter
278 }
279
280 pub fn search_fields(&self) -> &[String] {
282 &self.search_fields
283 }
284
285 pub fn ordering(&self) -> &[String] {
287 &self.ordering
288 }
289
290 pub fn list_per_page(&self) -> usize {
292 self.list_per_page
293 }
294
295 pub fn show_full_result_count(&self) -> bool {
297 self.show_full_result_count
298 }
299
300 pub fn readonly_fields(&self) -> &[String] {
302 &self.readonly_fields
303 }
304
305 pub async fn get_queryset(&self) -> Result<Vec<T>> {
307 match &self.queryset {
308 Some(qs) => Ok(qs.clone()),
309 None => Ok(Vec::new()),
310 }
311 }
312
313 pub async fn render_list(&self) -> Result<String> {
315 let objects = self.get_queryset().await?;
316 let count = objects.len();
317
318 let mut html = String::from("<div class=\"admin-list\">\n");
319 html.push_str(&format!("<h2>{} List</h2>\n", escape_html(T::table_name())));
320 html.push_str(&format!("<p>Total: {} items</p>\n", count));
321
322 html.push_str("<table>\n<thead>\n<tr>\n");
324 for field in &self.list_display {
325 html.push_str(&format!("<th>{}</th>\n", escape_html(field)));
326 }
327 html.push_str("</tr>\n</thead>\n<tbody>\n");
328
329 for obj in objects {
331 html.push_str("<tr>\n");
332 let obj_json =
333 serde_json::to_value(&obj).map_err(|e| Error::Serialization(e.to_string()))?;
334
335 for field in &self.list_display {
336 let value = obj_json
337 .get(field)
338 .map(|v| v.to_string())
339 .unwrap_or_else(|| "-".to_string());
340 html.push_str(&format!("<td>{}</td>\n", escape_html(&value)));
342 }
343 html.push_str("</tr>\n");
344 }
345
346 html.push_str("</tbody>\n</table>\n</div>");
347
348 Ok(html)
349 }
350
351 pub fn search(&self, query: &str, objects: Vec<T>) -> Vec<T> {
353 if query.is_empty() || self.search_fields.is_empty() {
354 return objects;
355 }
356
357 objects
358 .into_iter()
359 .filter(|obj| {
360 let obj_json = serde_json::to_value(obj).ok();
361 if let Some(json) = obj_json {
362 self.search_fields.iter().any(|field| {
363 json.get(field)
364 .and_then(|v| v.as_str())
365 .map(|s| s.to_lowercase().contains(&query.to_lowercase()))
366 .unwrap_or(false)
367 })
368 } else {
369 false
370 }
371 })
372 .collect()
373 }
374
375 pub fn filter(&self, filters: &HashMap<String, String>, objects: Vec<T>) -> Vec<T> {
377 if filters.is_empty() {
378 return objects;
379 }
380
381 objects
382 .into_iter()
383 .filter(|obj| {
384 let obj_json = serde_json::to_value(obj).ok();
385 if let Some(json) = obj_json {
386 filters.iter().all(|(field, value)| {
387 json.get(field)
388 .map(|v| {
389 match v {
391 serde_json::Value::String(s) => s == value,
392 serde_json::Value::Bool(b) => {
393 value.to_lowercase() == b.to_string()
394 }
395 serde_json::Value::Number(n) => {
396 let n_str = n.to_string();
398 n_str == value.as_str()
399 }
400 _ => {
401 if let Some(s) = v.as_str() {
403 s == value.as_str()
404 } else {
405 let v_str = v.to_string();
407 v_str == value.as_str()
408 }
409 }
410 }
411 })
412 .unwrap_or(false)
413 })
414 } else {
415 false
416 }
417 })
418 .collect()
419 }
420}
421
422#[async_trait]
423impl<T: Model + Serialize + for<'de> Deserialize<'de> + Clone + Send + Sync> AdminView
424 for ModelAdmin<T>
425{
426 async fn render(&self) -> Result<String> {
427 self.render_list().await
428 }
429}
430
431impl<T: Model + Serialize + for<'de> Deserialize<'de> + Clone> Default for ModelAdmin<T> {
432 fn default() -> Self {
433 Self::new()
434 }
435}