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>
118where
119 M: Model + Serialize + for<'de> Deserialize<'de> + Send + Sync + Clone,
120{
121 list_display: Vec<String>,
122 search_fields: Vec<String>,
123 list_filter: Vec<String>,
124 ordering: Vec<String>,
125 list_per_page: usize,
126 show_full_result_count: bool,
127 readonly_fields: Vec<String>,
128 queryset: Option<Vec<M>>,
129 _phantom: PhantomData<M>,
130}
131
132impl<T: Model + Serialize + for<'de> Deserialize<'de> + Clone> ModelAdmin<T> {
133 pub fn new() -> Self {
170 Self {
171 list_display: vec![],
172 list_filter: vec![],
173 search_fields: vec![],
174 ordering: vec![],
175 list_per_page: 100,
176 show_full_result_count: true,
177 readonly_fields: vec![],
178 queryset: None,
179 _phantom: PhantomData,
180 }
181 }
182
183 pub fn with_list_display(mut self, fields: Vec<String>) -> Self {
221 self.list_display = fields;
222 self
223 }
224
225 pub fn with_list_filter(mut self, fields: Vec<String>) -> Self {
227 self.list_filter = fields;
228 self
229 }
230
231 pub fn with_search_fields(mut self, fields: Vec<String>) -> Self {
233 self.search_fields = fields;
234 self
235 }
236
237 pub fn with_ordering(mut self, fields: Vec<String>) -> Self {
239 self.ordering = fields;
240 self
241 }
242
243 pub fn with_list_per_page(mut self, count: usize) -> Self {
245 self.list_per_page = count;
246 self
247 }
248
249 pub fn with_show_full_result_count(mut self, show: bool) -> Self {
251 self.show_full_result_count = show;
252 self
253 }
254
255 pub fn with_readonly_fields(mut self, fields: Vec<String>) -> Self {
257 self.readonly_fields = fields;
258 self
259 }
260
261 pub fn with_queryset(mut self, queryset: Vec<T>) -> Self {
263 self.queryset = Some(queryset);
264 self
265 }
266
267 pub fn list_display(&self) -> &[String] {
269 &self.list_display
270 }
271
272 pub fn list_filter(&self) -> &[String] {
274 &self.list_filter
275 }
276
277 pub fn search_fields(&self) -> &[String] {
279 &self.search_fields
280 }
281
282 pub fn ordering(&self) -> &[String] {
284 &self.ordering
285 }
286
287 pub fn list_per_page(&self) -> usize {
289 self.list_per_page
290 }
291
292 pub fn show_full_result_count(&self) -> bool {
294 self.show_full_result_count
295 }
296
297 pub fn readonly_fields(&self) -> &[String] {
299 &self.readonly_fields
300 }
301
302 pub async fn get_queryset(&self) -> Result<Vec<T>> {
304 match &self.queryset {
305 Some(qs) => Ok(qs.clone()),
306 None => Ok(Vec::new()),
307 }
308 }
309
310 pub async fn render_list(&self) -> Result<String> {
312 let objects = self.get_queryset().await?;
313 let count = objects.len();
314
315 let mut html = String::from("<div class=\"admin-list\">\n");
316 html.push_str(&format!("<h2>{} List</h2>\n", escape_html(T::table_name())));
317 html.push_str(&format!("<p>Total: {} items</p>\n", count));
318
319 html.push_str("<table>\n<thead>\n<tr>\n");
321 for field in &self.list_display {
322 html.push_str(&format!("<th>{}</th>\n", escape_html(field)));
323 }
324 html.push_str("</tr>\n</thead>\n<tbody>\n");
325
326 for obj in objects {
328 html.push_str("<tr>\n");
329 let obj_json =
330 serde_json::to_value(&obj).map_err(|e| Error::Serialization(e.to_string()))?;
331
332 for field in &self.list_display {
333 let value = obj_json
334 .get(field)
335 .map(|v| v.to_string())
336 .unwrap_or_else(|| "-".to_string());
337 html.push_str(&format!("<td>{}</td>\n", escape_html(&value)));
339 }
340 html.push_str("</tr>\n");
341 }
342
343 html.push_str("</tbody>\n</table>\n</div>");
344
345 Ok(html)
346 }
347
348 pub fn search(&self, query: &str, objects: Vec<T>) -> Vec<T> {
350 if query.is_empty() || self.search_fields.is_empty() {
351 return objects;
352 }
353
354 objects
355 .into_iter()
356 .filter(|obj| {
357 let obj_json = serde_json::to_value(obj).ok();
358 if let Some(json) = obj_json {
359 self.search_fields.iter().any(|field| {
360 json.get(field)
361 .and_then(|v| v.as_str())
362 .map(|s| s.to_lowercase().contains(&query.to_lowercase()))
363 .unwrap_or(false)
364 })
365 } else {
366 false
367 }
368 })
369 .collect()
370 }
371
372 pub fn filter(&self, filters: &HashMap<String, String>, objects: Vec<T>) -> Vec<T> {
374 if filters.is_empty() {
375 return objects;
376 }
377
378 objects
379 .into_iter()
380 .filter(|obj| {
381 let obj_json = serde_json::to_value(obj).ok();
382 if let Some(json) = obj_json {
383 filters.iter().all(|(field, value)| {
384 json.get(field)
385 .map(|v| {
386 match v {
388 serde_json::Value::String(s) => s == value,
389 serde_json::Value::Bool(b) => {
390 value.to_lowercase() == b.to_string()
391 }
392 serde_json::Value::Number(n) => {
393 let n_str = n.to_string();
395 n_str == value.as_str()
396 }
397 _ => {
398 if let Some(s) = v.as_str() {
400 s == value.as_str()
401 } else {
402 let v_str = v.to_string();
404 v_str == value.as_str()
405 }
406 }
407 }
408 })
409 .unwrap_or(false)
410 })
411 } else {
412 false
413 }
414 })
415 .collect()
416 }
417}
418
419#[async_trait]
420impl<T: Model + Serialize + for<'de> Deserialize<'de> + Clone + Send + Sync> AdminView
421 for ModelAdmin<T>
422{
423 async fn render(&self) -> Result<String> {
424 self.render_list().await
425 }
426}
427
428impl<T: Model + Serialize + for<'de> Deserialize<'de> + Clone> Default for ModelAdmin<T> {
429 fn default() -> Self {
430 Self::new()
431 }
432}